From 5cba3050b952612317bc615eecf89ff2905e00c0 Mon Sep 17 00:00:00 2001 From: Anuken Date: Sun, 28 Jan 2018 01:21:33 -0500 Subject: [PATCH] Implemented standalone server, fixed breaking bugs --- build.gradle | 3 +- core/src/io/anuke/mindustry/Mindustry.java | 10 +- .../io/anuke/mindustry/MindustryServer.java | 25 ++++ core/src/io/anuke/mindustry/Vars.java | 3 + core/src/io/anuke/mindustry/core/Control.java | 5 +- .../io/anuke/mindustry/core/NetClient.java | 2 +- .../io/anuke/mindustry/core/NetCommon.java | 18 +-- .../io/anuke/mindustry/core/NetServer.java | 2 +- .../anuke/mindustry/core/ServerControl.java | 138 ++++++++++++++++++ core/src/io/anuke/mindustry/core/World.java | 4 +- core/src/io/anuke/mindustry/io/Maps.java | 4 +- core/src/io/anuke/mindustry/net/Net.java | 12 ++ .../src/io/anuke/mindustry/net/NetworkIO.java | 2 +- kryonet/src/io/anuke/kryonet/KryoClient.java | 4 +- .../src/io/anuke/kryonet/KryoRegistrator.java | 8 +- kryonet/src/io/anuke/kryonet/KryoServer.java | 6 +- .../mindustry/server/ServerLauncher.java | 4 +- 17 files changed, 218 insertions(+), 32 deletions(-) create mode 100644 core/src/io/anuke/mindustry/MindustryServer.java create mode 100644 core/src/io/anuke/mindustry/core/ServerControl.java diff --git a/build.gradle b/build.gradle index 1665ee61c0..0b2eeb59df 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ allprojects { appName = "Mindustry" gdxVersion = '1.9.8' aiVersion = '1.8.1' - uCoreVersion = 'ab9f87c' + uCoreVersion = '09bbb56' } repositories { @@ -120,6 +120,7 @@ project(":server") { compile project(":core") compile project(":kryonet") compile "com.badlogicgames.gdx:gdx-backend-headless:$gdxVersion" + compile "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop" } } diff --git a/core/src/io/anuke/mindustry/Mindustry.java b/core/src/io/anuke/mindustry/Mindustry.java index faec20fe90..30fd2945b4 100644 --- a/core/src/io/anuke/mindustry/Mindustry.java +++ b/core/src/io/anuke/mindustry/Mindustry.java @@ -1,8 +1,9 @@ package io.anuke.mindustry; import io.anuke.mindustry.core.*; -import io.anuke.mindustry.io.BundleLoader; import io.anuke.mindustry.io.BlockLoader; +import io.anuke.mindustry.io.BundleLoader; +import io.anuke.ucore.core.Inputs; import io.anuke.ucore.modules.ModuleCore; import static io.anuke.mindustry.Vars.*; @@ -23,4 +24,11 @@ public class Mindustry extends ModuleCore { module(netClient = new NetClient()); module(netCommon = new NetCommon()); } + + //hack + @Override + public void render() { + super.render(); + Inputs.update(); + } } diff --git a/core/src/io/anuke/mindustry/MindustryServer.java b/core/src/io/anuke/mindustry/MindustryServer.java new file mode 100644 index 0000000000..f4aba94386 --- /dev/null +++ b/core/src/io/anuke/mindustry/MindustryServer.java @@ -0,0 +1,25 @@ +package io.anuke.mindustry; + +import io.anuke.mindustry.core.*; +import io.anuke.mindustry.io.BlockLoader; +import io.anuke.mindustry.io.BundleLoader; +import io.anuke.ucore.modules.ModuleCore; + +import static io.anuke.mindustry.Vars.*; + +public class MindustryServer extends ModuleCore { + + @Override + public void init(){ + headless = true; + + BundleLoader.load(); + BlockLoader.load(); + + module(logic = new Logic()); + module(world = new World()); + module(netServer = new NetServer()); + module(netCommon = new NetCommon()); + module(serverControl = new ServerControl()); + } +} diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index 95a8b219b2..ab6e78cc64 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -62,6 +62,8 @@ public class Vars{ //whether to hide ui, only on debug public static boolean showUI = true; + public static boolean headless = false; + public static float controllerMin = 0.25f; public static float baseControllerSpeed = 11f; @@ -89,6 +91,7 @@ public class Vars{ public static NetCommon netCommon; public static NetServer netServer; public static NetClient netClient; + public static ServerControl serverControl; public static Player player; diff --git a/core/src/io/anuke/mindustry/core/Control.java b/core/src/io/anuke/mindustry/core/Control.java index ae5592af98..9687f4d706 100644 --- a/core/src/io/anuke/mindustry/core/Control.java +++ b/core/src/io/anuke/mindustry/core/Control.java @@ -137,8 +137,7 @@ public class Control extends Module{ Events.on(PlayEvent.class, () -> { renderer.clearTiles(); - player.x = world.getCore().worldx(); - player.y = world.getCore().worldy() - tilesize*2; + player.set(world.getSpawnX(), world.getSpawnY()); Core.camera.position.set(player.x, player.y, 0); @@ -263,7 +262,6 @@ public class Control extends Module{ @Override public void update(){ - Inputs.update(); if(Gdx.input != proxy){ Gdx.input = proxy; @@ -352,5 +350,6 @@ public class Control extends Module{ } } } + } } diff --git a/core/src/io/anuke/mindustry/core/NetClient.java b/core/src/io/anuke/mindustry/core/NetClient.java index 6a3d77ca5a..71cc508132 100644 --- a/core/src/io/anuke/mindustry/core/NetClient.java +++ b/core/src/io/anuke/mindustry/core/NetClient.java @@ -287,7 +287,7 @@ public class NetClient extends Module { public void update(){ if(!Net.client()) return; - if(!state.is(State.menu) && Net.active()){ + if(!state.is(State.menu)){ if(gotData) sync(); }else if(!connecting){ Net.disconnect(); diff --git a/core/src/io/anuke/mindustry/core/NetCommon.java b/core/src/io/anuke/mindustry/core/NetCommon.java index 4de233c37d..85b11aece2 100644 --- a/core/src/io/anuke/mindustry/core/NetCommon.java +++ b/core/src/io/anuke/mindustry/core/NetCommon.java @@ -22,29 +22,29 @@ public class NetCommon extends Module { public NetCommon(){ - Net.handleServer(ShootPacket.class, (id, packet) -> { + Net.handle(ShootPacket.class, (packet) -> { Player player = playerGroup.getByID(packet.playerid); Weapon weapon = (Weapon) Upgrade.getByID(packet.weaponid); weapon.shoot(player, packet.x, packet.y, packet.rotation); }); - Net.handleServer(PlacePacket.class, (id, packet) -> { + Net.handle(PlacePacket.class, (packet) -> { control.input().placeBlockInternal(packet.x, packet.y, Block.getByID(packet.block), packet.rotation, true, false); Recipe recipe = Recipes.getByResult(Block.getByID(packet.block)); if (recipe != null) state.inventory.removeItems(recipe.requirements); }); - Net.handleServer(BreakPacket.class, (id, packet) -> { + Net.handle(BreakPacket.class, (packet) -> { control.input().breakBlockInternal(packet.x, packet.y, false); }); - Net.handleServer(ChatPacket.class, (id, packet) -> { + Net.handle(ChatPacket.class, (packet) -> { ui.chatfrag.addMessage(packet.text, colorizeName(packet.id, packet.name)); }); - Net.handleServer(WeaponSwitchPacket.class, (id, packet) -> { + Net.handle(WeaponSwitchPacket.class, (packet) -> { Player player = playerGroup.getByID(packet.playerid); if (player == null) return; @@ -53,17 +53,17 @@ public class NetCommon extends Module { player.weaponRight = (Weapon) Upgrade.getByID(packet.right); }); - Net.handleServer(BlockTapPacket.class, (id, packet) -> { + Net.handle(BlockTapPacket.class, (packet) -> { Tile tile = world.tile(packet.position); tile.block().tapped(tile); }); - Net.handleServer(BlockConfigPacket.class, (id, packet) -> { + Net.handle(BlockConfigPacket.class, (packet) -> { Tile tile = world.tile(packet.position); if (tile != null) tile.block().configure(tile, packet.data); }); - Net.handleServer(PlayerDeathPacket.class, (id, packet) -> { + Net.handle(PlayerDeathPacket.class, (packet) -> { Player player = playerGroup.getByID(packet.id); if(player == null) return; @@ -76,7 +76,7 @@ public class NetCommon extends Module { packet.name = null; packet.text = message; Net.send(packet, SendMode.tcp); - ui.chatfrag.addMessage(message, null); + if(!headless) ui.chatfrag.addMessage(message, null); } public String colorizeName(int id, String name){ diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index d2d6b6b21a..e9345a0dcb 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -208,7 +208,7 @@ public class NetServer extends Module{ } public void update(){ - if(!closing && Net.active() && state.is(State.menu)){ + if(!closing && Net.server() && state.is(State.menu)){ closing = true; weapons.clear(); ui.loadfrag.show("$text.server.closing"); diff --git a/core/src/io/anuke/mindustry/core/ServerControl.java b/core/src/io/anuke/mindustry/core/ServerControl.java new file mode 100644 index 0000000000..35108cb1b5 --- /dev/null +++ b/core/src/io/anuke/mindustry/core/ServerControl.java @@ -0,0 +1,138 @@ +package io.anuke.mindustry.core; + +import com.badlogic.gdx.Gdx; +import io.anuke.mindustry.core.GameState.State; +import io.anuke.mindustry.net.Net; +import io.anuke.mindustry.net.Packets.ChatPacket; +import io.anuke.mindustry.world.Map; +import io.anuke.ucore.UCore; +import io.anuke.ucore.core.Effects; +import io.anuke.ucore.core.Sounds; +import io.anuke.ucore.modules.Module; +import io.anuke.ucore.util.ColorCodes; +import io.anuke.ucore.util.CommandHandler; +import io.anuke.ucore.util.CommandHandler.Command; +import io.anuke.ucore.util.CommandHandler.Response; +import io.anuke.ucore.util.CommandHandler.ResponseType; + +import java.io.IOException; +import java.util.Scanner; + +import static io.anuke.mindustry.Vars.*; +import static io.anuke.ucore.util.ColorCodes.*; + +public class ServerControl extends Module { + private final CommandHandler handler = new CommandHandler(""); + + public ServerControl(){ + Effects.setScreenShakeProvider((a, b) -> {}); + Effects.setEffectProvider((a, b, c, d, e) -> {}); + Sounds.setHeadless(true); + + //override default handling + Net.handle(ChatPacket.class, (packet) -> { + info("&y" + (packet.name == null ? "" : packet.name) + ": &lb{0}", packet.text); + }); + + registerCommands(); + Thread thread = new Thread(this::readCommands, "Server Controls"); + thread.setDaemon(true); + thread.start(); + + info("&lcServer loaded. Type &ly'help'&lc for help."); + } + + private void registerCommands(){ + handler.register("help", "", "Displays this command list.", arg -> { + info("Commands:"); + for(Command command : handler.getCommandList()){ + print(" &y" + command.text + (command.params.isEmpty() ? "" : " ") + command.params + " - &lm" + command.description); + } + }); + + handler.register("exit", "", "Exit the server application.", arg -> { + info("Shutting down server."); + Net.dispose(); + Gdx.app.exit(); + }); + + handler.register("stop", "", "Stop hosting the server.", arg -> { + Net.closeServer(); + state.set(State.menu); + }); + + handler.register("host", "", "Open the server with a specific map.", arg -> { + if(state.is(State.playing)){ + err("Already hosting. Type 'stop' to stop hosting first."); + } + + String search = arg[0]; + Map result = null; + for(Map map : world.maps().list()){ + if(map.name.equalsIgnoreCase(search)) + result = map; + } + + if(result == null){ + err("No map with name &y'{0}'&lg found.", search); + return; + } + + info("Loading map..."); + logic.reset(); + world.loadMap(result); + state.set(State.playing); + info("Map loaded."); + + try { + Net.host(port); + info("Server opened."); + }catch (IOException e){ + UCore.error(e); + } + }); + } + + private void readCommands(){ + Scanner scan = new Scanner(System.in); + System.out.print(LIGHT_BLUE + "> " + RESET); + while(true){ + String line = scan.nextLine(); + + Gdx.app.postRunnable(() -> { + Response response = handler.handleMessage(line); + + if (response.type == ResponseType.unknownCommand) { + err("Invalid command. Type 'help' for help."); + } else if (response.type == ResponseType.invalidArguments) { + err("Invalid command arguments. Usage: " + response.command.text + " " + response.command.params); + } + + System.out.print(LIGHT_BLUE + "> " + RESET); + }); + } + } + + private void print(String text, Object... args){ + System.out.println(format(text, args) + RESET); + } + + private void info(String text, Object... args){ + print(LIGHT_GREEN + BOLD + format(text, args)); + } + + private void err(String text, Object... args){ + print(LIGHT_RED + BOLD + format(text, args)); + } + + private String format(String text, Object... args){ + for(int i = 0; i < args.length; i ++){ + text = text.replace("{" + i + "}", args[i].toString()); + } + + for(String color : ColorCodes.getColorCodes()){ + text = text.replace("&" + color, ColorCodes.getColorText(color)); + } + return text; + } +} diff --git a/core/src/io/anuke/mindustry/core/World.java b/core/src/io/anuke/mindustry/core/World.java index 1a4b22e753..5ae8c51caf 100644 --- a/core/src/io/anuke/mindustry/core/World.java +++ b/core/src/io/anuke/mindustry/core/World.java @@ -44,7 +44,7 @@ public class World extends Module{ @Override public void update(){ - if(!(Net.client())) + if(!Net.client()) pathfind.update(); } @@ -74,7 +74,7 @@ public class World extends Module{ } public float getSpawnY(){ - return core.worldy() - tilesize/2; + return core.worldy() - tilesize*2; } public boolean solid(int x, int y){ diff --git a/core/src/io/anuke/mindustry/io/Maps.java b/core/src/io/anuke/mindustry/io/Maps.java index 07c451412f..452414cf70 100644 --- a/core/src/io/anuke/mindustry/io/Maps.java +++ b/core/src/io/anuke/mindustry/io/Maps.java @@ -147,7 +147,7 @@ public class Maps implements Disposable{ if(arr != null){ //can be an empty map file for(Map map : arr){ map.pixmap = new Pixmap(file.sibling(map.name + ".png")); - map.texture = new Texture(map.pixmap); + if(!headless) map.texture = new Texture(map.pixmap); maps.put(map.id, map); mapNames.put(map.name, map); lastID = Math.max(lastID, map.id); @@ -164,7 +164,7 @@ public class Maps implements Disposable{ @Override public void dispose(){ for(Map map : maps.values()){ - map.texture.dispose(); + if(map.texture != null) map.texture.dispose(); map.pixmap.dispose(); } maps.clear(); diff --git a/core/src/io/anuke/mindustry/net/Net.java b/core/src/io/anuke/mindustry/net/Net.java index 5f7b2e6192..9075823100 100644 --- a/core/src/io/anuke/mindustry/net/Net.java +++ b/core/src/io/anuke/mindustry/net/Net.java @@ -15,6 +15,9 @@ import io.anuke.ucore.function.Consumer; import java.io.IOException; +import static io.anuke.mindustry.Vars.headless; +import static io.anuke.mindustry.Vars.ui; + public class Net{ public static final int version = 13; @@ -29,6 +32,15 @@ public class Net{ private static IntMap streams = new IntMap<>(); + /**Display a network error.*/ + public static void showError(String text){ + if(!headless){ + ui.showError(text); + }else{ + UCore.log(text); + } + } + /**Sets the client loaded status, or whether it will recieve normal packets from the server.*/ public static void setClientLoaded(boolean loaded){ clientLoaded = loaded; diff --git a/core/src/io/anuke/mindustry/net/NetworkIO.java b/core/src/io/anuke/mindustry/net/NetworkIO.java index eb4824328c..7e78198bc2 100644 --- a/core/src/io/anuke/mindustry/net/NetworkIO.java +++ b/core/src/io/anuke/mindustry/net/NetworkIO.java @@ -266,7 +266,7 @@ public class NetworkIO { world.loadMap(world.maps().getMap(mapid), seed); renderer.clearTiles(); - player.set(world.getCore().worldx(), world.getCore().worldy()); + player.set(world.getSpawnX(), world.getSpawnY()); for(int x = 0; x < world.width(); x ++){ for(int y = 0; y < world.height(); y ++){ diff --git a/kryonet/src/io/anuke/kryonet/KryoClient.java b/kryonet/src/io/anuke/kryonet/KryoClient.java index 8e36cc792f..89111b34a2 100644 --- a/kryonet/src/io/anuke/kryonet/KryoClient.java +++ b/kryonet/src/io/anuke/kryonet/KryoClient.java @@ -79,7 +79,7 @@ public class KryoClient implements ClientProvider{ Net.handleClientReceived(object); }catch (Exception e){ if(e instanceof KryoNetException && e.getMessage() != null && e.getMessage().toLowerCase().contains("incorrect")) { - ui.showError("$text.server.mismatch"); + Net.showError("$text.server.mismatch"); netClient.disconnectQuietly(); }else{ throw new RuntimeException(e); @@ -207,7 +207,7 @@ public class KryoClient implements ClientProvider{ private void handleException(Exception e){ e.printStackTrace(); if(e instanceof KryoNetException){ - Gdx.app.postRunnable(() -> ui.showError("$text.server.mismatch")); + Gdx.app.postRunnable(() -> Net.showError("$text.server.mismatch")); }else{ //TODO better exception handling. disconnect(); diff --git a/kryonet/src/io/anuke/kryonet/KryoRegistrator.java b/kryonet/src/io/anuke/kryonet/KryoRegistrator.java index 757bd07fde..324d394bc5 100644 --- a/kryonet/src/io/anuke/kryonet/KryoRegistrator.java +++ b/kryonet/src/io/anuke/kryonet/KryoRegistrator.java @@ -18,9 +18,11 @@ public class KryoRegistrator { } public static ByteBuffer writeServerData(){ - ByteBuffer buffer = ByteBuffer.allocate(1 + Vars.player.name.length() + 4); - buffer.put((byte)Vars.player.name.length()); - buffer.put(Vars.player.name.getBytes()); + String host = Vars.headless ? "Server" : Vars.player.name; + + ByteBuffer buffer = ByteBuffer.allocate(1 + host.getBytes().length + 4); + buffer.put((byte)host.getBytes().length); + buffer.put(host.getBytes()); buffer.putInt(Net.getConnections().size + 1); return buffer; } diff --git a/kryonet/src/io/anuke/kryonet/KryoServer.java b/kryonet/src/io/anuke/kryonet/KryoServer.java index 0dc9560ac5..68b36f845e 100644 --- a/kryonet/src/io/anuke/kryonet/KryoServer.java +++ b/kryonet/src/io/anuke/kryonet/KryoServer.java @@ -35,8 +35,6 @@ import java.nio.ByteBuffer; import java.util.Arrays; import java.util.concurrent.CopyOnWriteArrayList; -import static io.anuke.mindustry.Vars.ui; - public class KryoServer implements ServerProvider { final boolean debug = false; final Server server; @@ -434,10 +432,10 @@ public class KryoServer implements ServerProvider { ex.printStackTrace(); if(ex instanceof BindException){ Net.closeServer(); - ui.showError("$text.server.addressinuse"); + Net.showError("$text.server.addressinuse"); }else if(ex.getMessage().equals("Permission denied")){ Net.closeServer(); - ui.showError("Permission denied."); + Net.showError("Permission denied."); } } diff --git a/server/src/io/anuke/mindustry/server/ServerLauncher.java b/server/src/io/anuke/mindustry/server/ServerLauncher.java index c549eab312..f21b0425cb 100644 --- a/server/src/io/anuke/mindustry/server/ServerLauncher.java +++ b/server/src/io/anuke/mindustry/server/ServerLauncher.java @@ -3,7 +3,7 @@ package io.anuke.mindustry.server; import com.badlogic.gdx.backends.headless.HeadlessApplication; import io.anuke.kryonet.KryoClient; import io.anuke.kryonet.KryoServer; -import io.anuke.mindustry.Mindustry; +import io.anuke.mindustry.MindustryServer; import io.anuke.mindustry.net.Net; public class ServerLauncher{ @@ -13,6 +13,6 @@ public class ServerLauncher{ Net.setClientProvider(new KryoClient()); Net.setServerProvider(new KryoServer()); - new HeadlessApplication(new Mindustry()); + new HeadlessApplication(new MindustryServer()); } } \ No newline at end of file