diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 68e1fba775..a111ec0bae 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -7,7 +7,7 @@ - + , Consumer> listeners = new ObjectMap<>(); - private static NetProvider provider; - - /**Connect to an address.*/ - public static void connect(String ip, String port) throws IOException{ - provider.connect(ip, port); - } - - /**Send an object to all connected clients.*/ - public static void send(Object object){ - provider.send(object); - } - - /**Sets the net provider, e.g. what handles sending, recieving and connecting.*/ - public static void setProvider(NetProvider provider){ - Net.provider = provider; - } - - /**Registers a listener for when an object is recieved.*/ - public static void handle(Class type, Consumer listener){ - listeners.put(type, listener); - } - - /**Call to handle a packet being recieved.*/ - public static void handleNetReceived(Object object){ - if(listeners.get(object.getClass()) != null){ - - }else{ - Gdx.app.error("Net", "Unhandled packet type: '" + object.getClass() + "'!"); - } - } - - /**Whether the net is active, e.g. whether this is a multiplayer game.*/ - public static boolean active(){ - return active; - } - - /**Whether this is a server or not.*/ - public static boolean server(){ - return server; - } - - public static interface NetProvider{ - public void connect(String ip, String port) throws IOException; - public void send(Object object); - } -} diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index 7cd08e69d1..d2048a193e 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -3,7 +3,6 @@ package io.anuke.mindustry; import com.badlogic.gdx.Application.ApplicationType; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.files.FileHandle; -import com.badlogic.gdx.utils.I18NBundle; import io.anuke.mindustry.core.*; import io.anuke.mindustry.entities.Player; @@ -72,11 +71,16 @@ public class Vars{ public static final float multiplier = android ? 3 : 2; public static final int tilesize = 8; + + //server port + public static final int port = 6567; public static Control control; public static Renderer renderer; public static UI ui; public static World world; + public static NetServer netServer; + public static NetClient netClient; public static Player player; diff --git a/core/src/io/anuke/mindustry/core/NetClient.java b/core/src/io/anuke/mindustry/core/NetClient.java index 7e2c4c4141..ebd61cbfe8 100644 --- a/core/src/io/anuke/mindustry/core/NetClient.java +++ b/core/src/io/anuke/mindustry/core/NetClient.java @@ -1,5 +1,7 @@ package io.anuke.mindustry.core; +import io.anuke.mindustry.core.GameState.State; +import io.anuke.mindustry.net.Net; import com.badlogic.gdx.Gdx; import io.anuke.mindustry.Vars; import io.anuke.mindustry.core.GameState.State; diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java new file mode 100644 index 0000000000..fcc7495b0e --- /dev/null +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -0,0 +1,25 @@ +package io.anuke.mindustry.core; + +import io.anuke.mindustry.Vars; +import io.anuke.mindustry.core.GameState.State; +import io.anuke.mindustry.net.Net; +import io.anuke.ucore.modules.Module; + +import java.io.IOException; + +public class NetServer extends Module{ + + public NetServer(){ + + } + + public void update(){ + if(!Net.server()) return; + + if(!GameState.is(State.menu) && Net.active()){ + + }else{ + Net.closeServer(); + } + } +} diff --git a/core/src/io/anuke/mindustry/core/Network.java b/core/src/io/anuke/mindustry/core/Network.java deleted file mode 100644 index 8b0de963ea..0000000000 --- a/core/src/io/anuke/mindustry/core/Network.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.anuke.mindustry.core; - -import io.anuke.ucore.modules.Module; - -public class Network extends Module{ - //TODO implementation -} diff --git a/core/src/io/anuke/mindustry/core/UI.java b/core/src/io/anuke/mindustry/core/UI.java index 79b08f37b9..73d8260315 100644 --- a/core/src/io/anuke/mindustry/core/UI.java +++ b/core/src/io/anuke/mindustry/core/UI.java @@ -22,6 +22,7 @@ import io.anuke.mindustry.Vars; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.mapeditor.MapEditor; import io.anuke.mindustry.mapeditor.MapEditorDialog; +import io.anuke.mindustry.net.Net; import io.anuke.mindustry.ui.*; import io.anuke.mindustry.ui.fragments.*; import io.anuke.mindustry.world.Tile; @@ -42,24 +43,29 @@ import io.anuke.ucore.scene.builders.label; import io.anuke.ucore.scene.builders.table; import io.anuke.ucore.scene.event.Touchable; import io.anuke.ucore.scene.ui.*; +import io.anuke.ucore.scene.ui.TextField.TextFieldFilter; +import io.anuke.ucore.scene.ui.TextField.TextFieldFilter.DigitsOnlyFilter; import io.anuke.ucore.scene.ui.Window.WindowStyle; import io.anuke.ucore.scene.ui.layout.Table; import io.anuke.ucore.scene.ui.layout.Unit; import io.anuke.ucore.util.Bundles; +import io.anuke.ucore.util.Strings; import javax.tools.Tool; +import java.io.IOException; public class UI extends SceneModule{ - Table loadingtable, desctable, configtable; + Table loadingtable, configtable; MindustrySettingsDialog prefs; MindustryKeybindDialog keys; MapEditorDialog editorDialog; - Dialog about, restart, levels, upgrades, load, settingserror, gameerror, discord; + Dialog about, restart, levels, upgrades, load, settingserror, gameerror, discord, join; MenuDialog menu; Tooltip tooltip; Tile configTile; - Array statlist = new Array<>(); MapEditor editor; + String lastip = "localhost"; + int lastport = Vars.port; boolean wasPaused = false; private Fragment menufrag = new MenuFragment(), @@ -67,9 +73,6 @@ public class UI extends SceneModule{ hudfrag = new HudFragment(), placefrag = new PlacementFragment(), weaponfrag = new WeaponFragment(); - - VisibilityProvider play = () -> !GameState.is(State.menu); - VisibilityProvider nplay = () -> GameState.is(State.menu); public UI() { Dialog.setShowAction(()-> sequence( @@ -155,7 +158,7 @@ public class UI extends SceneModule{ public void update(){ if(Vars.debug && !Vars.showUI) return; - if(nplay.visible()){ + if(GameState.is(State.menu)){ scene.getBatch().getProjectionMatrix().setToOrtho2D(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); scene.getBatch().begin(); @@ -186,13 +189,32 @@ public class UI extends SceneModule{ editor = new MapEditor(); editorDialog = new MapEditorDialog(editor); } + + join = new FloatingDialog("$text.joingame.title"); + join.content().add("$text.joingame.ip").left(); + join.content().addField("localhost", text -> lastip = text).size(180f, 54f); + join.content().row(); + join.content().add("$text.server.port").left(); + join.content().addField(Vars.port + "", new DigitsOnlyFilter(), text -> lastport = Strings.parseInt(text)).size(180f, 54f); + join.buttons().defaults().size(140f, 60f).pad(4f); + join.buttons().addButton("$text.cancel", join::hide); + join.buttons().addButton("$text.ok", () -> { + showLoading("$text.connecting"); + + Timers.runTask(2f, () -> { + try{ + Net.connect(lastip, lastport); + }catch (IOException e) { + showError(Bundles.format("text.connectfail", Strings.parseException(e, false))); + hideLoading(); + } + }); + }).disabled(b -> lastip.isEmpty() || lastport == Integer.MIN_VALUE); settingserror = new Dialog("Warning", "dialog"); settingserror.content().add("[crimson]Failed to access local storage.\nSettings will not be saved."); settingserror.content().margin(10f); - settingserror.getButtonTable().addButton("OK", ()->{ - settingserror.hide(); - }).size(80f, 55f).pad(4); + settingserror.getButtonTable().addButton("OK", settingserror::hide).size(80f, 55f).pad(4); gameerror = new Dialog("$text.error.crashtitle", "dialog"); gameerror.content().labelWrap("$text.error.crashmessage").width(600f).pad(10f); @@ -325,14 +347,6 @@ public class UI extends SceneModule{ } - void invalidateAll(){ - for(Element e : scene.getElements()){ - if(e instanceof Table){ - ((Table)e).invalidateHierarchy(); - } - } - } - public void showGameError(){ gameerror.show(); } @@ -371,12 +385,12 @@ public class UI extends SceneModule{ configtable.setVisible(false); } - public void showTextInput(String title, String text, String def, Consumer confirmed){ + public void showTextInput(String title, String text, String def, TextFieldFilter filter, Consumer confirmed){ new Dialog(title, "dialog"){{ content().margin(30); content().add(text).padRight(6f); TextField field = content().addField(def, t->{}).size(170f, 50f).get(); - field.setTextFieldFilter((f, c) -> field.getText().length() < 12); + field.setTextFieldFilter((f, c) -> field.getText().length() < 12 && filter.acceptChar(f, c)); Mindustry.platforms.addDialog(field); buttons().defaults().size(120, 54).pad(4); buttons().addButton("$text.ok", () -> { @@ -387,6 +401,10 @@ public class UI extends SceneModule{ }}.show(); } + public void showTextInput(String title, String text, String def, Consumer confirmed){ + showTextInput(title, text, def, (field, c) -> true, confirmed); + } + public void showError(String text){ new Dialog("$text.error.title", "dialog"){{ content().margin(15); @@ -432,6 +450,10 @@ public class UI extends SceneModule{ public void showLoadGame(){ load.show(); } + + public void showJoinGame(){ + join.show(); + } public void showMenu(){ menu.show(); diff --git a/core/src/io/anuke/mindustry/net/Net.java b/core/src/io/anuke/mindustry/net/Net.java new file mode 100644 index 0000000000..30151c8e45 --- /dev/null +++ b/core/src/io/anuke/mindustry/net/Net.java @@ -0,0 +1,149 @@ +package io.anuke.mindustry.net; + +import java.io.IOException; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.utils.ObjectMap; + +import io.anuke.ucore.function.Consumer; + +//TODO stub +public class Net{ + private static boolean server; + private static boolean active; + private static ObjectMap, Consumer> clientListeners = new ObjectMap<>(); + private static ObjectMap, Consumer> serverListeners = new ObjectMap<>(); + private static ClientProvider clientProvider; + private static ServerProvider serverProvider; + + /**Connect to an address.*/ + public static void connect(String ip, int port) throws IOException{ + clientProvider.connect(ip, port); + active = true; + server = false; + } + + /**Host a server at an address*/ + public static void host(int port) throws IOException{ + serverProvider.host(port); + active = true; + server = true; + } + + /**Closes the server.*/ + public static void closeServer(){ + serverProvider.close(); + server = false; + active = false; + } + + public static void disconnect(){ + clientProvider.disconnect(); + server = false; + active = false; + } + + /**Send an object to all connected clients, or to the server if this is a client.*/ + public static void send(Object object, SendMode mode){ + if(server){ + serverProvider.send(object, mode); + }else { + clientProvider.send(object, mode); + } + } + + /**Sets the net clientProvider, e.g. what handles sending, recieving and connecting to a server.*/ + public static void setClientProvider(ClientProvider provider){ + Net.clientProvider = provider; + } + + /**Sets the net serverProvider, e.g. what handles hosting a server.*/ + public static void setServerProvider(ServerProvider provider){ + Net.serverProvider = provider; + } + + /**Registers a client listener for when an object is recieved.*/ + public static void handle(Class type, Consumer listener){ + clientListeners.put(type, listener); + } + + /**Registers a server listener for when an object is recieved.*/ + public static void handleServer(Class type, Consumer listener){ + serverListeners.put(type, listener); + } + + /**Call to handle a packet being recieved for the client.*/ + public static void handleClientReceived(Object object){ + if(clientListeners.get(object.getClass()) != null){ + clientListeners.get(object.getClass()).accept(object); + }else{ + Gdx.app.error("Mindustry::Net", "Unhandled packet type: '" + object.getClass() + "'!"); + } + } + + /**Call to handle a packet being recieved for the server.*/ + public static void handleServerReceived(Object object){ + if(serverListeners.get(object.getClass()) != null){ + serverListeners.get(object.getClass()).accept(object); + }else{ + Gdx.app.error("Mindustry::Net", "Unhandled packet type: '" + object.getClass() + "'!"); + } + } + + /**Whether the net is active, e.g. whether this is a multiplayer game.*/ + public static boolean active(){ + return active; + } + + /**Whether this is a server or not.*/ + public static boolean server(){ + return server; + } + + /**Whether this is a client or not.*/ + public static boolean client(){ + return !server; + } + + /**Register classes that will be sent. Must be done for all classes.*/ + public static void registerClasses(Class... classes){ + clientProvider.register(classes); + serverProvider.register(classes); + } + + /**Client implementation.*/ + public static interface ClientProvider { + /**Connect to a server.*/ + public void connect(String ip, int port) throws IOException; + /**Send an object to the server.*/ + public void send(Object object, SendMode mode); + /**Update the ping. Should be done every second or so.*/ + public void updatePing(); + /**Get ping in milliseconds. Will only be valid after a call to updatePing.*/ + public int getPing(); + /**Disconnect from the server.*/ + public void disconnect(); + /**Register classes to be sent.*/ + public void register(Class... types); + } + + /**Server implementation.*/ + public static interface ServerProvider { + /**Host a server at specified port.*/ + public void host(int port) throws IOException; + /**Send an object to everyone connected.*/ + public void send(Object object, SendMode mode); + /**Send an object to a specific client ID.*/ + public void sendTo(int id, Object object, SendMode mode); + /**Send an object to everyone except a client ID.*/ + public void sendExcept(int id, Object object, SendMode mode); + /**Close the server connection.*/ + public void close(); + /**Register classes to be sent.*/ + public void register(Class... types); + } + + public enum SendMode{ + tcp, udp + } +} diff --git a/core/src/io/anuke/mindustry/net/Packets.java b/core/src/io/anuke/mindustry/net/Packets.java index 5b5638caf8..947b72ceff 100644 --- a/core/src/io/anuke/mindustry/net/Packets.java +++ b/core/src/io/anuke/mindustry/net/Packets.java @@ -16,5 +16,4 @@ public class Packets { public static class WorldData { } - } diff --git a/core/src/io/anuke/mindustry/ui/MenuButton.java b/core/src/io/anuke/mindustry/ui/MenuButton.java index a114c9162a..d5c3e3e837 100644 --- a/core/src/io/anuke/mindustry/ui/MenuButton.java +++ b/core/src/io/anuke/mindustry/ui/MenuButton.java @@ -1,5 +1,7 @@ package io.anuke.mindustry.ui; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.Colors; import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.Scaling; @@ -9,13 +11,9 @@ import io.anuke.ucore.scene.ui.Image; public class MenuButton extends Button{ - public MenuButton(String icon, PressGroup group, Listenable clicked){ + public MenuButton(String text, PressGroup group, Listenable clicked){ super("menu"); - Image image = new Image(icon); - image.setScaling(Scaling.fit); - image.setScale(4f); - image.setOrigin(Align.center); - add(image); + add(text, "title", 4); clicked(clicked); group.add(this); } diff --git a/core/src/io/anuke/mindustry/ui/MenuDialog.java b/core/src/io/anuke/mindustry/ui/MenuDialog.java index ee0d959cd3..dd484104a5 100644 --- a/core/src/io/anuke/mindustry/ui/MenuDialog.java +++ b/core/src/io/anuke/mindustry/ui/MenuDialog.java @@ -6,15 +6,18 @@ import com.badlogic.gdx.utils.reflect.ClassReflection; import io.anuke.mindustry.Vars; import io.anuke.mindustry.core.GameState; import io.anuke.mindustry.core.GameState.State; +import io.anuke.mindustry.net.Net; import io.anuke.ucore.UCore; import io.anuke.ucore.core.Timers; import io.anuke.ucore.scene.Element; import io.anuke.ucore.scene.builders.build; import io.anuke.ucore.scene.builders.imagebutton; -import io.anuke.ucore.scene.ui.ConfirmDialog; import io.anuke.ucore.scene.ui.ImageButton; -import io.anuke.ucore.scene.ui.layout.Cell; +import io.anuke.ucore.scene.ui.TextField.TextFieldFilter.DigitsOnlyFilter; import io.anuke.ucore.util.Bundles; +import io.anuke.ucore.util.Strings; + +import java.io.IOException; public class MenuDialog extends FloatingDialog{ private SaveDialog save = new SaveDialog(); @@ -59,17 +62,30 @@ public class MenuDialog extends FloatingDialog{ } content().row(); + + content().addButton("$text.hostserver", () -> { + Vars.ui.showTextInput("$text.hostserver", "$text.server.port", Vars.port + "", new DigitsOnlyFilter(), text -> { + int result = Strings.parseInt(text); + if(result == Integer.MIN_VALUE || result >= 65535){ + Vars.ui.showError("$text.server.invalidport"); + }else{ + try{ + Net.host(result); + }catch (IOException e){ + Vars.ui.showError(Bundles.format("text.server.error", Strings.parseException(e, false))); + } + } + }); + }).disabled(b -> Net.active() || (Net.active() && !Net.server())); + + content().row(); + content().addButton("$text.quit", () -> { - new ConfirmDialog("$text.confirm", "$text.quit.confirm", () -> { + Vars.ui.showConfirm("$text.confirm", "$text.quit.confirm", () -> { runSave(); hide(); GameState.set(State.menu); - }){ - { - for(Cell cell : getButtonTable().getCells()) - cell.pad(3).size(180, 44); - } - }.show(); + }); }); }else{ @@ -93,14 +109,11 @@ public class MenuDialog extends FloatingDialog{ new imagebutton("icon-load", isize, () -> load.show()).text("$text.load").padTop(4f); new imagebutton("icon-quit", isize, () -> { - new ConfirmDialog("$text.confirm", "$text.quit.confirm", () -> { + Vars.ui.showConfirm("$text.confirm", "$text.quit.confirm", () -> { runSave(); hide(); GameState.set(State.menu); - }){{ - for(Cell cell : getButtonTable().getCells()) - cell.pad(3).size(180, 44); - }}.show(); + }); }).text("Quit").padTop(4f); for(Element e : content().getChildren()){ diff --git a/core/src/io/anuke/mindustry/ui/fragments/MenuFragment.java b/core/src/io/anuke/mindustry/ui/fragments/MenuFragment.java index 08a24e1ff6..a7aeb3706e 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/MenuFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/MenuFragment.java @@ -25,25 +25,30 @@ public class MenuFragment implements Fragment{ float scale = 4f; defaults().size(100*scale, 21*scale).pad(-10f); - add(new MenuButton("text-play", group, ui::showLevels)); + add(new MenuButton("$text.play", group, ui::showLevels)); row(); - - add(new MenuButton("text-tutorial", group, ()-> control.playMap(world.maps().getMap("tutorial")))); - row(); - + if(!gwt){ - add(new MenuButton("text-load", group, ui::showLoadGame)); - row(); - - add(new MenuButton("text-editor", group, ui::showEditor)); + add(new MenuButton("$text.joingame", group, ui::showJoinGame)); row(); } - add(new MenuButton("text-settings", group, ui::showPrefs)); + add(new MenuButton("$text.tutorial", group, ()-> control.playMap(world.maps().getMap("tutorial")))); row(); if(!gwt){ - add(new MenuButton("text-exit", group, Gdx.app::exit)); + add(new MenuButton("$text.loadgame", group, ui::showLoadGame)); + row(); + + add(new MenuButton("$text.editor", group, ui::showEditor)); + row(); + } + + add(new MenuButton("$text.settings", group, ui::showPrefs)); + row(); + + if(!gwt){ + add(new MenuButton("$text.quit", group, Gdx.app::exit)); } get().margin(16); }}.end(); diff --git a/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java b/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java index 4ca906fa7d..41a1506e47 100644 --- a/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java +++ b/desktop/src/io/anuke/mindustry/desktop/DesktopLauncher.java @@ -6,19 +6,23 @@ import java.net.URI; import java.text.DateFormat; import java.text.NumberFormat; import java.text.SimpleDateFormat; -import java.util.Arrays; import java.util.Date; import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application; import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration; import com.badlogic.gdx.utils.Array; +import com.esotericsoftware.kryonet.*; import io.anuke.mindustry.Mindustry; +import io.anuke.mindustry.net.Net; +import io.anuke.mindustry.net.Net.ClientProvider; +import io.anuke.mindustry.net.Net.SendMode; import io.anuke.mindustry.Vars; import io.anuke.mindustry.io.PlatformFunction; -import io.anuke.ucore.UCore; +import io.anuke.mindustry.net.Net.ServerProvider; +import io.anuke.mindustry.net.Packets.Connect; +import io.anuke.mindustry.net.Packets.Disconnect; import io.anuke.ucore.scene.ui.TextField; -import io.anuke.ucore.scene.ui.layout.Unit; public class DesktopLauncher { @@ -60,6 +64,150 @@ public class DesktopLauncher { }; Mindustry.args = Array.with(arg); + + Net.setClientProvider(new ClientProvider() { + Client client; + + { + client = new Client(); + client.start(); + client.addListener(new Listener(){ + @Override + public void connected (Connection connection) { + Connect c = new Connect(); + c.id = connection.getID(); + c.addressTCP = connection.getRemoteAddressTCP().toString(); + Net.handleClientReceived(c); + } + + @Override + public void disconnected (Connection connection) { + Disconnect c = new Disconnect(); + Net.handleClientReceived(c); + } + + @Override + public void received (Connection connection, Object object) { + if(object instanceof FrameworkMessage) return; + Net.handleClientReceived(object); + } + }); + } + + @Override + public void connect(String ip, int port) throws IOException { + client.connect(5000, ip, port, port); + } + + @Override + public void disconnect() { + client.close(); + } + + @Override + public void send(Object object, SendMode mode) { + if(mode == SendMode.tcp){ + client.sendTCP(object); + }else{ + client.sendUDP(object); + } + } + + @Override + public void updatePing() { + client.updateReturnTripTime(); + } + + @Override + public int getPing() { + return client.getReturnTripTime(); + } + + @Override + public void register(Class... types) { + for(Class c : types){ + client.getKryo().register(c); + } + } + }); + + Net.setServerProvider(new ServerProvider() { + Server server; + + { + server = new Server(); + Thread thread = new Thread(server, "Kryonet Server"); + thread.setDaemon(true); + thread.start(); + server.addListener(new Listener(){ + @Override + public void connected (Connection connection) { + Connect c = new Connect(); + c.id = connection.getID(); + c.addressTCP = connection.getRemoteAddressTCP().toString(); + Net.handleClientReceived(c); + } + + @Override + public void disconnected (Connection connection) { + Disconnect c = new Disconnect(); + c.id = connection.getID(); + c.addressTCP = connection.getRemoteAddressTCP().toString(); + Net.handleClientReceived(c); + } + + @Override + public void received (Connection connection, Object object) { + if(object instanceof FrameworkMessage) return; + Net.handleServerReceived(object); + } + }); + } + + @Override + public void host(int port) throws IOException { + server.bind(port, port); + } + + @Override + public void close() { + server.close(); + } + + @Override + public void send(Object object, SendMode mode) { + if(mode == SendMode.tcp){ + server.sendToAllTCP(object); + }else{ + server.sendToAllUDP(object); + } + } + + @Override + public void sendTo(int id, Object object, SendMode mode) { + if(mode == SendMode.tcp){ + server.sendToTCP(id, object); + }else{ + server.sendToUDP(id, object); + } + } + + @Override + public void sendExcept(int id, Object object, SendMode mode) { + if(mode == SendMode.tcp){ + server.sendToAllExceptTCP(id, object); + }else{ + server.sendToAllExceptUDP(id, object); + } + } + + @Override + public void register(Class... types) { + for(Class c : types){ + server.getKryo().register(c); + } + } + }); new Lwjgl3Application(new Mindustry(), config); }