diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 9ee0b4842a..2b2a1aa548 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -29,6 +29,13 @@ load.system = System load.mod = Mods load.scripts = Scripts +be.update = A new Bleeding Edge build is available: +be.update.confirm = Download it and restart now? +be.updating = Updating... +be.ignore = Ignore +be.noupdates = No updates found. +be.check = Check for updates + schematic = Schematic schematic.add = Save Schematic... schematics = Schematics diff --git a/core/src/mindustry/Vars.java b/core/src/mindustry/Vars.java index dfefeb2a68..2cfa4bad45 100644 --- a/core/src/mindustry/Vars.java +++ b/core/src/mindustry/Vars.java @@ -21,6 +21,7 @@ import mindustry.gen.*; import mindustry.input.*; import mindustry.maps.*; import mindustry.mod.*; +import mindustry.net.*; import mindustry.net.Net; import mindustry.world.blocks.defense.ForceProjector.*; @@ -136,6 +137,8 @@ public class Vars implements Loadable{ public static Fi modDirectory; /** data subdirectory used for schematics */ public static Fi schematicDirectory; + /** data subdirectory used for bleeding edge build versions */ + public static Fi bebuildDirectory; /** map file extension */ public static final String mapExtension = "msav"; /** save file extension */ @@ -157,6 +160,7 @@ public class Vars implements Loadable{ public static Platform platform = new Platform(){}; public static Mods mods; public static Schematics schematics = new Schematics(); + public static BeControl becontrol; public static World world; public static Maps maps; @@ -220,6 +224,7 @@ public class Vars implements Loadable{ defaultWaves = new DefaultWaves(); collisions = new EntityCollisions(); world = new World(); + becontrol = new BeControl(); maps = new Maps(); spawner = new WaveSpawner(); @@ -260,6 +265,7 @@ public class Vars implements Loadable{ tmpDirectory = dataDirectory.child("tmp/"); modDirectory = dataDirectory.child("mods/"); schematicDirectory = dataDirectory.child("schematics/"); + bebuildDirectory = dataDirectory.child("be_builds/"); modDirectory.mkdirs(); diff --git a/core/src/mindustry/ai/Pathfinder.java b/core/src/mindustry/ai/Pathfinder.java index 6a2515bb7a..a16ac8f410 100644 --- a/core/src/mindustry/ai/Pathfinder.java +++ b/core/src/mindustry/ai/Pathfinder.java @@ -139,7 +139,7 @@ public class Pathfinder implements Runnable{ //stop looping when interrupted externally return; } - }catch(Exception e){ + }catch(Throwable e){ e.printStackTrace(); } } diff --git a/core/src/mindustry/core/Version.java b/core/src/mindustry/core/Version.java index 700b8776e6..08a105d8fb 100644 --- a/core/src/mindustry/core/Version.java +++ b/core/src/mindustry/core/Version.java @@ -9,9 +9,9 @@ import arc.util.io.*; public class Version{ /** Build type. 'official' for official releases; 'custom' or 'bleeding edge' are also used. */ - public static String type; + public static String type = "unknown"; /** Build modifier, e.g. 'alpha' or 'release' */ - public static String modifier; + public static String modifier = "unknown"; /** Number specifying the major version, e.g. '4' */ public static int number; /** Build number, e.g. '43'. set to '-1' for custom builds. */ diff --git a/core/src/mindustry/core/World.java b/core/src/mindustry/core/World.java index ea0e9aa054..bbc3f4074c 100644 --- a/core/src/mindustry/core/World.java +++ b/core/src/mindustry/core/World.java @@ -217,7 +217,7 @@ public class World{ public void loadMap(Map map, Rules checkRules){ try{ SaveIO.load(map.file, new FilterContext(map)); - }catch(Exception e){ + }catch(Throwable e){ Log.err(e); if(!headless){ ui.showErrorMessage("$map.invalid"); diff --git a/core/src/mindustry/game/EventType.java b/core/src/mindustry/game/EventType.java index dfa1469fc1..6cbaba5e4b 100644 --- a/core/src/mindustry/game/EventType.java +++ b/core/src/mindustry/game/EventType.java @@ -28,7 +28,8 @@ public class EventType{ exclusionDeath, suicideBomb, openWiki, - teamCoreDamage + teamCoreDamage, + socketConfigChanged } public static class WinEvent{} diff --git a/core/src/mindustry/net/Administration.java b/core/src/mindustry/net/Administration.java index dabf679d34..157566a5c3 100644 --- a/core/src/mindustry/net/Administration.java +++ b/core/src/mindustry/net/Administration.java @@ -18,11 +18,6 @@ public class Administration{ private Array chatFilters = new Array<>(); public Administration(){ - Core.settings.defaults( - "strict", true, - "servername", "Server" - ); - load(); } @@ -51,21 +46,12 @@ public class Administration{ Core.settings.putSave("playerlimit", limit); } - public void setStrict(boolean on){ - Core.settings.putSave("strict", on); - } - public boolean getStrict(){ - return Core.settings.getBool("strict"); + return Config.strict.bool(); } public boolean allowsCustomClients(){ - return Core.settings.getBool("allow-custom", !headless); - } - - public void setCustomClients(boolean allowed){ - Core.settings.put("allow-custom", allowed); - Core.settings.save(); + return Config.allowCustomClients.bool(); } /** Call when a player joins to update their information here. */ @@ -219,11 +205,7 @@ public class Administration{ } public boolean isWhitelistEnabled(){ - return Core.settings.getBool("whitelist", false); - } - - public void setWhitelist(boolean enabled){ - Core.settings.putSave("whitelist", enabled); + return Config.whitelist.bool(); } public boolean isWhitelisted(String id, String usid){ @@ -333,6 +315,79 @@ public class Administration{ whitelist = Core.settings.getObject("whitelisted", Array.class, Array::new); } + /** Server configuration definition. Each config value can be a string, boolean or number. */ + public enum Config{ + name("The server name as displayed on clients.", "Server", "servername"), + port("The port to host on.", Vars.port), + autoUpdate("Whether to auto-restart when a new update arrives.", false), + crashReport("Whether to send crash reports.", false, "crashreport"), + logging("Whether to log everything to files.", true), + strict("Whether strict mode is on - corrects positions and prevents duplicate UUIDs.", true), + socketInput("Allows a local application to control this server through a local TCP socket.", false, "socket", () -> Events.fire(Trigger.socketConfigChanged)), + socketInputPort("The port for socket input.", 6859, () -> Events.fire(Trigger.socketConfigChanged)), + socketInputAddress("The bind address for socket input.", "localhost", () -> Events.fire(Trigger.socketConfigChanged)), + allowCustomClients("Whether custom clients are allowed to connect.", !headless, "allow-custom"), + whitelist("Whether the whitelist is used.", false); + + public static final Config[] all = values(); + + public final Object defaultValue; + public final String key, description; + final Runnable changed; + + Config(String description, Object def){ + this(description, def, null, null); + } + + Config(String description, Object def, String key){ + this(description, def, key, null); + } + + Config(String description, Object def, Runnable changed){ + this(description, def, null, changed); + } + + Config(String description, Object def, String key, Runnable changed){ + this.description = description; + this.key = key == null ? name() : key; + this.defaultValue = def; + this.changed = changed == null ? () -> {} : changed; + } + + public boolean isNum(){ + return defaultValue instanceof Integer; + } + + public boolean isBool(){ + return defaultValue instanceof Boolean; + } + + public boolean isString(){ + return defaultValue instanceof String; + } + + public Object get(){ + return Core.settings.get(key, defaultValue); + } + + public boolean bool(){ + return Core.settings.getBool(key, (Boolean)defaultValue); + } + + public int num(){ + return Core.settings.getInt(key, (Integer)defaultValue); + } + + public String string(){ + return Core.settings.getString(key, (String)defaultValue); + } + + public void set(Object value){ + Core.settings.putSave(key, value); + changed.run(); + } + } + @Serialize public static class PlayerInfo{ public String id; diff --git a/core/src/mindustry/net/BeControl.java b/core/src/mindustry/net/BeControl.java new file mode 100644 index 0000000000..d81c5e721d --- /dev/null +++ b/core/src/mindustry/net/BeControl.java @@ -0,0 +1,168 @@ +package mindustry.net; + +import arc.*; +import arc.Net.*; +import arc.files.*; +import arc.func.*; +import arc.util.*; +import arc.util.async.*; +import arc.util.serialization.*; +import mindustry.core.*; +import mindustry.gen.*; +import mindustry.graphics.*; +import mindustry.net.Administration.*; +import mindustry.ui.*; +import mindustry.ui.dialogs.*; + +import java.io.*; +import java.net.*; + +import static mindustry.Vars.*; + +/** Handles control of bleeding edge builds. */ +public class BeControl{ + private static final int updateInterval = 60; + + private AsyncExecutor executor = new AsyncExecutor(1); + private boolean checkUpdates = true; + private boolean updateAvailable; + private String updateUrl; + private int updateBuild; + + /** @return whether this is a bleeding edge build. */ + public boolean active(){ + return Version.type.equals("bleeding-edge"); + } + + public BeControl(){ + if(active()){ + Timer.schedule(() -> { + if(checkUpdates && !mobile){ + checkUpdate(t -> {}); + } + }, 1, updateInterval); + } + } + + /** asynchronously checks for updates. */ + public void checkUpdate(Boolc done){ + Core.net.httpGet("https://api.github.com/repos/Anuken/MindustryBuilds/releases/latest", res -> { + if(res.getStatus() == HttpStatus.OK){ + Jval val = Jval.read(res.getResultAsString()); + int newBuild = Strings.parseInt(val.getString("tag_name", "0")); + if(newBuild > Version.build){ + Jval asset = val.get("assets").asArray().find(v -> v.getString("name", "").startsWith(headless ? "Mindustry-BE-Server" : "Mindustry-BE-Desktop")); + String url = asset.getString("browser_download_url", ""); + updateAvailable = true; + updateBuild = newBuild; + updateUrl = url; + showUpdateDialog(); + Core.app.post(() -> done.get(true)); + }else{ + Core.app.post(() -> done.get(false)); + } + }else{ + Core.app.post(() -> done.get(false)); + Log.err("Update check responded with: {0}", res.getStatus()); + } + }, error -> { + if(!headless){ + ui.showException(error); + }else{ + error.printStackTrace(); + } + }); + } + + /** @return whether a new update is available */ + public boolean isUpdateAvailable(){ + return updateAvailable; + } + + /** shows the dialog for updating the game on desktop, or a prompt for doing so on the server */ + public void showUpdateDialog(){ + if(!updateAvailable) return; + + if(!headless){ + ui.showCustomConfirm(Core.bundle.format("be.update", "") + " " + updateBuild, "$be.update.confirm", "$ok", "$be.ignore", () -> { + boolean[] cancel = {false}; + float[] progress = {0}; + int[] length = {0}; + Fi file = bebuildDirectory.child("client-be-" + updateBuild + ".jar"); + + FloatingDialog dialog = new FloatingDialog("$be.updating"); + download(updateUrl, file, i -> length[0] = i, v -> progress[0] = v, () -> cancel[0], () -> { + try{ + Runtime.getRuntime().exec(new String[]{"java", "-DlastBuild=" + Version.build, "-Dberestart", "-jar", file.absolutePath()}); + System.exit(0); + }catch(IOException e){ + ui.showException(e); + } + }, e -> { + dialog.hide(); + ui.showException(e); + }); + + dialog.cont.add(new Bar(() -> length[0] == 0 ? Core.bundle.get("be.updating") : (int)(progress[0] * length[0]) / 1024/ 1024 + "/" + length[0]/1024/1024 + " MB", () -> Pal.accent, () -> progress[0])).width(400f).height(70f); + dialog.buttons.addImageTextButton("$cancel", Icon.cancelSmall, () -> { + cancel[0] = true; + dialog.hide(); + }).size(210f, 64f); + dialog.setFillParent(false); + dialog.show(); + }, () -> checkUpdates = false); + }else{ + Log.info("&lcA new update is available: &lyBleeding Edge build {0}", updateBuild); + if(Config.autoUpdate.bool()){ + Log.info("&lcAuto-downloading next version..."); + + try{ + Fi source = Fi.get(BeControl.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath()); + Fi dest = source.sibling("server-be-" + updateBuild + ".jar"); + + download(updateUrl, dest, + len -> Core.app.post(() -> Log.info("&ly| Size: {0} MB.", Strings.fixed((float)len / 1024 / 1024, 2))), + progress -> {}, + () -> false, + () -> { + Log.info("&lcVersion downloaded, exiting. Note that if you are not using the run-server script, the server will not restart automatically."); + dest.copyTo(source); + dest.delete(); + System.exit(2); //this will cause a restart if using the script + }, + Throwable::printStackTrace); + }catch(Exception e){ + e.printStackTrace(); + } + } + checkUpdates = false; + //todo server updates + } + } + + private void download(String furl, Fi dest, Intc length, Floatc progressor, Boolp canceled, Runnable done, Cons error){ + executor.submit(() -> { + try{ + HttpURLConnection con = (HttpURLConnection)new URL(furl).openConnection(); + BufferedInputStream in = new BufferedInputStream(con.getInputStream()); + OutputStream out = dest.write(false, 4096); + + byte[] data = new byte[4096]; + long size = con.getContentLength(); + long counter = 0; + length.get((int)size); + int x; + while((x = in.read(data, 0, data.length)) >= 0 && !canceled.get()){ + counter += x; + progressor.get((float)counter / (float)size); + out.write(data, 0, x); + } + out.close(); + in.close(); + if(!canceled.get()) done.run(); + }catch(Throwable e){ + error.get(e); + } + }); + } +} diff --git a/core/src/mindustry/net/CrashSender.java b/core/src/mindustry/net/CrashSender.java index 43692c77d6..1b24b2af3a 100644 --- a/core/src/mindustry/net/CrashSender.java +++ b/core/src/mindustry/net/CrashSender.java @@ -27,7 +27,9 @@ public class CrashSender{ exception.printStackTrace(); //don't create crash logs for custom builds, as it's expected - if(Version.build == -1 || (System.getProperty("user.name").equals("anuke") && "release".equals(Version.modifier))) return; + if(Version.build == -1 || (System.getProperty("user.name").equals("anuke") && "release".equals(Version.modifier))){ + ret(); + } //attempt to load version regardless if(Version.number == 0){ @@ -63,7 +65,7 @@ public class CrashSender{ try{ //check crash report setting if(!Core.settings.getBool("crashreport", true)){ - return; + ret(); } }catch(Throwable ignored){ //if there's no settings init we don't know what the user wants but chances are it's an important crash, so send it anyway @@ -72,14 +74,14 @@ public class CrashSender{ try{ //check any mods - if there are any, don't send reports if(Vars.mods != null && !Vars.mods.list().isEmpty()){ - return; + ret(); } }catch(Throwable ignored){ } //do not send exceptions that occur for versions that can't be parsed if(Version.number == 0){ - return; + ret(); } boolean netActive = false, netServer = false; @@ -130,12 +132,16 @@ public class CrashSender{ while(!sent[0]){ Thread.sleep(30); } - }catch(InterruptedException ignored){ - } + }catch(InterruptedException ignored){} }catch(Throwable death){ death.printStackTrace(); - System.exit(1); } + + ret(); + } + + private static void ret(){ + System.exit(1); } private static void httpPost(String url, String content, Cons success, Cons failure){ diff --git a/core/src/mindustry/net/NetworkIO.java b/core/src/mindustry/net/NetworkIO.java index d877ac6e3a..1c6e1acba2 100644 --- a/core/src/mindustry/net/NetworkIO.java +++ b/core/src/mindustry/net/NetworkIO.java @@ -1,12 +1,12 @@ package mindustry.net; -import arc.*; import arc.util.*; import mindustry.core.*; import mindustry.entities.type.*; import mindustry.game.*; import mindustry.io.*; import mindustry.maps.Map; +import mindustry.net.Administration.*; import java.io.*; import java.nio.*; @@ -62,7 +62,7 @@ public class NetworkIO{ } public static ByteBuffer writeServerData(){ - String name = (headless ? Core.settings.getString("servername") : player.name); + String name = (headless ? Config.name.string() : player.name); String map = world.getMap() == null ? "None" : world.getMap().name(); ByteBuffer buffer = ByteBuffer.allocate(256); diff --git a/core/src/mindustry/ui/dialogs/ModsDialog.java b/core/src/mindustry/ui/dialogs/ModsDialog.java index 96b71b6861..4b42f64d80 100644 --- a/core/src/mindustry/ui/dialogs/ModsDialog.java +++ b/core/src/mindustry/ui/dialogs/ModsDialog.java @@ -32,7 +32,7 @@ public class ModsDialog extends FloatingDialog{ buttons.row(); - buttons.addImageTextButton("$mods.guide", Icon.wiki, + buttons.addImageTextButton("$mods.guide", Icon.link, () -> Core.net.openURI(modGuideURL)) .size(210, 64f); diff --git a/core/src/mindustry/ui/fragments/MenuFragment.java b/core/src/mindustry/ui/fragments/MenuFragment.java index 535f4d6046..8737db0d9a 100644 --- a/core/src/mindustry/ui/fragments/MenuFragment.java +++ b/core/src/mindustry/ui/fragments/MenuFragment.java @@ -59,6 +59,18 @@ public class MenuFragment extends Fragment{ if(mobile){ parent.fill(c -> c.bottom().left().addButton("", Styles.infot, ui.about::show).size(84, 45)); parent.fill(c -> c.bottom().right().addButton("", Styles.discordt, ui.discord::show).size(84, 45)); + }else if(becontrol.active()){ + parent.fill(c -> c.bottom().right().addImageTextButton("$be.check", Icon.refreshSmall, () -> { + ui.loadfrag.show(); + becontrol.checkUpdate(result -> { + ui.loadfrag.hide(); + if(!result){ + ui.showInfo("$be.noupdates"); + } + }); + }).size(200, 60).update(t -> { + t.getLabel().setColor(becontrol.isUpdateAvailable() ? Tmp.c1.set(Color.white).lerp(Pal.accent, Mathf.absin(5f, 1f)) : Color.white); + })); } String versionText = "[#ffffffba]" + ((Version.build == -1) ? "[#fc8140aa]custom build" : (Version.type.equals("official") ? Version.modifier : Version.type) + " build " + Version.build + (Version.revision == 0 ? "" : "." + Version.revision)); diff --git a/core/src/mindustry/world/Tile.java b/core/src/mindustry/world/Tile.java index 0572c74286..119af5b8df 100644 --- a/core/src/mindustry/world/Tile.java +++ b/core/src/mindustry/world/Tile.java @@ -257,7 +257,7 @@ public class Tile implements Position, TargetTrait{ } public boolean solid(){ - return block.solid || block.isSolidFor(this) || (isLinked() && link().solid()); + return block.solid || block.isSolidFor(this) || (isLinked() && link() != this && link().solid()); } public boolean breakable(){ diff --git a/core/src/mindustry/world/blocks/production/SolidPump.java b/core/src/mindustry/world/blocks/production/SolidPump.java index 854d4be010..ac199c7b27 100644 --- a/core/src/mindustry/world/blocks/production/SolidPump.java +++ b/core/src/mindustry/world/blocks/production/SolidPump.java @@ -4,6 +4,7 @@ import arc.Core; import arc.graphics.g2d.Draw; import arc.graphics.g2d.TextureRegion; import arc.math.Mathf; +import arc.util.*; import mindustry.content.Fx; import mindustry.content.Liquids; import mindustry.entities.Effects; @@ -51,8 +52,8 @@ public class SolidPump extends Pump{ public void setBars(){ super.setBars(); bars.add("efficiency", entity -> new Bar(() -> - Core.bundle.formatFloat("bar.efficiency", - ((((SolidPumpEntity)entity).boost + 1f) * ((SolidPumpEntity)entity).warmup) * 100 * percentSolid(entity.tile.x, entity.tile.y), 1), + Core.bundle.formatFloat("bar.pumpspeed", + ((SolidPumpEntity)entity).lastPump / Time.delta() * 60, 1), () -> Pal.ammo, () -> ((SolidPumpEntity)entity).warmup)); } @@ -104,11 +105,13 @@ public class SolidPump extends Pump{ if(tile.entity.cons.valid() && typeLiquid(tile) < liquidCapacity - 0.001f){ float maxPump = Math.min(liquidCapacity - typeLiquid(tile), pumpAmount * entity.delta() * fraction * entity.efficiency()); tile.entity.liquids.add(result, maxPump); + entity.lastPump = maxPump; entity.warmup = Mathf.lerpDelta(entity.warmup, 1f, 0.02f); if(Mathf.chance(entity.delta() * updateEffectChance)) Effects.effect(updateEffect, entity.x + Mathf.range(size * 2f), entity.y + Mathf.range(size * 2f)); }else{ entity.warmup = Mathf.lerpDelta(entity.warmup, 0f, 0.02f); + entity.lastPump = 0f; } entity.pumpTime += entity.warmup * entity.delta(); @@ -153,5 +156,6 @@ public class SolidPump extends Pump{ public float warmup; public float pumpTime; public float boost; + public float lastPump; } } diff --git a/gradle.properties b/gradle.properties index 19101944cf..0b0bc673e4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xms256m -Xmx1024m -archash=60a9ebe264f92f2c3082596c77b9ab29474c4a7f +archash=0e25944f7f3a065ed6707f0dbe48548980cad8a6 diff --git a/run-server b/run-server index 0358cb521b..25a8254723 100755 --- a/run-server +++ b/run-server @@ -5,4 +5,12 @@ if [[ $# -eq 0 ]] ; then fi ./gradlew server:dist -Pbuildversion=$1 + +while true; do +#auto-restart until ctrl-c or exit 0 java -jar -XX:+HeapDumpOnOutOfMemoryError server/build/libs/server-release.jar +excode=$? +if [ $excode -eq 0 ] || [ $excode -eq 130 ]; then + exit 0 +fi +done diff --git a/server/src/mindustry/server/ServerControl.java b/server/src/mindustry/server/ServerControl.java index 3637ac972e..f1dbb59803 100644 --- a/server/src/mindustry/server/ServerControl.java +++ b/server/src/mindustry/server/ServerControl.java @@ -40,7 +40,6 @@ import static mindustry.Vars.*; public class ServerControl implements ApplicationListener{ private static final int roundExtraTime = 12; private static final int maxLogLength = 1024 * 512; - private static final int commandSocketPort = 6859; protected static String[] tags = {"&lc&fb[D]", "&lg&fb[I]", "&ly&fb[W]", "&lr&fb[E]", ""}; protected static DateTimeFormatter dateTime = DateTimeFormatter.ofPattern("MM-dd-yyyy | HH:mm:ss"); @@ -64,10 +63,6 @@ public class ServerControl implements ApplicationListener{ "bans", "", "admins", "", "shufflemode", "custom", - "crashreport", false, - "port", port, - "logging", true, - "socket", false, "globalrules", "{reactorExplosions: false}" ); @@ -75,7 +70,7 @@ public class ServerControl implements ApplicationListener{ String result = "[" + dateTime.format(LocalDateTime.now()) + "] " + format(tags[level.ordinal()] + " " + text + "&fr", args1); System.out.println(result); - if(Core.settings.getBool("logging")){ + if(Config.logging.bool()){ logToFile("[" + dateTime.format(LocalDateTime.now()) + "] " + format(tags[level.ordinal()] + " " + text + "&fr", false, args1)); } @@ -158,16 +153,19 @@ public class ServerControl implements ApplicationListener{ } }); + Events.on(Trigger.socketConfigChanged, () -> { + toggleSocket(false); + toggleSocket(Config.socketInput.bool()); + }); + if(!mods.list().isEmpty()){ info("&lc{0} mods loaded.", mods.list().size); } + toggleSocket(Config.socketInput.bool()); + info("&lcServer loaded. Type &ly'help'&lc for help."); System.out.print("> "); - - if(Core.settings.getBool("socket")){ - toggleSocket(true); - } } private void registerCommands(){ @@ -247,21 +245,6 @@ public class ServerControl implements ApplicationListener{ } }); - handler.register("port", "[port]", "Sets or displays the port for hosting the server.", arg -> { - if(arg.length == 0){ - info("&lyPort: &lc{0}", Core.settings.getInt("port")); - }else{ - int port = Strings.parseInt(arg[0]); - if(port < 0 || port > 65535){ - err("Port must be a number between 0 and 65535."); - return; - } - info("&lyPort set to {0}.", port); - Core.settings.put("port", port); - Core.settings.save(); - } - }); - handler.register("maps", "Display all available maps.", arg -> { if(!maps.all().isEmpty()){ info("Maps:"); @@ -440,16 +423,6 @@ public class ServerControl implements ApplicationListener{ }); - handler.register("name", "[name...]", "Change the server display name.", arg -> { - if(arg.length == 0){ - info("Server name is currently &lc'{0}'.", Core.settings.getString("servername")); - return; - } - Core.settings.put("servername", arg[0]); - Core.settings.save(); - info("Server name is now &lc'{0}'.", arg[0]); - }); - handler.register("playerlimit", "[off/somenumber]", "Set the server player limit.", arg -> { if(arg.length == 0){ info("Player limit is currently &lc{0}.", netServer.admins.getPlayerLimit() == 0 ? "off" : netServer.admins.getPlayerLimit()); @@ -470,14 +443,40 @@ public class ServerControl implements ApplicationListener{ } }); - handler.register("whitelist", "[on/off...]", "Enable/disable whitelisting.", arg -> { + handler.register("config", "[name] [value]", "Configure server settings.", arg -> { if(arg.length == 0){ - info("Whitelist is currently &lc{0}.", netServer.admins.isWhitelistEnabled() ? "on" : "off"); + info("&lyAll config values:"); + for(Config c : Config.all){ + Log.info("&ly| &lc{0}:&lm {1}", c.name(), c.get()); + Log.info("&ly| | {0}", c.description); + Log.info("&ly|"); + } return; } - boolean on = arg[0].equalsIgnoreCase("on"); - netServer.admins.setWhitelist(on); - info("Whitelist is now &lc{0}.", on ? "on" : "off"); + + try{ + Config c = Config.valueOf(arg[0]); + if(arg.length == 1){ + Log.info("&lc'{0}'&lg is currently &lc{0}.", c.name(), c.get()); + }else{ + if(c.isBool()){ + c.set(arg[1].equals("on") || arg[1].equals("true")); + }else if(c.isNum()){ + try{ + c.set(Integer.parseInt(arg[1])); + }catch(NumberFormatException e){ + Log.err("Not a valid number: {0}", arg[1]); + return; + } + }else if(c.isString()){ + c.set(arg[1]); + } + + Log.info("&lc{0}&lg set to &lc{1}.", c.name(), c.get()); + } + }catch(IllegalArgumentException e){ + err("Unknown config: '{0}'. Run the command with no arguments to get a list of valid configs.", arg[0]); + } }); handler.register("whitelisted", "List the entire whitelist.", arg -> { @@ -512,67 +511,6 @@ public class ServerControl implements ApplicationListener{ info("Player &ly'{0}'&lg has been un-whitelisted.", info.lastName); }); - handler.register("sync", "[on/off...]", "Enable/disable block sync. Experimental.", arg -> { - if(arg.length == 0){ - info("Block sync is currently &lc{0}.", Core.settings.getBool("blocksync") ? "enabled" : "disabled"); - return; - } - boolean on = arg[0].equalsIgnoreCase("on"); - Core.settings.putSave("blocksync", on); - info("Block syncing is now &lc{0}.", on ? "on" : "off"); - }); - - handler.register("crashreport", "", "Disables or enables automatic crash reporting", arg -> { - boolean value = arg[0].equalsIgnoreCase("on"); - Core.settings.put("crashreport", value); - Core.settings.save(); - info("Crash reporting is now {0}.", value ? "on" : "off"); - }); - - handler.register("logging", "", "Disables or enables server logs", arg -> { - boolean value = arg[0].equalsIgnoreCase("on"); - Core.settings.put("logging", value); - Core.settings.save(); - info("Logging is now {0}.", value ? "on" : "off"); - }); - - handler.register("strict", "", "Disables or enables strict mode", arg -> { - boolean value = arg[0].equalsIgnoreCase("on"); - netServer.admins.setStrict(value); - info("Strict mode is now {0}.", netServer.admins.getStrict() ? "on" : "off"); - }); - - handler.register("socketinput", "[on/off]", "Disables or enables a local TCP socket at port "+commandSocketPort+" to recieve commands from other applications", arg -> { - if(arg.length == 0){ - info("Socket input is currently &lc{0}.", Core.settings.getBool("socket") ? "on" : "off"); - return; - } - - boolean value = arg[0].equalsIgnoreCase("on"); - toggleSocket(value); - Core.settings.put("socket", value); - Core.settings.save(); - info("Socket input is now &lc{0}.", value ? "on" : "off"); - }); - - handler.register("allow-custom-clients", "[on/off]", "Allow or disallow custom clients.", arg -> { - if(arg.length == 0){ - info("Custom clients are currently &lc{0}.", netServer.admins.allowsCustomClients() ? "allowed" : "disallowed"); - return; - } - - String s = arg[0]; - if(s.equalsIgnoreCase("on")){ - netServer.admins.setCustomClients(true); - info("Custom clients enabled."); - }else if(s.equalsIgnoreCase("off")){ - netServer.admins.setCustomClients(false); - info("Custom clients disabled."); - }else{ - err("Incorrect command usage."); - } - }); - handler.register("shuffle", "[none/all/custom/builtin]", "Set map shuffling mode.", arg -> { if(arg.length == 0){ info("Shuffle mode current set to &ly'{0}'&lg.", maps.getShuffleMode()); @@ -933,8 +871,8 @@ public class ServerControl implements ApplicationListener{ private void host(){ try{ - net.host(Core.settings.getInt("port")); - info("&lcOpened a server on port {0}.", Core.settings.getInt("port")); + net.host(Config.port.num()); + info("&lcOpened a server on port {0}.", Config.port.num()); }catch(BindException e){ Log.err("Unable to host: Port already in use! Make sure no other servers are running on the same port in your network."); state.set(State.menu); @@ -968,7 +906,7 @@ public class ServerControl implements ApplicationListener{ socketThread = new Thread(() -> { try{ serverSocket = new ServerSocket(); - serverSocket.bind(new InetSocketAddress("localhost", commandSocketPort)); + serverSocket.bind(new InetSocketAddress(Config.socketInputAddress.string(), Config.socketInputPort.num())); while(true){ Socket client = serverSocket.accept(); info("&lmRecieved command socket connection: &lb{0}", serverSocket.getLocalSocketAddress());