diff --git a/annotations/src/io/anuke/annotations/Annotations.java b/annotations/src/io/anuke/annotations/Annotations.java index e6e7705dde..191ba41370 100644 --- a/annotations/src/io/anuke/annotations/Annotations.java +++ b/annotations/src/io/anuke/annotations/Annotations.java @@ -15,9 +15,8 @@ public class Annotations { @Target(ElementType.METHOD) @Retention(RetentionPolicy.CLASS) public @interface Remote { - /**Whether this method can be invoked from remote clients.*/ - boolean client() default false; - /**Whether this method can be invoked from the remote server.*/ + /**If true, this method can only be invoked on clients from the server. + * If false, this method can only be invoked on servers from a client.*/ boolean server() default true; /**Whether a client-specific method is generated that accepts a connecton ID and sends to only one player. Default is false. * Only affects client methods.*/ diff --git a/annotations/src/io/anuke/annotations/MethodEntry.java b/annotations/src/io/anuke/annotations/MethodEntry.java index d0a60d031e..026ab0e27c 100644 --- a/annotations/src/io/anuke/annotations/MethodEntry.java +++ b/annotations/src/io/anuke/annotations/MethodEntry.java @@ -9,7 +9,7 @@ public class MethodEntry { /**Fully qualified target method to call.*/ public final String targetMethod; /**Whether this method can be called on a client/server.*/ - public final boolean client, server; + public final boolean server; /**Whether an additional 'one' and 'all' method variant is generated. At least one of these must be true. * Only applicable to client (server-invoked) methods.*/ public final boolean allVariant, oneVariant; @@ -22,11 +22,10 @@ public class MethodEntry { /**The element method associated with this entry.*/ public final ExecutableElement element; - public MethodEntry(String className, String targetMethod, boolean client, boolean server, + public MethodEntry(String className, String targetMethod, boolean server, boolean allVariant, boolean oneVariant, boolean local, boolean unreliable, int id, ExecutableElement element) { this.className = className; this.targetMethod = targetMethod; - this.client = client; this.server = server; this.allVariant = allVariant; this.oneVariant = oneVariant; diff --git a/annotations/src/io/anuke/annotations/RemoteMethodAnnotationProcessor.java b/annotations/src/io/anuke/annotations/RemoteMethodAnnotationProcessor.java index f37c852dba..9bfff8f54d 100644 --- a/annotations/src/io/anuke/annotations/RemoteMethodAnnotationProcessor.java +++ b/annotations/src/io/anuke/annotations/RemoteMethodAnnotationProcessor.java @@ -96,13 +96,8 @@ public class RemoteMethodAnnotationProcessor extends AbstractProcessor { return false; } - if (annotation.server() && annotation.client()) { - Utils.messager.printMessage(Kind.ERROR, "A method cannot be client and server simulatenously!", element); - return false; - } - //create and add entry - MethodEntry method = new MethodEntry(entry.name, Utils.getMethodName(element), annotation.client(), annotation.server(), + MethodEntry method = new MethodEntry(entry.name, Utils.getMethodName(element), annotation.server(), annotation.all(), annotation.one(), annotation.local(), annotation.unreliable(), lastMethodID ++, (ExecutableElement)element); entry.methods.add(method); @@ -114,7 +109,7 @@ public class RemoteMethodAnnotationProcessor extends AbstractProcessor { RemoteWriteGenerator writegen = new RemoteWriteGenerator(serializers); //generate server readers - readgen.generateFor(methods.stream().filter(method -> method.client).collect(Collectors.toList()), readServerName, packageName, true); + readgen.generateFor(methods.stream().filter(method -> !method.server).collect(Collectors.toList()), readServerName, packageName, true); //generate client readers readgen.generateFor(methods.stream().filter(method -> method.server).collect(Collectors.toList()), readClientName, packageName, false); diff --git a/annotations/src/io/anuke/annotations/RemoteReadGenerator.java b/annotations/src/io/anuke/annotations/RemoteReadGenerator.java index 7a053ede6f..52ec6de044 100644 --- a/annotations/src/io/anuke/annotations/RemoteReadGenerator.java +++ b/annotations/src/io/anuke/annotations/RemoteReadGenerator.java @@ -70,7 +70,7 @@ public class RemoteReadGenerator { for(int i = 0; i < entry.element.getParameters().size(); i ++){ VariableElement var = entry.element.getParameters().get(i); - if(!(entry.client && i == 0)) { //if client, skip first parameter since it's always of type player and doesn't need to be read + if(entry.server || i != 0) { //if client, skip first parameter since it's always of type player and doesn't need to be read //full type name of parameter //TODO check if the result is correct String typeName = var.asType().toString(); diff --git a/annotations/src/io/anuke/annotations/RemoteWriteGenerator.java b/annotations/src/io/anuke/annotations/RemoteWriteGenerator.java index 7086611528..0b6541635c 100644 --- a/annotations/src/io/anuke/annotations/RemoteWriteGenerator.java +++ b/annotations/src/io/anuke/annotations/RemoteWriteGenerator.java @@ -62,7 +62,7 @@ public class RemoteWriteGenerator { .returns(void.class); //validate client methods to make sure - if(methodEntry.client){ + if(!methodEntry.server){ if(elem.getParameters().isEmpty()){ Utils.messager.printMessage(Kind.ERROR, "Client invoke methods must have a first parameter of type Player.", elem); return; @@ -79,13 +79,8 @@ public class RemoteWriteGenerator { method.addParameter(int.class, "playerClientID"); } - //add all other parameters to method - for(VariableElement var : elem.getParameters()){ - method.addParameter(TypeName.get(var.asType()), var.getSimpleName().toString()); - } - //call local method if applicable - if(methodEntry.local){ + if(methodEntry.local && methodEntry.server){ //concatenate parameters int index = 0; StringBuilder results = new StringBuilder(); @@ -101,7 +96,7 @@ public class RemoteWriteGenerator { } //start control flow to check if it's actually client/server so no netcode is called - method.beginControlFlow("if(io.anuke.mindustry.net.Net." + (methodEntry.client ? "client" : "server")+"())"); + method.beginControlFlow("if(io.anuke.mindustry.net.Net." + (!methodEntry.server ? "client" : "server")+"())"); //add statement to create packet from pool method.addStatement("$1N packet = $2N.obtain($1N.class)", "io.anuke.mindustry.net.Packets.InvokePacket", "com.badlogic.gdx.utils.Pools"); @@ -110,7 +105,17 @@ public class RemoteWriteGenerator { //rewind buffer method.addStatement("TEMP_BUFFER.position(0)"); - for(VariableElement var : elem.getParameters()){ + for(int i = 0; i < elem.getParameters().size(); i ++){ + //first argument is skipped as it is always the player caller + if(!methodEntry.server && i == 0){ + continue; + } + + VariableElement var = elem.getParameters().get(i); + + //add parameter to method + method.addParameter(TypeName.get(var.asType()), var.getSimpleName().toString()); + //name of parameter String varName = var.getSimpleName().toString(); //name of parameter type diff --git a/core/src/io/anuke/mindustry/core/NetClient.java b/core/src/io/anuke/mindustry/core/NetClient.java index 889edaa58e..00ccc5a3c2 100644 --- a/core/src/io/anuke/mindustry/core/NetClient.java +++ b/core/src/io/anuke/mindustry/core/NetClient.java @@ -1,10 +1,9 @@ package io.anuke.mindustry.core; -import com.badlogic.gdx.utils.IntMap; -import com.badlogic.gdx.utils.IntSet; +import com.badlogic.gdx.graphics.Color; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.entities.Player; -import io.anuke.mindustry.entities.traits.SyncTrait; +import io.anuke.mindustry.gen.Call; import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.Net.SendMode; import io.anuke.mindustry.net.NetworkIO; @@ -27,13 +26,8 @@ public class NetClient extends Module { private boolean connecting = false; /**If true, no message will be shown on disconnect.*/ private boolean quiet = false; - /**List of all recieved entitity IDs, to prevent duplicates.*/ - private IntSet recieved = new IntSet(); - /**List of recently recieved entities that have not been added to the queue yet.*/ - private IntMap recent = new IntMap<>(); /**Counter for data timeout.*/ private float timeoutTime = 0f; - private int requests = 0; public NetClient(){ @@ -43,8 +37,6 @@ public class NetClient extends Module { player.isAdmin = false; Net.setClientLoaded(false); - recieved.clear(); - recent.clear(); timeoutTime = 0f; connecting = true; quiet = false; @@ -55,7 +47,20 @@ public class NetClient extends Module { Entities.clear(); - //TODO send connect packet here + ConnectPacket c = new ConnectPacket(); + c.name = player.name; + c.mobile = mobile; + c.color = Color.rgba8888(player.color); + c.uuid = Platform.instance.getUUID(); + + if(c.uuid == null){ + ui.showError("$text.invalidid"); + ui.loadfrag.hide(); + disconnectQuietly(); + return; + } + + Net.send(c, SendMode.tcp); }); Net.handleClient(Disconnect.class, packet -> { @@ -78,10 +83,7 @@ public class NetClient extends Module { finishConnecting(); }); - Net.handleClient(InvokePacket.class, packet -> { - //TODO invoke it - - }); + Net.handleClient(InvokePacket.class, packet -> {}); } @Override @@ -115,8 +117,7 @@ public class NetClient extends Module { ui.loadfrag.hide(); ui.join.hide(); Net.setClientLoaded(true); - //send connect ACK packet - //Timers.runTask(1f, () -> Net.send(new ConnectConfirmPacket(), SendMode.tcp)); + Call.connectConfirm(); Timers.runTask(40f, Platform.instance::updateRPC); } @@ -130,7 +131,6 @@ public class NetClient extends Module { } void sync(){ - requests = 0; if(timer.get(0, playerSyncTime)){ Player player = players[0]; diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index d8204e4319..2d515f674f 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -1,24 +1,24 @@ package io.anuke.mindustry.core; +import com.badlogic.gdx.utils.Base64Coder; import com.badlogic.gdx.utils.IntMap; import com.badlogic.gdx.utils.TimeUtils; +import io.anuke.annotations.Annotations.Remote; +import io.anuke.mindustry.content.Mechs; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.gen.RemoteReadServer; -import io.anuke.mindustry.net.Administration; +import io.anuke.mindustry.io.Version; +import io.anuke.mindustry.net.*; import io.anuke.mindustry.net.Administration.PlayerInfo; -import io.anuke.mindustry.net.Net; -import io.anuke.mindustry.net.NetConnection; -import io.anuke.mindustry.net.Packets.ClientSnapshotPacket; -import io.anuke.mindustry.net.Packets.Connect; -import io.anuke.mindustry.net.Packets.InvokePacket; -import io.anuke.mindustry.net.Packets.KickReason; +import io.anuke.mindustry.net.Packets.*; import io.anuke.ucore.core.Timers; import io.anuke.ucore.modules.Module; import io.anuke.ucore.util.Log; import io.anuke.ucore.util.Timer; -import java.nio.ByteBuffer; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import static io.anuke.mindustry.Vars.*; @@ -34,7 +34,6 @@ public class NetServer extends Module{ private IntMap connections = new IntMap<>(); private boolean closing = false; private Timer timer = new Timer(5); - private ByteBuffer writeBuffer = ByteBuffer.allocate(32); public NetServer(){ @@ -44,14 +43,81 @@ public class NetServer extends Module{ } }); - Net.handleServer(ClientSnapshotPacket.class, (id, packet) -> { - //...don't do anything here as it's already handled by the packet itself + Net.handleServer(Disconnect.class, (id, packet) -> {}); + + Net.handleServer(ConnectPacket.class, (id, packet) -> { + String uuid = new String(Base64Coder.encode(packet.uuid)); + + if(Net.getConnection(id) == null || + admins.isIPBanned(Net.getConnection(id).address)) return; + + TraceInfo trace = admins.getTraceByID(uuid); + PlayerInfo info = admins.getInfo(uuid); + trace.uuid = uuid; + trace.android = packet.mobile; + + if(admins.isIDBanned(uuid)){ + kick(id, KickReason.banned); + return; + } + + if(TimeUtils.millis() - info.lastKicked < kickDuration){ + kick(id, KickReason.recentKick); + return; + } + + for(Player player : playerGroup.all()){ + if(player.name.equalsIgnoreCase(packet.name)){ + kick(id, KickReason.nameInUse); + return; + } + } + + Log.info("Recieved connect packet for player '{0}' / UUID {1} / IP {2}", packet.name, uuid, trace.ip); + + String ip = Net.getConnection(id).address; + + admins.updatePlayerJoined(uuid, ip, packet.name); + + if(packet.version != Version.build && Version.build != -1 && packet.version != -1){ + kick(id, packet.version > Version.build ? KickReason.serverOutdated : KickReason.clientOutdated); + return; + } + + if(packet.version == -1){ + trace.modclient = true; + } + + Player player = new Player(); + player.isAdmin = admins.isAdmin(uuid, ip); + player.clientid = id; + player.name = packet.name; + player.uuid = uuid; + player.mech = packet.mobile ? Mechs.standardShip : Mechs.standard; + player.dead = true; + player.setNet(player.x, player.y); + player.setNet(player.x, player.y); + player.color.set(packet.color); + connections.put(id, player); + + trace.playerid = player.id; + + //TODO try DeflaterOutputStream + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + NetworkIO.writeWorld(player, stream); + WorldStream data = new WorldStream(); + data.stream = new ByteArrayInputStream(stream.toByteArray()); + Net.sendStream(id, data); + + Log.info("Packed {0} uncompressed bytes of WORLD data.", stream.size()); + + Platform.instance.updateRPC(); }); - Net.handleServer(InvokePacket.class, (id, packet) -> { - //TODO implement - RemoteReadServer.readPacket(packet.writeBuffer, packet.type, connections.get(id)); - }); + + Net.handleServer(ClientSnapshotPacket.class, (id, packet) -> {}); + + Net.handleServer(InvokePacket.class, (id, packet) -> RemoteReadServer.readPacket(packet.writeBuffer, packet.type, connections.get(id))); } public void update(){ @@ -101,11 +167,14 @@ public class NetServer extends Module{ return connections.get(connectionID).uuid; } - void sendMessageTo(int id, String message){ - //TODO implement - } - void sync(){ //TODO implement snapshot packets w/ delta compression } + + @Remote(server = false) + public static void connectConfirm(Player player){ + player.add(); + Log.info("&y{0} has connected.", player.name); + netCommon.sendMessage("[accent]" + player.name + " has connected."); + } } diff --git a/core/src/io/anuke/mindustry/io/TypeIO.java b/core/src/io/anuke/mindustry/io/TypeIO.java index 76b321508c..9ef0165d3e 100644 --- a/core/src/io/anuke/mindustry/io/TypeIO.java +++ b/core/src/io/anuke/mindustry/io/TypeIO.java @@ -11,6 +11,7 @@ import java.nio.ByteBuffer; import static io.anuke.mindustry.Vars.playerGroup; import static io.anuke.mindustry.Vars.world; +/**Class for specifying read/write methods for code generation.*/ public class TypeIO { @WriteClass(Player.class) @@ -47,4 +48,18 @@ public class TypeIO { buffer.get(bytes); return new String(bytes); } + + @WriteClass(byte[].class) + public static void writeBytes(ByteBuffer buffer, byte[] bytes){ + buffer.putShort((short)bytes.length); + buffer.put(bytes); + } + + @ReadClass(byte[].class) + public static byte[] readBytes(ByteBuffer buffer){ + short length = buffer.getShort(); + byte[] bytes = new byte[length]; + buffer.get(bytes); + return bytes; + } } diff --git a/core/src/io/anuke/mindustry/net/NetEvents.java b/core/src/io/anuke/mindustry/net/NetEvents.java index b2f207c230..2b338fc885 100644 --- a/core/src/io/anuke/mindustry/net/NetEvents.java +++ b/core/src/io/anuke/mindustry/net/NetEvents.java @@ -1,18 +1,5 @@ package io.anuke.mindustry.net; -import io.anuke.annotations.Annotations.Remote; -import io.anuke.mindustry.entities.Player; -import io.anuke.mindustry.world.Tile; - public class NetEvents { - @Remote(unreliable = true, one = true, all = false) - public static void callClientMethod(int something, Player player, String str, boolean bool){ - System.out.println("Called " + something + " ? " + bool); - } - - @Remote(local = false) - public static void someOtherMethod(Tile tile){ - System.out.println("Called with tile " + tile); - } } diff --git a/core/src/io/anuke/mindustry/net/NetworkIO.java b/core/src/io/anuke/mindustry/net/NetworkIO.java index 50b813b2aa..addff02ee9 100644 --- a/core/src/io/anuke/mindustry/net/NetworkIO.java +++ b/core/src/io/anuke/mindustry/net/NetworkIO.java @@ -1,11 +1,14 @@ package io.anuke.mindustry.net; +import com.badlogic.gdx.utils.ObjectMap; import com.badlogic.gdx.utils.TimeUtils; import io.anuke.mindustry.content.Weapons; import io.anuke.mindustry.content.blocks.Blocks; import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.game.GameMode; import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.io.Map; +import io.anuke.mindustry.io.MapMeta; import io.anuke.mindustry.io.Version; import io.anuke.mindustry.type.Upgrade; import io.anuke.mindustry.world.Tile; @@ -30,7 +33,7 @@ public class NetworkIO { //--GENERAL STATE-- stream.writeByte(state.mode.ordinal()); //gamemode - stream.writeUTF(world.getMap().name); //map ID + stream.writeUTF(world.getMap().name); //map name stream.writeInt(state.wave); //wave stream.writeFloat(state.wavetime); //wave countdown @@ -133,10 +136,15 @@ public class NetworkIO { int width = stream.readShort(); int height = stream.readShort(); + //TODO send advanced map meta such as author, etc + //TODO scan for cores + Map currentMap = new Map(map, new MapMeta(0, new ObjectMap<>(), width, height, null), true, () -> null); + world.setMap(currentMap); + Tile[][] tiles = world.createTiles(width, height); - for(int x = 0; x < world.width(); x ++){ - for(int y = 0; y < world.height(); y ++){ + for(int x = 0; x < width; x ++){ + for(int y = 0; y < height; y ++){ byte floorid = stream.readByte(); byte blockid = stream.readByte(); diff --git a/core/src/io/anuke/mindustry/net/Packets.java b/core/src/io/anuke/mindustry/net/Packets.java index 9bdf56d74f..9f832375c6 100644 --- a/core/src/io/anuke/mindustry/net/Packets.java +++ b/core/src/io/anuke/mindustry/net/Packets.java @@ -4,8 +4,10 @@ import com.badlogic.gdx.utils.TimeUtils; import io.anuke.mindustry.Vars; import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.gen.RemoteReadClient; +import io.anuke.mindustry.io.Version; import io.anuke.mindustry.net.Packet.ImportantPacket; import io.anuke.mindustry.net.Packet.UnimportantPacket; +import io.anuke.ucore.util.IOUtils; import java.nio.ByteBuffer; @@ -19,13 +21,40 @@ public class Packets { public static class Disconnect implements ImportantPacket{ public int id; - public String addressTCP; } public static class WorldStream extends Streamable{ } + public static class ConnectPacket implements Packet{ + public int version; + public int players; + public String name; + public boolean mobile; + public int color; + public byte[] uuid; + + @Override + public void write(ByteBuffer buffer) { + buffer.putInt(Version.build); + IOUtils.writeString(buffer, name); + buffer.put(mobile ? (byte)1 : 0); + buffer.putInt(color); + buffer.put(uuid); + } + + @Override + public void read(ByteBuffer buffer) { + version = buffer.getInt(); + name = IOUtils.readString(buffer); + mobile = buffer.get() == 1; + color = buffer.getInt(); + uuid = new byte[8]; + buffer.get(uuid); + } + } + public static class InvokePacket implements Packet{ public byte type; @@ -37,8 +66,6 @@ public class Packets { type = buffer.get(); if(Net.client()){ - //TODO implement - //CallClient.readPacket(buffer, type); RemoteReadClient.readPacket(buffer, type); }else{ byte[] bytes = new byte[writeLength]; diff --git a/core/src/io/anuke/mindustry/net/Registrator.java b/core/src/io/anuke/mindustry/net/Registrator.java index 9e0262a8db..4128f02ed3 100644 --- a/core/src/io/anuke/mindustry/net/Registrator.java +++ b/core/src/io/anuke/mindustry/net/Registrator.java @@ -11,6 +11,7 @@ public class Registrator { StreamBegin.class, StreamChunk.class, WorldStream.class, + ConnectPacket.class, ClientSnapshotPacket.class, SnapshotPacket.class, InvokePacket.class