Added server browser

This commit is contained in:
Anuken
2018-01-13 13:41:11 -05:00
parent 7c014de2c5
commit f4635b2882
12 changed files with 541 additions and 331 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

View File

@@ -29,7 +29,13 @@ text.host=Host
text.hosting=[accent]Opening server... text.hosting=[accent]Opening server...
text.hosts.refresh=Refresh text.hosts.refresh=Refresh
text.hosts.discovering=Discovering LAN games text.hosts.discovering=Discovering LAN games
text.hosts.none=[lightgray]No games found! text.server.refreshing=Refreshing server
text.hosts.none=[lightgray]No LAN games found!
text.host.invalid=[scarlet]Can't connect to host.
text.server.add=Add Server
text.server.delete=Are you sure you want to delete this server?
text.server.hostname=Host: {0}
text.server.edit=Edit Server
text.joingame.byip=Join by IP... text.joingame.byip=Join by IP...
text.joingame.title=Join Game text.joingame.title=Join Game
text.joingame.ip=IP: text.joingame.ip=IP:

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

@@ -199,7 +199,8 @@ public class Control extends Module{
Settings.defaultList( Settings.defaultList(
"ip", "localhost", "ip", "localhost",
"port", Vars.port+"", "port", Vars.port+"",
"name", Vars.android ? "player" : UCore.getProperty("user.name") "name", Vars.android ? "player" : UCore.getProperty("user.name"),
"servers", ""
); );
Settings.loadAll("io.anuke.moment"); Settings.loadAll("io.anuke.moment");

View File

@@ -1,11 +1,11 @@
package io.anuke.mindustry.net; package io.anuke.mindustry.net;
public class Address { public class Host {
public final String name; public final String name;
public final String address; public final String address;
public final int players; public final int players;
public Address(String name, String address, int players){ public Host(String name, String address, int players){
this.name = name; this.name = name;
this.address = address; this.address = address;
this.players = players; this.players = players;

View File

@@ -66,9 +66,9 @@ public class Net{
/**Starts discovering servers on a different thread. Does not work with GWT. /**Starts discovering servers on a different thread. Does not work with GWT.
* Callback is run on the main libGDX thread.*/ * Callback is run on the main libGDX thread.*/
public static void discoverServers(Consumer<Array<Address>> cons){ public static void discoverServers(Consumer<Array<Host>> cons){
executor.submit(() -> { executor.submit(() -> {
Array<Address> arr = clientProvider.discover(); Array<Host> arr = clientProvider.discover();
Gdx.app.postRunnable(() -> { Gdx.app.postRunnable(() -> {
cons.accept(arr); cons.accept(arr);
}); });
@@ -163,6 +163,11 @@ public class Net{
} }
} }
/**Pings a host in an new thread. If an error occured, failed() should be called with the exception. */
public static void pingHost(String address, int port, Consumer<Host> valid, Consumer<IOException> failed){
clientProvider.pingHost(address, port, valid, failed);
}
/**Update client ping.*/ /**Update client ping.*/
public static void updatePing(){ public static void updatePing(){
clientProvider.updatePing(); clientProvider.updatePing();
@@ -218,7 +223,9 @@ public class Net{
/**Disconnect from the server.*/ /**Disconnect from the server.*/
public void disconnect(); public void disconnect();
/**Discover servers. This should block for a certain amount of time, and will most likely be run in a different thread.*/ /**Discover servers. This should block for a certain amount of time, and will most likely be run in a different thread.*/
public Array<Address> discover(); public Array<Host> discover();
/**Ping a host. If an error occured, failed() should be called with the exception. */
public void pingHost(String address, int port, Consumer<Host> valid, Consumer<IOException> failed);
/**Register classes to be sent.*/ /**Register classes to be sent.*/
public void register(Class<?>... types); public void register(Class<?>... types);
/**Close all connections.*/ /**Close all connections.*/

View File

@@ -3,7 +3,7 @@ package io.anuke.mindustry.ui.dialogs;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import io.anuke.mindustry.Mindustry; import io.anuke.mindustry.Mindustry;
import io.anuke.mindustry.Vars; import io.anuke.mindustry.Vars;
import io.anuke.mindustry.net.Address; import io.anuke.mindustry.net.Host;
import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.Net;
import io.anuke.ucore.core.Settings; import io.anuke.ucore.core.Settings;
import io.anuke.ucore.core.Timers; import io.anuke.ucore.core.Timers;
@@ -17,13 +17,19 @@ import io.anuke.ucore.util.Bundles;
import io.anuke.ucore.util.Strings; import io.anuke.ucore.util.Strings;
public class JoinDialog extends FloatingDialog { public class JoinDialog extends FloatingDialog {
Array<Server> servers = new Array<>();
Dialog join; Dialog join;
Server renaming;
Table local = new Table();
Table remote = new Table();
Table hosts = new Table(); Table hosts = new Table();
float w = 400; float w = 500;
public JoinDialog(){ public JoinDialog(){
super("$text.joingame"); super("$text.joingame");
loadServers();
addCloseButton(); addCloseButton();
join = new FloatingDialog("$text.joingame.title"); join = new FloatingDialog("$text.joingame.title");
@@ -31,7 +37,7 @@ public class JoinDialog extends FloatingDialog {
Mindustry.platforms.addDialog(join.content().addField(Settings.getString("ip"),text ->{ Mindustry.platforms.addDialog(join.content().addField(Settings.getString("ip"),text ->{
Settings.putString("ip", text); Settings.putString("ip", text);
Settings.save(); Settings.save();
}).size(180f, 54f).get(), 100); }).size(240f, 54f).get(), 100);
join.content().row(); join.content().row();
join.content().add("$text.server.port").left(); join.content().add("$text.server.port").left();
@@ -39,33 +45,123 @@ public class JoinDialog extends FloatingDialog {
.addField(Settings.getString("port"), new DigitsOnlyFilter(), text ->{ .addField(Settings.getString("port"), new DigitsOnlyFilter(), text ->{
Settings.putString("port", text); Settings.putString("port", text);
Settings.save(); Settings.save();
}) }).size(240f, 54f).get());
.size(180f, 54f).get());
join.buttons().defaults().size(140f, 60f).pad(4f); join.buttons().defaults().size(140f, 60f).pad(4f);
join.buttons().addButton("$text.cancel", join::hide); join.buttons().addButton("$text.cancel", join::hide);
join.buttons().addButton("$text.ok", () -> join.buttons().addButton("$text.ok", () -> {
connect(Settings.getString("ip"), Strings.parseInt(Settings.getString("port"))) if(renaming == null) {
).disabled(b -> Settings.getString("ip").isEmpty() || Strings.parseInt(Settings.getString("port")) == Integer.MIN_VALUE || Net.active()); Server server = new Server(Settings.getString("ip"), Strings.parseInt(Settings.getString("port")));
servers.add(server);
saveServers();
setupRemote();
refreshRemote();
}else{
renaming.port = Strings.parseInt(Settings.getString("port"));
renaming.ip = Settings.getString("ip");
saveServers();
setupRemote();
refreshRemote();
}
join.hide();
}).disabled(b -> Settings.getString("ip").isEmpty() || Strings.parseInt(Settings.getString("port")) == Integer.MIN_VALUE || Net.active());
join.shown(() -> {
join.getTitleLabel().setText(renaming != null ? "$text.server.edit" : "$text.server.add");
});
setup(); setup();
shown(this::refresh); shown(this::refreshLocal);
} }
void refresh(){ void setupRemote(){
hosts.clear(); remote.clear();
hosts.background("button"); for (Server server : servers) {
hosts.label(() -> "[accent]" + Bundles.get("text.hosts.discovering") + new String(new char[(int)(Timers.time() / 10) % 4]).replace("\0", ".")).pad(10f); //why are java lambdas this bad
Net.discoverServers(this::addHosts); TextButton[] buttons = {null};
TextButton button = buttons[0] = remote.addButton("[accent]"+server.ip + " / " + server.port, "clear", () -> {
if(!buttons[0].childrenPressed()) connect(server.ip, server.port);
}).width(w).height(120f).pad(4f).get();
button.getLabel().setWrap(true);
Table inner = new Table();
button.clearChildren();
button.add(inner).growX();
inner.add(button.getLabel()).growX();
inner.addImageButton("icon-loading", "empty", 16*2, () -> {
refreshServer(server);
}).margin(3f).padTop(6f).top().right();
inner.addImageButton("icon-pencil", "empty", 16*2, () -> {
renaming = server;
join.show();
}).margin(3f).padTop(6f).top().right();
inner.addImageButton("icon-trash-16", "empty", 16*2, () -> {
Vars.ui.showConfirm("$text.confirm", "$text.server.delete", () -> {
servers.removeValue(server, true);
saveServers();
setupRemote();
refreshRemote();
});
}).margin(3f).pad(6).top().right();
button.row();
server.content = button.table(t -> {}).grow().get();
remote.row();
}
}
void refreshRemote(){
for(Server server : servers){
refreshServer(server);
}
}
void refreshServer(Server server){
server.content.clear();
server.content.label(() -> Bundles.get("text.server.refreshing") + Strings.animated(4, 11, "."));
Net.pingHost(server.ip, server.port, host -> {
server.content.clear();
server.content.add("[lightgray]" + Bundles.format("text.server.hostname", host.name)).pad(4);
server.content.row();
server.content.add("[lightgray]" + (host.players != 1 ? Bundles.format("text.players", host.players) :
Bundles.format("text.players.single", host.players))).left();
}, e -> {
server.content.clear();
server.content.add("$text.host.invalid");
});
}
void refreshLocal(){
local.clear();
local.background("button");
local.label(() -> "[accent]" + Bundles.get("text.hosts.discovering") + new String(new char[(int)(Timers.time() / 10) % 4]).replace("\0", ".")).pad(10f);
Net.discoverServers(this::addLocalHosts);
} }
void setup(){ void setup(){
hosts.background("button"); hosts.clear();
hosts.add(remote).growX();
hosts.row();
hosts.add(local).growX();
ScrollPane pane = new ScrollPane(hosts, "clear"); ScrollPane pane = new ScrollPane(hosts, "clear");
pane.setFadeScrollBars(false); pane.setFadeScrollBars(false);
pane.setScrollingDisabled(true, false); pane.setScrollingDisabled(true, false);
setupRemote();
refreshRemote();
content().clear(); content().clear();
content().table(t -> { content().table(t -> {
t.add("$text.name").padRight(10); t.add("$text.name").padRight(10);
@@ -79,19 +175,22 @@ public class JoinDialog extends FloatingDialog {
content().row(); content().row();
content().add(pane).width(w).pad(0); content().add(pane).width(w).pad(0);
content().row(); content().row();
content().addButton("$text.joingame.byip", "clear", join::show).width(w).height(80f); content().addButton("$text.server.add", "clear", () -> {
renaming = null;
join.show();
}).width(w).height(80f);
} }
void addHosts(Array<Address> array){ void addLocalHosts(Array<Host> array){
hosts.clear(); local.clear();
if(array.size == 0){ if(array.size == 0){
hosts.add("$text.hosts.none").pad(10f); local.add("$text.hosts.none").pad(10f);
hosts.add().growX(); local.add().growX();
hosts.addImageButton("icon-loading", 16*2f, this::refresh).pad(-10f).padLeft(0).padTop(-6).size(70f, 74f); local.addImageButton("icon-loading", 16*2f, this::refreshLocal).pad(-10f).padLeft(0).padTop(-6).size(70f, 74f);
}else { }else {
for (Address a : array) { for (Host a : array) {
TextButton button = hosts.addButton("[accent]"+a.name, "clear", () -> { TextButton button = local.addButton("[accent]"+a.name, "clear", () -> {
connect(a.address, Vars.port); connect(a.address, Vars.port);
}).width(w).height(80f).pad(4f).get(); }).width(w).height(80f).pad(4f).get();
button.left(); button.left();
@@ -101,8 +200,8 @@ public class JoinDialog extends FloatingDialog {
button.row(); button.row();
button.add("[lightgray]" + a.address + " / " + Vars.port).pad(4).left(); button.add("[lightgray]" + a.address + " / " + Vars.port).pad(4).left();
hosts.row(); local.row();
hosts.background((Drawable) null); local.background((Drawable) null);
} }
} }
} }
@@ -140,4 +239,41 @@ public class JoinDialog extends FloatingDialog {
} }
}); });
} }
private void loadServers(){
String h = Settings.getString("servers");
String[] list = h.split("\\|\\|\\|");
for(String fname : list){
if(fname.isEmpty()) continue;
String[] split = fname.split(":");
String host = split[0];
int port = Strings.parseInt(split[1]);
if(port != Integer.MIN_VALUE) servers.add(new Server(host, port));
}
}
private void saveServers(){
StringBuilder out = new StringBuilder();
for(Server server : servers){
out.append(server.ip);
out.append(":");
out.append(server.port);
out.append("|||");
}
Settings.putString("servers", out.toString());
Settings.save();
}
private class Server{
public String ip;
public int port;
public Host host;
public Table content;
public Server(String ip, int port){
this.ip = ip;
this.port = port;
}
}
} }

View File

@@ -5,8 +5,10 @@ import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.ObjectMap; import com.badlogic.gdx.utils.ObjectMap;
import com.badlogic.gdx.utils.ObjectSet; import com.badlogic.gdx.utils.ObjectSet;
import com.esotericsoftware.kryonet.*; import com.esotericsoftware.kryonet.*;
import com.esotericsoftware.kryonet.FrameworkMessage.DiscoverHost;
import com.esotericsoftware.kryonet.serialization.Serialization;
import io.anuke.mindustry.Vars; import io.anuke.mindustry.Vars;
import io.anuke.mindustry.net.Address; import io.anuke.mindustry.net.Host;
import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.Net.ClientProvider; import io.anuke.mindustry.net.Net.ClientProvider;
import io.anuke.mindustry.net.Net.SendMode; import io.anuke.mindustry.net.Net.SendMode;
@@ -14,21 +16,22 @@ import io.anuke.mindustry.net.Packets.Connect;
import io.anuke.mindustry.net.Packets.Disconnect; import io.anuke.mindustry.net.Packets.Disconnect;
import io.anuke.mindustry.net.Registrator; import io.anuke.mindustry.net.Registrator;
import io.anuke.ucore.UCore; import io.anuke.ucore.UCore;
import io.anuke.ucore.function.Consumer;
import java.io.IOException; import java.io.IOException;
import java.net.DatagramPacket; import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress; import java.net.InetAddress;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.List; import java.util.List;
public class KryoClient implements ClientProvider{ public class KryoClient implements ClientProvider{
Client client; Client client;
ObjectMap<InetAddress, Address> addresses = new ObjectMap<>(); ObjectMap<InetAddress, Host> addresses = new ObjectMap<>();
ClientDiscoveryHandler handler;
public KryoClient(){ public KryoClient(){
client = new Client(); handler = new ClientDiscoveryHandler() {
client.setDiscoveryHandler(new ClientDiscoveryHandler() {
@Override @Override
public DatagramPacket onRequestNewDatagramPacket() { public DatagramPacket onRequestNewDatagramPacket() {
return new DatagramPacket(new byte[32], 32); return new DatagramPacket(new byte[32], 32);
@@ -37,16 +40,19 @@ public class KryoClient implements ClientProvider{
@Override @Override
public void onDiscoveredHost(DatagramPacket datagramPacket) { public void onDiscoveredHost(DatagramPacket datagramPacket) {
ByteBuffer buffer = ByteBuffer.wrap(datagramPacket.getData()); ByteBuffer buffer = ByteBuffer.wrap(datagramPacket.getData());
Address address = KryoRegistrator.readServerData(datagramPacket.getAddress(), buffer); Host address = KryoRegistrator.readServerData(datagramPacket.getAddress(), buffer);
addresses.put(datagramPacket.getAddress(), address); addresses.put(datagramPacket.getAddress(), address);
UCore.log("Host data found: " + Arrays.toString(datagramPacket.getData())); UCore.log("Host data found: " + buffer.capacity() + " bytes.");
} }
@Override @Override
public void onFinally() { public void onFinally() {
} }
}); };
client = new Client();
client.setDiscoveryHandler(handler);
client.addListener(new Listener(){ client.addListener(new Listener(){
@Override @Override
@@ -81,7 +87,8 @@ public class KryoClient implements ClientProvider{
Net.handleClientReceived(object); Net.handleClientReceived(object);
}catch (Exception e){ }catch (Exception e){
if(e instanceof KryoNetException && e.getMessage() != null && e.getMessage().toLowerCase().contains("incorrect")) { if(e instanceof KryoNetException && e.getMessage() != null && e.getMessage().toLowerCase().contains("incorrect")) {
UCore.log("Mismatch!"); Gdx.app.postRunnable(() -> Vars.ui.showError("$text.server.mismatch"));
Vars.netClient.disconnectQuietly();
}else{ }else{
Gdx.app.postRunnable(() -> { Gdx.app.postRunnable(() -> {
throw new RuntimeException(e); throw new RuntimeException(e);
@@ -138,14 +145,56 @@ public class KryoClient implements ClientProvider{
} }
@Override @Override
public Array<Address> discover(){ public void pingHost(String address, int port, Consumer<Host> valid, Consumer<IOException> invalid){
Thread thread = new Thread(() -> {
try {
Serialization ser = (Serialization) UCore.getPrivate(client, "serialization");
DatagramSocket socket = new DatagramSocket();
ByteBuffer dataBuffer = ByteBuffer.allocate(64);
ser.write(dataBuffer, new DiscoverHost());
dataBuffer.flip();
byte[] data = new byte[dataBuffer.limit()];
dataBuffer.get(data);
socket.send(new DatagramPacket(data, data.length, InetAddress.getByName(address), port));
socket.setSoTimeout(2000);
addresses.clear();
DatagramPacket packet = handler.onRequestNewDatagramPacket();
socket.receive(packet);
handler.onDiscoveredHost(packet);
Host host = addresses.values().next();
if (host != null) {
Gdx.app.postRunnable(() -> valid.accept(host));
} else {
Gdx.app.postRunnable(() -> invalid.accept(new IOException("Outdated server.")));
}
} catch (IOException e) {
Gdx.app.postRunnable(() -> invalid.accept(e));
}
});
thread.setDaemon(true);
thread.start();
}
@Override
public Array<Host> discover(){
addresses.clear();
List<InetAddress> list = client.discoverHosts(Vars.port, 3000); List<InetAddress> list = client.discoverHosts(Vars.port, 3000);
ObjectSet<String> hostnames = new ObjectSet<>(); ObjectSet<String> hostnames = new ObjectSet<>();
Array<Address> result = new Array<>(); Array<Host> result = new Array<>();
for(InetAddress a : list){ for(InetAddress a : list){
if(!hostnames.contains(a.getHostName())) { if(!hostnames.contains(a.getHostName())) {
Address address = addresses.get(a); Host address = addresses.get(a);
if(address != null) result.add(address); if(address != null) result.add(address);
} }

View File

@@ -2,7 +2,7 @@ package io.anuke.kryonet;
import com.esotericsoftware.kryo.Kryo; import com.esotericsoftware.kryo.Kryo;
import io.anuke.mindustry.Vars; import io.anuke.mindustry.Vars;
import io.anuke.mindustry.net.Address; import io.anuke.mindustry.net.Host;
import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.Net;
import java.net.InetAddress; import java.net.InetAddress;
@@ -22,7 +22,7 @@ public class KryoRegistrator {
return buffer; return buffer;
} }
public static Address readServerData(InetAddress ia, ByteBuffer buffer){ public static Host readServerData(InetAddress ia, ByteBuffer buffer){
//old version address. //old version address.
if(buffer.capacity() == 4) return null; if(buffer.capacity() == 4) return null;
@@ -31,6 +31,6 @@ public class KryoRegistrator {
buffer.get(sname); buffer.get(sname);
int players = buffer.getInt(); int players = buffer.getInt();
return new Address(new String(sname), ia.getHostAddress(), players); return new Host(new String(sname), ia.getHostAddress(), players);
} }
} }

View File

@@ -2,7 +2,10 @@ package io.anuke.kryonet;
import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.utils.IntArray; import com.badlogic.gdx.utils.IntArray;
import com.esotericsoftware.kryonet.*; import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.FrameworkMessage;
import com.esotericsoftware.kryonet.Listener;
import com.esotericsoftware.kryonet.Server;
import com.esotericsoftware.kryonet.util.InputStreamSender; import com.esotericsoftware.kryonet.util.InputStreamSender;
import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.Net.SendMode; import io.anuke.mindustry.net.Net.SendMode;
@@ -19,9 +22,7 @@ import io.anuke.ucore.UCore;
import io.anuke.ucore.core.Timers; import io.anuke.ucore.core.Timers;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.Arrays; import java.util.Arrays;
public class KryoServer implements ServerProvider { public class KryoServer implements ServerProvider {
@@ -30,16 +31,12 @@ public class KryoServer implements ServerProvider {
public KryoServer(){ public KryoServer(){
server = new Server(4096*2, 2048); //TODO tweak server = new Server(4096*2, 2048); //TODO tweak
server.setDiscoveryHandler(new ServerDiscoveryHandler() { server.setDiscoveryHandler((datagramChannel, fromAddress) -> {
ByteBuffer buffer = KryoRegistrator.writeServerData();
@Override UCore.log("Replying to discover request with buffer of size " + buffer.capacity());
public boolean onDiscoverHost(DatagramChannel datagramChannel, InetSocketAddress fromAddress) throws IOException { buffer.position(0);
ByteBuffer buffer = KryoRegistrator.writeServerData(); datagramChannel.send(buffer, fromAddress);
UCore.log("Replying to discover request with buffer of size " + buffer.capacity()); return true;
buffer.position(0);
datagramChannel.send(buffer, fromAddress);
return true;
}
}); });
server.addListener(new Listener(){ server.addListener(new Listener(){