From 690571eec064cb1c570673c77016bfe7dc750d5d Mon Sep 17 00:00:00 2001 From: Anuken Date: Fri, 8 Jun 2018 22:43:23 -0400 Subject: [PATCH] Massive amount of fixes and changes with multiplayer/annotations --- .../src/io/anuke/annotations/Annotations.java | 59 +++++-- .../src/io/anuke/annotations/MethodEntry.java | 21 ++- .../RemoteMethodAnnotationProcessor.java | 144 ++++++++++-------- .../annotations/RemoteReadGenerator.java | 42 +++-- .../annotations/RemoteWriteGenerator.java | 77 +++++++--- core/src/io/anuke/mindustry/Mindustry.java | 1 - core/src/io/anuke/mindustry/Vars.java | 1 - core/src/io/anuke/mindustry/core/Control.java | 2 +- .../io/anuke/mindustry/core/NetClient.java | 33 ++-- .../io/anuke/mindustry/core/NetCommon.java | 19 --- .../io/anuke/mindustry/core/NetServer.java | 65 +++++--- .../io/anuke/mindustry/entities/Player.java | 28 +++- .../src/io/anuke/mindustry/entities/Unit.java | 7 +- .../entities/bullet/BasicBulletType.java | 4 +- .../mindustry/entities/bullet/Bullet.java | 35 ++--- .../mindustry/entities/units/BaseUnit.java | 24 ++- core/src/io/anuke/mindustry/io/TypeIO.java | 43 +++++- core/src/io/anuke/mindustry/net/In.java | 8 + .../io/anuke/mindustry/net/Interpolator.java | 2 +- core/src/io/anuke/mindustry/net/Net.java | 2 +- .../src/io/anuke/mindustry/net/NetEvents.java | 27 ++++ .../src/io/anuke/mindustry/net/NetworkIO.java | 58 ++++--- core/src/io/anuke/mindustry/net/Packets.java | 30 ++-- .../mindustry/net/ValidateException.java | 13 ++ .../src/io/anuke/mindustry/type/AmmoType.java | 2 +- core/src/io/anuke/mindustry/type/Weapon.java | 47 +++--- .../mindustry/ui/fragments/ChatFragment.java | 5 +- core/src/io/anuke/mindustry/world/Block.java | 2 +- core/src/io/anuke/mindustry/world/Tile.java | 4 +- .../world/blocks/storage/CoreBlock.java | 35 ++++- kryonet/src/io/anuke/kryonet/KryoClient.java | 2 - kryonet/src/io/anuke/kryonet/KryoServer.java | 2 - .../mindustry/server/MindustryServer.java | 1 - .../anuke/mindustry/server/ServerControl.java | 2 +- 34 files changed, 557 insertions(+), 290 deletions(-) delete mode 100644 core/src/io/anuke/mindustry/core/NetCommon.java create mode 100644 core/src/io/anuke/mindustry/net/In.java create mode 100644 core/src/io/anuke/mindustry/net/ValidateException.java diff --git a/annotations/src/io/anuke/annotations/Annotations.java b/annotations/src/io/anuke/annotations/Annotations.java index 191ba41370..0648a2468a 100644 --- a/annotations/src/io/anuke/annotations/Annotations.java +++ b/annotations/src/io/anuke/annotations/Annotations.java @@ -15,22 +15,19 @@ public class Annotations { @Target(ElementType.METHOD) @Retention(RetentionPolicy.CLASS) public @interface Remote { - /**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.*/ - boolean one() default false; - /**Whether a 'global' method is generated that sends the event to all players. Default is true. - * Only affects client methods.*/ - boolean all() default true; - /**Whether this method is invoked locally as well as remotely.*/ - boolean local() default true; + /**Specifies the locations where this method can be invoked.*/ + Loc targets() default Loc.server; + /**Specifies which methods are generated. Only affects client methods.*/ + Variant variants() default Variant.all; + /**The local locations where this method is called.*/ + Loc called() default Loc.none; + /**Whether to forward this packet to all other clients.*/ + boolean forward() default false; /**Whether the packet for this method is sent with UDP instead of TCP. * UDP is faster, but is prone to packet loss and duplication.*/ boolean unreliable() default false; /**The simple class name where this method is placed.*/ - String target() default "Call"; + String in() default "Call"; } /**Specifies that this method will be used to write classes of the type returned by {@link #value()}.
@@ -50,4 +47,42 @@ public class Annotations { public @interface ReadClass { Class value(); } + + /**A set of two booleans, one specifying server and one specifying client.*/ + public enum Loc { + /**Method can only be invoked on the client from the server.*/ + server(true, false), + /**Method can only be invoked on the server from the client.*/ + client(false, true), + /**Method can be invoked from anywhere*/ + both(true, true), + /**Neither server no client.*/ + none(false, false); + + /**If true, this method can be invoked ON clients FROM servers.*/ + public final boolean isServer; + /**If true, this method can be invoked ON servers FROM clients.*/ + public final boolean isClient; + + Loc(boolean server, boolean client){ + this.isServer = server; + this.isClient = client; + } + } + + public enum Variant { + /**Method can only be invoked targeting one player.*/ + one(true, false), + /**Method can only be invoked targeting all players.*/ + all(false, true), + /**Method targets both one player and all players.*/ + both(true, true); + + public final boolean isOne, isAll; + + Variant(boolean isOne, boolean isAll){ + this.isOne = isOne; + this.isAll = isAll; + } + } } diff --git a/annotations/src/io/anuke/annotations/MethodEntry.java b/annotations/src/io/anuke/annotations/MethodEntry.java index 026ab0e27c..fd01caa772 100644 --- a/annotations/src/io/anuke/annotations/MethodEntry.java +++ b/annotations/src/io/anuke/annotations/MethodEntry.java @@ -1,5 +1,8 @@ package io.anuke.annotations; +import io.anuke.annotations.Annotations.Loc; +import io.anuke.annotations.Annotations.Variant; + import javax.lang.model.element.ExecutableElement; /**Class that repesents a remote method to be constructed and put into a class.*/ @@ -9,26 +12,28 @@ 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 server; + public final Loc where; /**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; + public final Variant target; /**Whether this method is called locally as well as remotely.*/ - public final boolean local; + public final Loc local; /**Whether this method is unreliable and uses UDP.*/ public final boolean unreliable; + /**Whether to forward this method call to all other clients when a client invokes it. Server only.*/ + public final boolean forward; /**Unique method ID.*/ public final int id; /**The element method associated with this entry.*/ public final ExecutableElement element; - public MethodEntry(String className, String targetMethod, boolean server, - boolean allVariant, boolean oneVariant, boolean local, boolean unreliable, int id, ExecutableElement element) { + public MethodEntry(String className, String targetMethod, Loc where, Variant target, + Loc local, boolean unreliable, boolean forward, int id, ExecutableElement element) { this.className = className; + this.forward = forward; this.targetMethod = targetMethod; - this.server = server; - this.allVariant = allVariant; - this.oneVariant = oneVariant; + this.where = where; + this.target = target; this.local = local; this.id = id; this.element = element; diff --git a/annotations/src/io/anuke/annotations/RemoteMethodAnnotationProcessor.java b/annotations/src/io/anuke/annotations/RemoteMethodAnnotationProcessor.java index 9bfff8f54d..d08b6bc3ba 100644 --- a/annotations/src/io/anuke/annotations/RemoteMethodAnnotationProcessor.java +++ b/annotations/src/io/anuke/annotations/RemoteMethodAnnotationProcessor.java @@ -3,6 +3,7 @@ package io.anuke.annotations; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.TypeSpec; +import io.anuke.annotations.Annotations.Loc; import io.anuke.annotations.Annotations.Remote; import io.anuke.annotations.IOFinder.ClassSerializer; @@ -38,8 +39,19 @@ public class RemoteMethodAnnotationProcessor extends AbstractProcessor { /**Name of class that handles reading and invoking packets on the client.*/ private static final String readClientName = "RemoteReadClient"; - /**Whether the initial round is done.*/ - private boolean done; + /**Processing round number.*/ + private int round; + + //class serializers + private HashMap serializers; + //all elements with the Remote annotation + private Set elements; + //map of all classes to generate by name + private HashMap classMap; + //list of all method entries + private ArrayList methods; + //list of all method entries + private ArrayList classes; @Override public synchronized void init(ProcessingEnvironment processingEnv) { @@ -53,83 +65,91 @@ public class RemoteMethodAnnotationProcessor extends AbstractProcessor { @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { - if(done) return false; //only process 1 round - done = true; + if(round > 1) return false; //only process 2 rounds + + round ++; try { - //get serializers - HashMap serializers = new IOFinder().findSerializers(roundEnv); + //round 1: find all annotations, generate *writers* + if(round == 1) { + //get serializers + serializers = new IOFinder().findSerializers(roundEnv); - //last method ID used - int lastMethodID = 0; - //find all elements with the Remote annotation - Set elements = roundEnv.getElementsAnnotatedWith(Remote.class); - //map of all classes to generate by name - HashMap classMap = new HashMap<>(); - //list of all method entries - ArrayList methods = new ArrayList<>(); - //list of all method entries - ArrayList classes = new ArrayList<>(); + //last method ID used + int lastMethodID = 0; + //find all elements with the Remote annotation + elements = roundEnv.getElementsAnnotatedWith(Remote.class); + //map of all classes to generate by name + classMap = new HashMap<>(); + //list of all method entries + methods = new ArrayList<>(); + //list of all method entries + classes = new ArrayList<>(); - //create methods - for (Element element : elements) { - Remote annotation = element.getAnnotation(Remote.class); + //create methods + for (Element element : elements) { + Remote annotation = element.getAnnotation(Remote.class); - //check for static - if(!element.getModifiers().contains(Modifier.STATIC)) { - Utils.messager.printMessage(Kind.ERROR, "All Remote methods must be static: ", element); + //check for static + if (!element.getModifiers().contains(Modifier.STATIC) || !element.getModifiers().contains(Modifier.PUBLIC)) { + Utils.messager.printMessage(Kind.ERROR, "All @Remote methods must be public and static: ", element); + } + + //can't generate none methods + if (annotation.targets() == Loc.none) { + Utils.messager.printMessage(Kind.ERROR, "A @Remote method's where() cannot be equal to 'none':", element); + } + + //get and create class entry if needed + if (!classMap.containsKey(annotation.in())) { + ClassEntry clas = new ClassEntry(annotation.in()); + classMap.put(annotation.in(), clas); + classes.add(clas); + } + + ClassEntry entry = classMap.get(annotation.in()); + + //create and add entry + MethodEntry method = new MethodEntry(entry.name, Utils.getMethodName(element), annotation.targets(), annotation.variants(), + annotation.called(), annotation.unreliable(), annotation.forward(), lastMethodID++, (ExecutableElement) element); + + entry.methods.add(method); + methods.add(method); } - //get and create class entry if needed - if (!classMap.containsKey(annotation.target())) { - ClassEntry clas = new ClassEntry(annotation.target()); - classMap.put(annotation.target(), clas); - classes.add(clas); - } + //create read/write generators + RemoteWriteGenerator writegen = new RemoteWriteGenerator(serializers); - ClassEntry entry = classMap.get(annotation.target()); + //generate the methods to invoke (write) + writegen.generateFor(classes, packageName); - //make sure that each server annotation has at least one method to generate, otherwise throw an error - if (annotation.server() && !annotation.all() && !annotation.one()) { - Utils.messager.printMessage(Kind.ERROR, "A client method must not have all() and one() both be false!", element); - return false; - } + return true; + }else if(round == 2) { //round 2: generate all *readers* + RemoteReadGenerator readgen = new RemoteReadGenerator(serializers); - //create and add entry - MethodEntry method = new MethodEntry(entry.name, Utils.getMethodName(element), annotation.server(), - annotation.all(), annotation.one(), annotation.local(), annotation.unreliable(), lastMethodID ++, (ExecutableElement)element); + //generate server readers + readgen.generateFor(methods.stream().filter(method -> method.where.isClient).collect(Collectors.toList()), readServerName, packageName, true); + //generate client readers + readgen.generateFor(methods.stream().filter(method -> method.where.isServer).collect(Collectors.toList()), readClientName, packageName, false); - entry.methods.add(method); - methods.add(method); + //create class for storing unique method hash + TypeSpec.Builder hashBuilder = TypeSpec.classBuilder("MethodHash").addModifiers(Modifier.PUBLIC); + hashBuilder.addField(FieldSpec.builder(int.class, "HASH", Modifier.STATIC, Modifier.PUBLIC, Modifier.FINAL) + .initializer("$1L", Objects.hash(methods)).build()); + + //build and write resulting hash class + TypeSpec spec = hashBuilder.build(); + JavaFile.builder(packageName, spec).build().writeTo(Utils.filer); + + return true; } - //create read/write generators - RemoteReadGenerator readgen = new RemoteReadGenerator(serializers); - RemoteWriteGenerator writegen = new RemoteWriteGenerator(serializers); - - //generate server readers - 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); - - //generate the methods to invoke (write) - writegen.generateFor(classes, packageName); - - //create class for storing unique method hash - TypeSpec.Builder hashBuilder = TypeSpec.classBuilder("MethodHash").addModifiers(Modifier.PUBLIC); - hashBuilder.addField(FieldSpec.builder(int.class, "HASH", Modifier.STATIC, Modifier.PUBLIC, Modifier.FINAL) - .initializer("$1L", Objects.hash(methods)).build()); - - //build and write resulting hash class - TypeSpec spec = hashBuilder.build(); - JavaFile.builder(packageName, spec).build().writeTo(Utils.filer); - - return true; - }catch (Exception e){ e.printStackTrace(); throw new RuntimeException(e); } + + return false; } } diff --git a/annotations/src/io/anuke/annotations/RemoteReadGenerator.java b/annotations/src/io/anuke/annotations/RemoteReadGenerator.java index 52ec6de044..c96661a9af 100644 --- a/annotations/src/io/anuke/annotations/RemoteReadGenerator.java +++ b/annotations/src/io/anuke/annotations/RemoteReadGenerator.java @@ -70,9 +70,8 @@ public class RemoteReadGenerator { for(int i = 0; i < entry.element.getParameters().size(); i ++){ VariableElement var = entry.element.getParameters().get(i); - if(entry.server || i != 0) { //if client, skip first parameter since it's always of type player and doesn't need to be read + if(!needsPlayer || 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(); //name of parameter String varName = var.getSimpleName().toString(); @@ -98,16 +97,41 @@ public class RemoteReadGenerator { //add statement for reading it readBlock.addStatement(typeName + " " + varName + " = " + ser.readMethod + "(buffer)"); } - } - //append variable name to string builder - varResult.append(var.getSimpleName()); - if(i != entry.element.getParameters().size() - 1) varResult.append(", "); + //append variable name to string builder + varResult.append(var.getSimpleName()); + if(i != entry.element.getParameters().size() - 1) varResult.append(", "); + }else{ + varResult.append("player"); + if(i != entry.element.getParameters().size() - 1) varResult.append(", "); + } } - //now execute it - readBlock.addStatement("com.badlogic.gdx.Gdx.app.postRunnable(() -> $N." + entry.element.getSimpleName() + "(" + varResult.toString() + "))", - ((TypeElement)entry.element.getEnclosingElement()).getQualifiedName().toString()); + + + //begin lambda control flow + readBlock.beginControlFlow("com.badlogic.gdx.Gdx.app.postRunnable(() -> "); + + //call forwarded method before the method, so if it throws a ValidateException, the method won't be forwarded + if(entry.forward && entry.where.isServer){ + //try block to catch validate exception + readBlock.beginControlFlow("try"); + + //call forwarded method + readBlock.addStatement(packageName + "." + entry.className + "." + entry.element.getSimpleName() + + "__forward(player.clientid" + (varResult.length() == 0 ? "" : ", ") + varResult.toString() + ")"); + + //when a ValidateException is caught, print the error and return + readBlock.nextControlFlow("catch (io.anuke.mindustry.net.ValidateException e)"); + readBlock.addStatement("e.printStackTrace()"); + readBlock.addStatement("return"); + readBlock.endControlFlow(); + } + + //execute the relevant method + readBlock.addStatement("$N." + entry.element.getSimpleName() + "(" + varResult.toString() + ")", ((TypeElement) entry.element.getEnclosingElement()).getQualifiedName().toString()); + //end lambda + readBlock.endControlFlow(")"); } //end control flow if necessary diff --git a/annotations/src/io/anuke/annotations/RemoteWriteGenerator.java b/annotations/src/io/anuke/annotations/RemoteWriteGenerator.java index 0b6541635c..2ca0c75932 100644 --- a/annotations/src/io/anuke/annotations/RemoteWriteGenerator.java +++ b/annotations/src/io/anuke/annotations/RemoteWriteGenerator.java @@ -1,6 +1,7 @@ package io.anuke.annotations; import com.squareup.javapoet.*; +import io.anuke.annotations.Annotations.Loc; import io.anuke.annotations.IOFinder.ClassSerializer; import javax.lang.model.element.ExecutableElement; @@ -27,6 +28,7 @@ public class RemoteWriteGenerator { for(ClassEntry entry : entries){ //create builder + System.out.println("Generating class! " + entry.name); TypeSpec.Builder classBuilder = TypeSpec.classBuilder(entry.name).addModifiers(Modifier.PUBLIC); //add temporary write buffer @@ -36,13 +38,18 @@ public class RemoteWriteGenerator { //go through each method entry in this class for(MethodEntry methodEntry : entry.methods){ //write the 'send event to all players' variant: always happens for clients, but only happens if 'all' is enabled on the server method - if(!methodEntry.server || methodEntry.allVariant){ - writeMethodVariant(classBuilder, methodEntry, true); + if(methodEntry.where.isClient || methodEntry.target.isAll){ + writeMethodVariant(classBuilder, methodEntry, true, false); } - //write the 'send even to one player' variant, which is only applicable on the server - if(methodEntry.server && methodEntry.oneVariant){ - writeMethodVariant(classBuilder, methodEntry, false); + //write the 'send event to one player' variant, which is only applicable on the server + if(methodEntry.where.isServer && methodEntry.target.isOne){ + writeMethodVariant(classBuilder, methodEntry, false, false); + } + + //write the forwarded method version + if(methodEntry.where.isServer && methodEntry.forward){ + writeMethodVariant(classBuilder, methodEntry, true, true); } } @@ -53,16 +60,16 @@ public class RemoteWriteGenerator { } /**Creates a specific variant for a method entry.*/ - private void writeMethodVariant(TypeSpec.Builder classBuilder, MethodEntry methodEntry, boolean toAll){ + private void writeMethodVariant(TypeSpec.Builder classBuilder, MethodEntry methodEntry, boolean toAll, boolean forwarded){ ExecutableElement elem = methodEntry.element; //create builder - MethodSpec.Builder method = MethodSpec.methodBuilder(elem.getSimpleName().toString()) - .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + MethodSpec.Builder method = MethodSpec.methodBuilder(elem.getSimpleName().toString() + (forwarded ? "__forward" : "")) //add except suffix when forwarding + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.SYNCHRONIZED) .returns(void.class); //validate client methods to make sure - if(!methodEntry.server){ + if(methodEntry.where.isClient){ if(elem.getParameters().isEmpty()){ Utils.messager.printMessage(Kind.ERROR, "Client invoke methods must have a first parameter of type Player.", elem); return; @@ -79,8 +86,18 @@ public class RemoteWriteGenerator { method.addParameter(int.class, "playerClientID"); } - //call local method if applicable - if(methodEntry.local && methodEntry.server){ + //add sender to ignore + if(forwarded){ + method.addParameter(int.class, "exceptSenderID"); + } + + //call local method if applicable, shouldn't happen when forwarding method as that already happens by default + if(!forwarded && methodEntry.local != Loc.none){ + //add in local checks + if(methodEntry.local != Loc.both){ + method.beginControlFlow("if("+getCheckString(methodEntry.local) + " || !io.anuke.mindustry.net.Net.active())"); + } + //concatenate parameters int index = 0; StringBuilder results = new StringBuilder(); @@ -93,21 +110,27 @@ public class RemoteWriteGenerator { //add the statement to call it method.addStatement("$N." + elem.getSimpleName() + "(" + results.toString() + ")", ((TypeElement)elem.getEnclosingElement()).getQualifiedName().toString()); + + if(methodEntry.local != Loc.both){ + method.endControlFlow(); + } } //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.server ? "client" : "server")+"())"); + method.beginControlFlow("if("+getCheckString(methodEntry.where)+")"); //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"); //assign buffer method.addStatement("packet.writeBuffer = TEMP_BUFFER"); + //assign method ID + method.addStatement("packet.type = (byte)" + methodEntry.id); //rewind buffer method.addStatement("TEMP_BUFFER.position(0)"); 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){ + if((!methodEntry.where.isServer/* || methodEntry.mode == Loc.both*/) && i == 0){ continue; } @@ -147,21 +170,31 @@ public class RemoteWriteGenerator { //assign packet length method.addStatement("packet.writeLength = TEMP_BUFFER.position()"); - //send the actual packet - if(toAll){ - //send to all players / to server - method.addStatement("io.anuke.mindustry.net.Net.send(packet, "+ - (methodEntry.unreliable ? "io.anuke.mindustry.net.Net.SendMode.udp" : "io.anuke.mindustry.net.Net.SendMode.tcp")+")"); - }else{ - //send to specific client from server - method.addStatement("io.anuke.mindustry.net.Net.sendTo(playerClientID, packet, "+ - (methodEntry.unreliable ? "io.anuke.mindustry.net.Net.SendMode.udp" : "io.anuke.mindustry.net.Net.SendMode.tcp")+")"); + String sendString; + + if(forwarded){ //forward packet + sendString = "sendExcept(exceptSenderID, "; + }else if(toAll){ //send to all players / to server + sendString = "send("; + }else{ //send to specific client from server + sendString = "sendTo(playerClientID, "; } + //send the actual packet + method.addStatement("io.anuke.mindustry.net.Net." + sendString + "packet, "+ + (methodEntry.unreliable ? "io.anuke.mindustry.net.Net.SendMode.udp" : "io.anuke.mindustry.net.Net.SendMode.tcp")+")"); + + //end check for server/client method.endControlFlow(); //add method to class, finally classBuilder.addMethod(method.build()); } + + private String getCheckString(Loc loc){ + return loc.isClient && loc.isServer ? "io.anuke.mindustry.net.Net.server() || io.anuke.mindustry.net.Net.client()" : + loc.isClient ? "io.anuke.mindustry.net.Net.client()" : + loc.isServer ? "io.anuke.mindustry.net.Net.server()" : "false"; + } } diff --git a/core/src/io/anuke/mindustry/Mindustry.java b/core/src/io/anuke/mindustry/Mindustry.java index 8a818d2fdd..077a7ae4d3 100644 --- a/core/src/io/anuke/mindustry/Mindustry.java +++ b/core/src/io/anuke/mindustry/Mindustry.java @@ -31,7 +31,6 @@ public class Mindustry extends ModuleCore { module(ui = new UI()); module(netServer = new NetServer()); module(netClient = new NetClient()); - module(netCommon = new NetCommon()); } @Override diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index aa7def6b1f..4f988e083d 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -123,7 +123,6 @@ public class Vars{ public static Renderer renderer; public static UI ui; public static World world; - public static NetCommon netCommon; public static NetServer netServer; public static NetClient netClient; diff --git a/core/src/io/anuke/mindustry/core/Control.java b/core/src/io/anuke/mindustry/core/Control.java index b18bedabc6..371301be14 100644 --- a/core/src/io/anuke/mindustry/core/Control.java +++ b/core/src/io/anuke/mindustry/core/Control.java @@ -138,7 +138,7 @@ public class Control extends Module{ Events.on(PlayEvent.class, () -> { for(Player player : players){ - player.dead = true; + player.add(); } state.set(State.playing); diff --git a/core/src/io/anuke/mindustry/core/NetClient.java b/core/src/io/anuke/mindustry/core/NetClient.java index 4a1da71951..dc339cfdb1 100644 --- a/core/src/io/anuke/mindustry/core/NetClient.java +++ b/core/src/io/anuke/mindustry/core/NetClient.java @@ -4,10 +4,12 @@ import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.utils.reflect.ClassReflection; import com.badlogic.gdx.utils.reflect.ReflectionException; import io.anuke.annotations.Annotations.Remote; +import io.anuke.annotations.Annotations.Variant; 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.gen.RemoteReadClient; import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.Net.SendMode; import io.anuke.mindustry.net.NetworkIO; @@ -102,7 +104,10 @@ public class NetClient extends Module { finishConnecting(); }); - Net.handleClient(InvokePacket.class, packet -> {}); + Net.handleClient(InvokePacket.class, packet -> { + packet.writeBuffer.position(0); + RemoteReadClient.readPacket(packet.writeBuffer, packet.type); + }); } @Override @@ -165,9 +170,16 @@ public class NetClient extends Module { } } - @Remote(one = true, all = false, unreliable = true) - public static void onSnapshot(byte[] snapshot, int snapshotID){ + @Remote(variants = Variant.one) + public static void onKick(KickReason reason){ + netClient.disconnectQuietly(); + state.set(State.menu); + if(!reason.quiet) ui.showError("$text.server.kicked." + reason.name()); + ui.loadfrag.hide(); + } + @Remote(variants = Variant.one, unreliable = true) + public static void onSnapshot(byte[] snapshot, int snapshotID){ //skip snapshot IDs that have already been recieved if(snapshotID == netClient.lastSnapshotID){ return; @@ -177,7 +189,7 @@ public class NetClient extends Module { byte[] result; int length; - if (snapshotID == -1) { //-1 = fresh snapshot + if (snapshotID == 0) { //fresh snapshot result = snapshot; length = snapshot.length; netClient.lastSnapshot = snapshot; @@ -189,6 +201,8 @@ public class NetClient extends Module { netClient.lastSnapshot = Arrays.copyOf(result, length); } + netClient.lastSnapshotID = snapshotID; + //set stream bytes to begin write netClient.byteStream.setBytes(result, 0, length); @@ -214,6 +228,7 @@ public class NetClient extends Module { //entity must not be added yet, so create it if(entity == null){ entity = (SyncTrait) ClassReflection.newInstance(group.getType()); //TODO solution without reflection? + entity.resetID(id); entity.add(); } @@ -222,15 +237,11 @@ public class NetClient extends Module { } } - //confirm that snapshot 0 has been recieved if this is the initial snapshot - if(snapshotID == -1){ - netClient.lastSnapshotID = 0; - }else{ //confirm that the snapshot has been recieved - netClient.lastSnapshotID = snapshotID; - } + //confirm that snapshot has been recieved + netClient.lastSnapshotID = snapshotID; }catch (IOException | ReflectionException e){ - throw new RuntimeException(e); + e.printStackTrace(); } } } \ No newline at end of file diff --git a/core/src/io/anuke/mindustry/core/NetCommon.java b/core/src/io/anuke/mindustry/core/NetCommon.java deleted file mode 100644 index 6c3a8b476c..0000000000 --- a/core/src/io/anuke/mindustry/core/NetCommon.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.anuke.mindustry.core; - -import io.anuke.mindustry.entities.Player; -import io.anuke.ucore.modules.Module; - -import static io.anuke.mindustry.Vars.playerGroup; - -public class NetCommon extends Module { - - public void sendMessage(String message){ - //TODO implement - } - - public String colorizeName(int id, String name){ - Player player = playerGroup.getByID(id); - if(name == null || player == null) return null; - return "[#" + player.color.toString().toUpperCase() + "]" + name; - } -} diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index 61485c4062..065bdbe7d4 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -3,6 +3,7 @@ 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.Loc; import io.anuke.annotations.Annotations.Remote; import io.anuke.mindustry.content.Mechs; import io.anuke.mindustry.core.GameState.State; @@ -32,10 +33,8 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.*; public class NetServer extends Module{ - private final static float serverSyncTime = 4, itemSyncTime = 10, kickDuration = 30 * 1000; - - private final static int timerEntitySync = 0; - private final static int timerStateSync = 1; + private final static float serverSyncTime = 4, kickDuration = 30 * 1000; + private final static boolean preventDuplicatNames = false; public final Administration admins = new Administration(); @@ -58,7 +57,13 @@ public class NetServer extends Module{ } }); - Net.handleServer(Disconnect.class, (id, packet) -> {}); + Net.handleServer(Disconnect.class, (id, packet) -> { + Player player = connections.get(id); + if(player != null){ + Call.sendMessage("[accent]" + player.name + " has disconnected."); + player.remove(); + } + }); Net.handleServer(ConnectPacket.class, (id, packet) -> { String uuid = new String(Base64Coder.encode(packet.uuid)); @@ -81,10 +86,12 @@ public class NetServer extends Module{ return; } - for(Player player : playerGroup.all()){ - if(player.name.equalsIgnoreCase(packet.name)){ - kick(id, KickReason.nameInUse); - return; + if(preventDuplicatNames) { + for (Player player : playerGroup.all()) { + if (player.name.equalsIgnoreCase(packet.name)) { + kick(id, KickReason.nameInUse); + return; + } } } @@ -172,6 +179,7 @@ public class NetServer extends Module{ } //TODO kick player, send kick packet + Call.onKick(connection, reason); Timers.runTask(2f, con::close); @@ -184,15 +192,21 @@ public class NetServer extends Module{ void sync(){ try { - //TODO implement snapshot packets w/ delta compression //iterate through each player for (Player player : connections.values()) { NetConnection connection = Net.getConnection(player.clientid); + if(connection == null){ + Log.err("Player {0} failed to connect.", player.name); + connections.remove(player.clientid); + player.remove(); + return; + } + if(!player.timer.get(Player.timeSync, serverSyncTime)) continue; - //if the player hasn't acknolwedged that it has recieved the packet, send the same thing again + //if the player hasn't acknowledged that it has recieved the packet, send the same thing again if(connection.lastSentSnapshotID > connection.lastSnapshotID){ Call.onSnapshot(connection.id, connection.lastSentSnapshot, connection.lastSentSnapshotID); return; @@ -215,7 +229,7 @@ public class NetServer extends Module{ //check for syncable groups for (EntityGroup group : Entities.getAllGroups()) { - //TODO range-check sync positions? + //TODO range-check sync positions to optimize? if (group.isEmpty() || !(group.all().get(0) instanceof SyncTrait)) continue; //make sure mapping is enabled for this group @@ -223,13 +237,20 @@ public class NetServer extends Module{ throw new RuntimeException("Entity group '" + group.getType() + "' contains SyncTrait entities, yet mapping is not enabled. In order for syncing to work, you must enable mapping for this group."); } + int size = group.size(); + + if(group.getType() == Player.class){ + size --; + } + //write group ID + group size dataStream.writeByte(group.getID()); - dataStream.writeShort(group.size()); + dataStream.writeShort(size); //write timestamp dataStream.writeLong(TimeUtils.millis()); for(Entity entity : group.all()){ + if(entity == player) continue; //write all entities now dataStream.writeInt(entity.getID()); ((SyncTrait)entity).write(dataStream); @@ -237,17 +258,17 @@ public class NetServer extends Module{ } byte[] bytes = syncStream.toByteArray(); - if(connection.lastSnapshot == null){ + connection.lastSentSnapshot = bytes; + if(connection.lastSnapshotID == -1){ //no snapshot to diff, send it all - Call.onSnapshot(connection.id, bytes, -1); + Call.onSnapshot(connection.id, bytes, 0); + connection.lastSnapshotID = 0; }else{ - //increment snapshot ID - connection.lastSnapshotID ++; //send diff, otherwise byte[] diff = ByteDeltaEncoder.toDiff(new ByteMatcherHash(connection.lastSnapshot, bytes), encoder); - Call.onSnapshot(connection.id, diff, connection.lastSnapshotID); - - connection.lastSentSnapshot = bytes; + Call.onSnapshot(connection.id, diff, connection.lastSnapshotID + 1); + //increment snapshot ID + connection.lastSentSnapshotID ++; } } @@ -256,10 +277,10 @@ public class NetServer extends Module{ } } - @Remote(server = false) + @Remote(targets = Loc.client) public static void connectConfirm(Player player){ player.add(); - netCommon.sendMessage("[accent]" + player.name + " has connected."); + Call.sendMessage("[accent]" + player.name + " has connected."); Log.info("&y{0} has connected.", player.name); } } diff --git a/core/src/io/anuke/mindustry/entities/Player.java b/core/src/io/anuke/mindustry/entities/Player.java index 9286b911ce..88e42f5888 100644 --- a/core/src/io/anuke/mindustry/entities/Player.java +++ b/core/src/io/anuke/mindustry/entities/Player.java @@ -30,7 +30,9 @@ import io.anuke.ucore.graphics.Fill; import io.anuke.ucore.graphics.Lines; import io.anuke.ucore.util.*; -import java.io.*; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; import static io.anuke.mindustry.Vars.*; @@ -192,6 +194,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait { @Override public void removed() { + Log.info("\n\nPLAYER REMOVED\n\n"); dropCarry(); } @@ -368,7 +371,7 @@ public class Player extends Unit implements BuilderTrait, CarryTrait { hitTime = Math.max(0f, hitTime - Timers.delta()); if(!isLocal){ - interpolate(); + //interpolate(); return; } @@ -552,9 +555,9 @@ public class Player extends Unit implements BuilderTrait, CarryTrait { dead = true; respawning = false; trail.clear(); + health = maxHealth(); add(); - heal(); } public boolean isShooting(){ @@ -601,7 +604,6 @@ public class Player extends Unit implements BuilderTrait, CarryTrait { private void readSaveSuper(DataInput stream) throws IOException { super.readSave(stream); - byte uamount = stream.readByte(); for (int i = 0; i < uamount; i++) { upgrades.add(Upgrade.getByID(stream.readByte())); @@ -611,13 +613,23 @@ public class Player extends Unit implements BuilderTrait, CarryTrait { } @Override - public void write(DataOutput buffer) { - //todo + public void write(DataOutput buffer) throws IOException { + super.writeSave(buffer); + buffer.writeUTF(name); + buffer.writeInt(Color.rgba8888(color)); + buffer.writeBoolean(dead); + buffer.writeByte(weapon.id); + buffer.writeByte(mech.id); } @Override - public void read(DataInput buffer, long time) { - //todo + public void read(DataInput buffer, long time) throws IOException { + super.readSave(buffer); + name = buffer.readUTF(); + color.set(buffer.readInt()); + dead = buffer.readBoolean(); + weapon = Upgrade.getByID(buffer.readByte()); + mech = Upgrade.getByID(buffer.readByte()); } //endregion diff --git a/core/src/io/anuke/mindustry/entities/Unit.java b/core/src/io/anuke/mindustry/entities/Unit.java index ae88454880..eb60748e21 100644 --- a/core/src/io/anuke/mindustry/entities/Unit.java +++ b/core/src/io/anuke/mindustry/entities/Unit.java @@ -63,7 +63,9 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ x = interpolator.pos.x; y = interpolator.pos.y; - rotation = interpolator.values[0]; + if(interpolator.values.length > 0){ + rotation = interpolator.values[0]; + } } @Override @@ -100,6 +102,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ stream.writeByte(team.ordinal()); stream.writeFloat(x); stream.writeFloat(y); + stream.writeFloat(rotation); stream.writeShort((short)health); stream.writeByte(status.current().id); stream.writeFloat(status.getTime()); @@ -111,6 +114,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ byte team = stream.readByte(); float x = stream.readFloat(); float y = stream.readFloat(); + float rotation = stream.readFloat(); int health = stream.readShort(); byte effect = stream.readByte(); float etime = stream.readFloat(); @@ -120,6 +124,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ this.health = health; this.x = x; this.y = y; + this.rotation = rotation; this.status.set(StatusEffect.getByID(effect), etime); } diff --git a/core/src/io/anuke/mindustry/entities/bullet/BasicBulletType.java b/core/src/io/anuke/mindustry/entities/bullet/BasicBulletType.java index 3a3a02b980..db13a01b4c 100644 --- a/core/src/io/anuke/mindustry/entities/bullet/BasicBulletType.java +++ b/core/src/io/anuke/mindustry/entities/bullet/BasicBulletType.java @@ -51,9 +51,7 @@ public class BasicBulletType extends BulletType { for (int i = 0; i < fragBullets; i++) { float len = Mathf.random(1f, 7f); float a = Mathf.random(360f); - Bullet bullet = Bullet.create(fragBullet, b, - x + Angles.trnsx(a, len), y + Angles.trnsy(a, len), a); - bullet.getVelocity().scl(Mathf.random(fragVelocityMin, fragVelocityMax)); + Bullet.create(fragBullet, b, x + Angles.trnsx(a, len), y + Angles.trnsy(a, len), a, Mathf.random(fragVelocityMin, fragVelocityMax)); } } } diff --git a/core/src/io/anuke/mindustry/entities/bullet/Bullet.java b/core/src/io/anuke/mindustry/entities/bullet/Bullet.java index 30e8bc6abc..7752a3f0a2 100644 --- a/core/src/io/anuke/mindustry/entities/bullet/Bullet.java +++ b/core/src/io/anuke/mindustry/entities/bullet/Bullet.java @@ -3,10 +3,8 @@ package io.anuke.mindustry.entities.bullet; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Pools; import io.anuke.mindustry.entities.Unit; -import io.anuke.mindustry.entities.traits.SyncTrait; import io.anuke.mindustry.entities.traits.TeamTrait; import io.anuke.mindustry.game.Team; -import io.anuke.mindustry.net.Interpolator; import io.anuke.mindustry.world.Tile; import io.anuke.ucore.entities.EntityGroup; import io.anuke.ucore.entities.impl.BulletEntity; @@ -15,31 +13,31 @@ import io.anuke.ucore.entities.trait.SolidTrait; import io.anuke.ucore.entities.trait.VelocityTrait; import io.anuke.ucore.util.Timer; -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - import static io.anuke.mindustry.Vars.bulletGroup; import static io.anuke.mindustry.Vars.world; -public class Bullet extends BulletEntity implements TeamTrait, SyncTrait{ +public class Bullet extends BulletEntity implements TeamTrait{ private static Vector2 vector = new Vector2(); - private Interpolator interpolator = new Interpolator(); + //private Interpolator interpolator = new Interpolator(); private Team team; public Timer timer = new Timer(3); - public static Bullet create(BulletType type, TeamTrait owner, float x, float y, float angle){ - return create(type, owner, owner.getTeam(), x, y, angle); + public static void create (BulletType type, TeamTrait owner, float x, float y, float angle){ + create(type, owner, owner.getTeam(), x, y, angle); } - public static Bullet create (BulletType type, Entity owner, Team team, float x, float y, float angle){ + public static void create (BulletType type, Entity owner, Team team, float x, float y, float angle){ + create(type, owner, team, x, y, angle, 1f); + } + + public static void create (BulletType type, Entity owner, Team team, float x, float y, float angle, float velocityScl){ Bullet bullet = Pools.obtain(Bullet.class); bullet.type = type; bullet.owner = owner; - bullet.velocity.set(0, type.speed).setAngle(angle); + bullet.velocity.set(0, type.speed).setAngle(angle).scl(velocityScl); bullet.velocity.add(owner instanceof VelocityTrait ? ((VelocityTrait)owner).getVelocity() : Vector2.Zero); bullet.hitbox.setSize(type.hitsize); @@ -47,11 +45,14 @@ public class Bullet extends BulletEntity implements TeamTrait, SyncT bullet.type = type; bullet.set(x, y); bullet.add(); - return bullet; } - public static Bullet create(BulletType type, Bullet parent, float x, float y, float angle){ - return create(type, parent.owner, parent.team, x, y, angle); + public static void create(BulletType type, Bullet parent, float x, float y, float angle){ + create(type, parent.owner, parent.team, x, y, angle); + } + + public static void create(BulletType type, Bullet parent, float x, float y, float angle, float velocityScl){ + create(type, parent.owner, parent.team, x, y, angle, velocityScl); } /**Internal use only!*/ @@ -60,7 +61,7 @@ public class Bullet extends BulletEntity implements TeamTrait, SyncT public boolean collidesTiles(){ return true; //TODO make artillery and such not do this } - +/* @Override public boolean doSync(){ return type.syncable; @@ -85,7 +86,7 @@ public class Bullet extends BulletEntity implements TeamTrait, SyncT y = data.readFloat(); team = Team.values()[data.readByte()]; type = BulletType.getByID(data.readByte()); - } + }*/ @Override public Team getTeam() { diff --git a/core/src/io/anuke/mindustry/entities/units/BaseUnit.java b/core/src/io/anuke/mindustry/entities/units/BaseUnit.java index 8716bae685..8fdedabd2d 100644 --- a/core/src/io/anuke/mindustry/entities/units/BaseUnit.java +++ b/core/src/io/anuke/mindustry/entities/units/BaseUnit.java @@ -1,19 +1,18 @@ package io.anuke.mindustry.entities.units; import io.anuke.mindustry.content.fx.ExplosionFx; -import io.anuke.mindustry.entities.traits.TargetTrait; import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.entities.Unit; import io.anuke.mindustry.entities.Units; import io.anuke.mindustry.entities.bullet.Bullet; +import io.anuke.mindustry.entities.traits.TargetTrait; import io.anuke.mindustry.game.Team; import io.anuke.mindustry.net.Net; import io.anuke.mindustry.type.AmmoType; import io.anuke.mindustry.type.Item; -import io.anuke.mindustry.world.meta.BlockFlag; import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.meta.BlockFlag; import io.anuke.ucore.core.Effects; -import io.anuke.ucore.core.Effects.Effect; import io.anuke.ucore.core.Timers; import io.anuke.ucore.entities.EntityGroup; import io.anuke.ucore.util.Angles; @@ -21,7 +20,9 @@ import io.anuke.ucore.util.Geometry; import io.anuke.ucore.util.Mathf; import io.anuke.ucore.util.Timer; -import java.io.*; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; import static io.anuke.mindustry.Vars.*; @@ -55,12 +56,6 @@ public abstract class BaseUnit extends Unit{ rotation = Mathf.slerpDelta(rotation, angle, type.rotatespeed); } - public void effectAt(Effect effect, float rotation, float dx, float dy){ - Effects.effect(effect, - x + Angles.trnsx(rotation, dx, dy), - y + Angles.trnsy(rotation, dx, dy), Mathf.atan2(dx, dy) + rotation); - } - public boolean targetHasFlag(BlockFlag flag){ return target instanceof TileEntity && ((TileEntity)target).tile.block().flags.contains(flag); @@ -248,12 +243,13 @@ public abstract class BaseUnit extends Unit{ } @Override - public void write(DataOutput data) { - //todo + public void write(DataOutput data) throws IOException{ + writeSave(data); } @Override - public void read(DataInput data, long time) { - //todo + public void read(DataInput data, long time) throws IOException{ + super.readSave(data); + this.type = UnitType.getByID(data.readByte()); } } diff --git a/core/src/io/anuke/mindustry/io/TypeIO.java b/core/src/io/anuke/mindustry/io/TypeIO.java index 9ef0165d3e..2109778e5c 100644 --- a/core/src/io/anuke/mindustry/io/TypeIO.java +++ b/core/src/io/anuke/mindustry/io/TypeIO.java @@ -3,6 +3,9 @@ package io.anuke.mindustry.io; import io.anuke.annotations.Annotations.ReadClass; import io.anuke.annotations.Annotations.WriteClass; import io.anuke.mindustry.entities.Player; +import io.anuke.mindustry.net.Packets.KickReason; +import io.anuke.mindustry.type.Upgrade; +import io.anuke.mindustry.type.Weapon; import io.anuke.mindustry.world.Tile; @@ -34,19 +37,47 @@ public class TypeIO { return world.tile(buffer.getInt()); } + @WriteClass(KickReason.class) + public static void writeKick(ByteBuffer buffer, KickReason reason){ + buffer.put((byte)reason.ordinal()); + } + + @ReadClass(KickReason.class) + public static KickReason readKick(ByteBuffer buffer){ + return KickReason.values()[buffer.get()]; + } + + @WriteClass(Weapon.class) + public static void writeWeapon(ByteBuffer buffer, Weapon weapon){ + buffer.put(weapon.id); + } + + @ReadClass(Weapon.class) + public static Weapon readWeapon(ByteBuffer buffer){ + return Upgrade.getByID(buffer.get()); + } + @WriteClass(String.class) public static void writeString(ByteBuffer buffer, String string){ - byte[] bytes = string.getBytes(); - buffer.putShort((short)bytes.length); - buffer.put(bytes); + if(string != null) { + byte[] bytes = string.getBytes(); + buffer.putShort((short) bytes.length); + buffer.put(bytes); + }else{ + buffer.putShort((short)-1); + } } @ReadClass(String.class) public static String readString(ByteBuffer buffer){ short length = buffer.getShort(); - byte[] bytes = new byte[length]; - buffer.get(bytes); - return new String(bytes); + if(length != -1) { + byte[] bytes = new byte[length]; + buffer.get(bytes); + return new String(bytes); + }else{ + return null; + } } @WriteClass(byte[].class) diff --git a/core/src/io/anuke/mindustry/net/In.java b/core/src/io/anuke/mindustry/net/In.java new file mode 100644 index 0000000000..712eec04bb --- /dev/null +++ b/core/src/io/anuke/mindustry/net/In.java @@ -0,0 +1,8 @@ +package io.anuke.mindustry.net; + +/**Stores class nameas for remote method invocation for consistency's sake.*/ +public class In { + public static final String normal = "Call"; + public static final String entities = "CallEntity"; + public static final String blocks = "CallBlocks"; +} diff --git a/core/src/io/anuke/mindustry/net/Interpolator.java b/core/src/io/anuke/mindustry/net/Interpolator.java index aed9a61259..06e3ae2183 100644 --- a/core/src/io/anuke/mindustry/net/Interpolator.java +++ b/core/src/io/anuke/mindustry/net/Interpolator.java @@ -10,7 +10,7 @@ public class Interpolator { //used for movement public Vector2 target = new Vector2(); public Vector2 last = new Vector2(); - public float[] targets; + public float[] targets = {}; public float spacing = 1f; public float time; diff --git a/core/src/io/anuke/mindustry/net/Net.java b/core/src/io/anuke/mindustry/net/Net.java index f01f82d140..00eb5e7f3d 100644 --- a/core/src/io/anuke/mindustry/net/Net.java +++ b/core/src/io/anuke/mindustry/net/Net.java @@ -56,7 +56,6 @@ public class Net{ for(int i = 0; i < packetQueue.size; i ++){ Log.info("Processing {0} packet post-load.", ClassReflection.getSimpleName(packetQueue.get(i).getClass())); handleClientReceived(packetQueue.get(i)); - Pools.free(packetQueue.get(i)); } } //clear inbound packet queue @@ -180,6 +179,7 @@ public class Net{ } }else if(clientListeners.get(object.getClass()) != null || listeners.get(object.getClass()) != null){ + if(clientLoaded || object instanceof ImportantPacket){ if(clientListeners.get(object.getClass()) != null) clientListeners.get(object.getClass()).accept(object); if(listeners.get(object.getClass()) != null) listeners.get(object.getClass()).accept(object); diff --git a/core/src/io/anuke/mindustry/net/NetEvents.java b/core/src/io/anuke/mindustry/net/NetEvents.java index 2b338fc885..8aeb9a524c 100644 --- a/core/src/io/anuke/mindustry/net/NetEvents.java +++ b/core/src/io/anuke/mindustry/net/NetEvents.java @@ -1,5 +1,32 @@ package io.anuke.mindustry.net; +import io.anuke.annotations.Annotations.Loc; +import io.anuke.annotations.Annotations.Remote; +import io.anuke.annotations.Annotations.Variant; +import io.anuke.mindustry.Vars; +import io.anuke.mindustry.entities.Player; + +import static io.anuke.mindustry.Vars.playerGroup; + public class NetEvents { + @Remote(called = Loc.both, targets = Loc.both) + public static void sendMessage(Player player, String message){ + if(Vars.ui != null){ + Vars.ui.chatfrag.addMessage(message, player == null ? null : colorizeName(player.id, player.name)); + } + } + + @Remote(called = Loc.both, variants = Variant.both) + public static void sendMessage(String message){ + if(Vars.ui != null){ + Vars.ui.chatfrag.addMessage(message, null); + } + } + + private static String colorizeName(int id, String name){ + Player player = playerGroup.getByID(id); + if(name == null || player == null) return null; + return "[#" + player.color.toString().toUpperCase() + "]" + name; + } } diff --git a/core/src/io/anuke/mindustry/net/NetworkIO.java b/core/src/io/anuke/mindustry/net/NetworkIO.java index addff02ee9..3bd8e6a3e9 100644 --- a/core/src/io/anuke/mindustry/net/NetworkIO.java +++ b/core/src/io/anuke/mindustry/net/NetworkIO.java @@ -2,15 +2,15 @@ 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.game.TeamInfo; +import io.anuke.mindustry.game.TeamInfo.TeamData; 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; import io.anuke.mindustry.world.blocks.BlockPart; import io.anuke.ucore.core.Timers; @@ -37,16 +37,11 @@ public class NetworkIO { stream.writeInt(state.wave); //wave stream.writeFloat(state.wavetime); //wave countdown - stream.writeInt(state.enemies); //enemy amount stream.writeBoolean(state.friendlyFire); //friendly fire state - stream.writeInt(player.id); //player remap ID - stream.writeBoolean(player.isAdmin); - stream.writeByte(player.upgrades.size); - for(Upgrade u : player.upgrades){ - stream.writeByte(u.id); - } + stream.writeInt(player.id); + player.write(stream); //--MAP DATA-- @@ -80,6 +75,17 @@ public class NetworkIO { } } + //write team data + stream.writeByte(state.teams.getTeams().size); + for(TeamData data : state.teams.getTeams()){ + stream.writeByte(data.team.ordinal()); + stream.writeBoolean(data.ally); + stream.writeShort(data.cores.size); + for(Tile tile : data.cores){ + stream.writeInt(tile.packedPosition()); + } + } + }catch (IOException e){ throw new RuntimeException(e); } @@ -105,29 +111,18 @@ public class NetworkIO { int wave = stream.readInt(); float wavetime = stream.readFloat(); - int enemies = stream.readInt(); + boolean friendlyfire = stream.readBoolean(); - state.enemies = enemies; state.wave = wave; state.wavetime = wavetime; state.mode = GameMode.values()[mode]; state.friendlyFire = friendlyfire; - int pid = stream.readInt(); - boolean admin = stream.readBoolean(); - - byte weapons = stream.readByte(); - - for(int i = 0; i < weapons; i ++){ - player.upgrades.add(Upgrade.getByID(stream.readByte())); - } - - player.weapon = Weapons.blaster; - Entities.clear(); - player.id = pid; - player.isAdmin = admin; + int id = stream.readInt(); + player.read(stream, TimeUtils.millis()); + player.resetID(id); player.add(); world.beginMapLoad(); @@ -174,7 +169,20 @@ public class NetworkIO { } } - player.dead = true; + player.reset(); + state.teams = new TeamInfo(); + + byte teams = stream.readByte(); + for (int i = 0; i < teams; i++) { + Team team = Team.values()[stream.readByte()]; + boolean ally = stream.readBoolean(); + short cores = stream.readShort(); + state.teams.add(team, ally); + + for (int j = 0; j < cores; j++) { + state.teams.get(team).cores.add(world.tile(stream.readInt())); + } + } world.endMapLoad(); diff --git a/core/src/io/anuke/mindustry/net/Packets.java b/core/src/io/anuke/mindustry/net/Packets.java index 35a4968049..a54ee5661b 100644 --- a/core/src/io/anuke/mindustry/net/Packets.java +++ b/core/src/io/anuke/mindustry/net/Packets.java @@ -3,14 +3,14 @@ package io.anuke.mindustry.net; 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.io.ByteBufferInput; import io.anuke.ucore.io.ByteBufferOutput; import io.anuke.ucore.io.IOUtils; -import io.anuke.ucore.io.ByteBufferInput; +import java.io.IOException; import java.nio.ByteBuffer; /**Class for storing all packets.*/ @@ -66,19 +66,17 @@ public class Packets { @Override public void read(ByteBuffer buffer) { type = buffer.get(); - - if(Net.client()){ - RemoteReadClient.readPacket(buffer, type); - }else{ - byte[] bytes = new byte[writeLength]; - buffer.get(bytes); - writeBuffer = ByteBuffer.wrap(bytes); - } + writeLength = buffer.getShort(); + byte[] bytes = new byte[writeLength]; + buffer.get(bytes); + writeBuffer = ByteBuffer.wrap(bytes); } @Override public void write(ByteBuffer buffer) { buffer.put(type); + buffer.putShort((short)writeLength); + writeBuffer.position(0); for(int i = 0; i < writeLength; i ++){ buffer.put(writeBuffer.get()); @@ -109,7 +107,11 @@ public class Packets { buffer.putInt(lastSnapshot); buffer.putInt(player.id); buffer.putLong(TimeUtils.millis()); - player.write(out); + try { + player.write(out); + }catch (IOException e){ + e.printStackTrace(); + } } @Override @@ -120,7 +122,11 @@ public class Packets { int id = buffer.getInt(); long time = buffer.getLong(); player = Vars.playerGroup.getByID(id); - player.read(in, time); + try { + player.read(in, time); + }catch (IOException e){ + e.printStackTrace(); + } } } diff --git a/core/src/io/anuke/mindustry/net/ValidateException.java b/core/src/io/anuke/mindustry/net/ValidateException.java new file mode 100644 index 0000000000..35fe5d6114 --- /dev/null +++ b/core/src/io/anuke/mindustry/net/ValidateException.java @@ -0,0 +1,13 @@ +package io.anuke.mindustry.net; + +import io.anuke.mindustry.entities.Player; + +/**Thrown when a client sends invalid information.*/ +public class ValidateException extends RuntimeException{ + public final Player player; + + public ValidateException(Player player, String s) { + super(s); + this.player = player; + } +} diff --git a/core/src/io/anuke/mindustry/type/AmmoType.java b/core/src/io/anuke/mindustry/type/AmmoType.java index ab5f36f9f9..d5ba6890da 100644 --- a/core/src/io/anuke/mindustry/type/AmmoType.java +++ b/core/src/io/anuke/mindustry/type/AmmoType.java @@ -8,7 +8,7 @@ import io.anuke.ucore.core.Effects.Effect; public class AmmoType implements Content{ private static int lastID = 0; - private static Array allTypes = new Array<>(); + private static Array allTypes = new Array<>(32); public final byte id; /**The item used. Always null if liquid isn't.*/ diff --git a/core/src/io/anuke/mindustry/type/Weapon.java b/core/src/io/anuke/mindustry/type/Weapon.java index 0e0a6fd9c5..bb203b673c 100644 --- a/core/src/io/anuke/mindustry/type/Weapon.java +++ b/core/src/io/anuke/mindustry/type/Weapon.java @@ -2,10 +2,14 @@ package io.anuke.mindustry.type; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.utils.ObjectMap; +import io.anuke.annotations.Annotations.Remote; +import io.anuke.annotations.Annotations.Loc; import io.anuke.mindustry.content.fx.Fx; import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.entities.Unit; import io.anuke.mindustry.entities.bullet.Bullet; +import io.anuke.mindustry.gen.CallEntity; +import io.anuke.mindustry.net.In; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Effects.Effect; import io.anuke.ucore.graphics.Draw; @@ -51,7 +55,7 @@ public class Weapon extends Upgrade { public void update(Player p, boolean left, float pointerX, float pointerY){ int t = left ? Player.timerShootLeft : Player.timerShootRight; - int t2 = !left ? Player.timerShootRight : Player.timerShootLeft; + int t2 = !left ? Player.timerShootLeft : Player.timerShootRight; if(p.inventory.hasAmmo() && p.timer.get(t, reload)){ if(roundrobin){ p.timer.reset(t2, reload/2f); @@ -78,8 +82,7 @@ public class Weapon extends Upgrade { } public void shoot(Player p, float x, float y, float angle, boolean left){ - shootInternal(p, x, y, angle, left); - + CallEntity.onShootWeapon(p, this, x, y, angle, left); p.inventory.useAmmo(); } @@ -92,26 +95,30 @@ public class Weapon extends Upgrade { ammoMap.put(type.item, type); } } - - void shootInternal(Player p, float x, float y, float rotation, boolean left){ - Angles.shotgun(shots, spacing, rotation, f -> bullet(p, x, y, f + Mathf.range(inaccuracy))); - - AmmoType type = p.inventory.getAmmo(); - - tr.trns(rotation + 180f, type.recoil); - - p.getVelocity().add(tr); - - tr.trns(rotation, 3f); - - Effects.shake(shake, shake, x, y); - Effects.effect(ejectEffect, x, y, rotation * -Mathf.sign(left)); - Effects.effect(type.shootEffect, x + tr.x, y + tr.y, rotation, p); - Effects.effect(type.smokeEffect, x + tr.x, y + tr.y, rotation, p); - } void bullet(Unit owner, float x, float y, float angle){ tr.trns(angle, 3f); Bullet.create(owner.inventory.getAmmo().bullet, owner, x + tr.x, y + tr.y, angle); } + + @Remote(targets = Loc.both, called = Loc.both, in = In.entities, forward = true) + public static void onShootWeapon(Player player, Weapon weapon, float x, float y, float rotation, boolean left){ + Angles.shotgun(weapon.shots, weapon.spacing, rotation, f -> weapon.bullet(player, x, y, f + Mathf.range(weapon.inaccuracy))); + + AmmoType type = player.inventory.getAmmo(); + + weapon.tr.trns(rotation + 180f, type.recoil); + + player.getVelocity().add(weapon.tr); + + weapon.tr.trns(rotation, 3f); + + Effects.shake(weapon.shake, weapon.shake, x, y); + Effects.effect(weapon.ejectEffect, x, y, rotation * -Mathf.sign(left)); + Effects.effect(type.shootEffect, x + weapon.tr.x, y + weapon.tr.y, rotation, player); + Effects.effect(type.smokeEffect, x + weapon.tr.x, y + weapon.tr.y, rotation, player); + + //reset timer for remote players + player.timer.getTime(left ? Player.timerShootLeft : Player.timerShootRight); + } } diff --git a/core/src/io/anuke/mindustry/ui/fragments/ChatFragment.java b/core/src/io/anuke/mindustry/ui/fragments/ChatFragment.java index 93695be55b..187d913b2d 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/ChatFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/ChatFragment.java @@ -9,6 +9,7 @@ import com.badlogic.gdx.utils.Array; import io.anuke.mindustry.Vars; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.core.Platform; +import io.anuke.mindustry.gen.Call; import io.anuke.mindustry.net.Net; import io.anuke.ucore.core.Core; import io.anuke.ucore.core.Inputs; @@ -21,6 +22,7 @@ import io.anuke.ucore.scene.ui.layout.Table; import io.anuke.ucore.scene.ui.layout.Unit; import io.anuke.ucore.util.Mathf; +import static io.anuke.mindustry.Vars.players; import static io.anuke.mindustry.Vars.state; import static io.anuke.ucore.core.Core.scene; import static io.anuke.ucore.core.Core.skin; @@ -166,7 +168,8 @@ public class ChatFragment extends Table implements Fragment{ if(message.replaceAll(" ", "").isEmpty()) return; history.insert(1, message); - //TODO send the message + + Call.sendMessage(players[0], message); } public void toggle(){ diff --git a/core/src/io/anuke/mindustry/world/Block.java b/core/src/io/anuke/mindustry/world/Block.java index f69a07aa9d..0223089eb5 100644 --- a/core/src/io/anuke/mindustry/world/Block.java +++ b/core/src/io/anuke/mindustry/world/Block.java @@ -39,7 +39,7 @@ import static io.anuke.mindustry.Vars.*; public class Block extends BaseBlock implements UnlockableContent{ private static int lastid; - private static Array blocks = new Array<>(); + private static Array blocks = new Array<>(140); private static ObjectMap map = new ObjectMap<>(); protected Array tempTiles = new Array<>(); diff --git a/core/src/io/anuke/mindustry/world/Tile.java b/core/src/io/anuke/mindustry/world/Tile.java index 060e3e5565..0219a3003b 100644 --- a/core/src/io/anuke/mindustry/world/Tile.java +++ b/core/src/io/anuke/mindustry/world/Tile.java @@ -189,6 +189,7 @@ public class Tile implements PosTrait, TargetTrait { return isLinked() || !((floor.solid && (block == Blocks.air || block.solidifes)) || (block.solid && (!block.destructible && !block.update))); } + /**Whether this block was placed by a player/unit.*/ public boolean synthetic(){ Block block = block(); return block.update || block.destructible; @@ -197,7 +198,8 @@ public class Tile implements PosTrait, TargetTrait { public boolean solid(){ Block block = block(); Block floor = floor(); - return block.solid || (floor.solid && (block == Blocks.air || block.solidifes)) || block.isSolidFor(this); + return block.solid || (floor.solid && (block == Blocks.air || block.solidifes)) || block.isSolidFor(this) + || (isLinked() && getLinked().block().isSolidFor(getLinked())); } public boolean breakable(){ diff --git a/core/src/io/anuke/mindustry/world/blocks/storage/CoreBlock.java b/core/src/io/anuke/mindustry/world/blocks/storage/CoreBlock.java index 78f3043281..eae79f9af5 100644 --- a/core/src/io/anuke/mindustry/world/blocks/storage/CoreBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/storage/CoreBlock.java @@ -2,17 +2,24 @@ package io.anuke.mindustry.world.blocks.storage; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.math.Rectangle; +import io.anuke.annotations.Annotations.Loc; +import io.anuke.annotations.Annotations.Remote; import io.anuke.mindustry.Vars; import io.anuke.mindustry.content.fx.Fx; -import io.anuke.mindustry.entities.*; +import io.anuke.mindustry.entities.Player; +import io.anuke.mindustry.entities.TileEntity; +import io.anuke.mindustry.entities.Unit; +import io.anuke.mindustry.entities.Units; import io.anuke.mindustry.entities.effect.ItemTransfer; +import io.anuke.mindustry.gen.CallBlocks; import io.anuke.mindustry.graphics.Palette; import io.anuke.mindustry.graphics.Shaders; +import io.anuke.mindustry.net.In; import io.anuke.mindustry.net.Net; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.type.ItemType; -import io.anuke.mindustry.world.meta.BlockFlag; import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.meta.BlockFlag; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Graphics; import io.anuke.ucore.core.Timers; @@ -21,6 +28,10 @@ import io.anuke.ucore.graphics.Lines; import io.anuke.ucore.util.EnumSet; import io.anuke.ucore.util.Mathf; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + import static io.anuke.mindustry.Vars.debug; import static io.anuke.mindustry.Vars.state; @@ -130,7 +141,7 @@ public class CoreBlock extends StorageBlock { CoreEntity entity = tile.entity(); if(!entity.solid && !Units.anyEntities(tile)){ - entity.solid = true; + CallBlocks.setCoreSolid(tile, true); } if(entity.currentPlayer != null){ @@ -145,8 +156,8 @@ public class CoreBlock extends StorageBlock { if(entity.progress >= 1f){ Effects.effect(Fx.spawn, entity); + CallBlocks.setCoreSolid(tile, false); entity.progress = 0; - entity.solid = false; entity.currentPlayer.heal(); entity.currentPlayer.rotation = 90f; entity.currentPlayer.baseRotation = 90f; @@ -182,6 +193,12 @@ public class CoreBlock extends StorageBlock { return new CoreEntity(); } + @Remote(called = Loc.server, in = In.blocks) + public static void setCoreSolid(Tile tile, boolean solid){ + CoreEntity entity = tile.entity(); + entity.solid = solid; + } + public class CoreEntity extends TileEntity{ Player currentPlayer; boolean solid = true; @@ -196,5 +213,15 @@ public class CoreBlock extends StorageBlock { progress = 0f; return true; } + + @Override + public void write(DataOutputStream stream) throws IOException { + stream.writeBoolean(solid); + } + + @Override + public void read(DataInputStream stream) throws IOException { + solid = stream.readBoolean(); + } } } diff --git a/kryonet/src/io/anuke/kryonet/KryoClient.java b/kryonet/src/io/anuke/kryonet/KryoClient.java index 847ec86f14..2e220d1bdd 100644 --- a/kryonet/src/io/anuke/kryonet/KryoClient.java +++ b/kryonet/src/io/anuke/kryonet/KryoClient.java @@ -4,7 +4,6 @@ import com.badlogic.gdx.Gdx; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.ObjectMap; import com.badlogic.gdx.utils.ObjectSet; -import com.badlogic.gdx.utils.Pools; import com.esotericsoftware.kryonet.*; import com.esotericsoftware.kryonet.Listener.LagListener; import com.esotericsoftware.minlog.Log; @@ -133,7 +132,6 @@ public class KryoClient implements ClientProvider{ }else{ client.sendUDP(object); } - Pools.free(object); } @Override diff --git a/kryonet/src/io/anuke/kryonet/KryoServer.java b/kryonet/src/io/anuke/kryonet/KryoServer.java index de96336f91..8cc2dd66a9 100644 --- a/kryonet/src/io/anuke/kryonet/KryoServer.java +++ b/kryonet/src/io/anuke/kryonet/KryoServer.java @@ -3,7 +3,6 @@ package io.anuke.kryonet; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Base64Coder; -import com.badlogic.gdx.utils.Pools; import com.esotericsoftware.kryonet.Connection; import com.esotericsoftware.kryonet.FrameworkMessage; import com.esotericsoftware.kryonet.Listener; @@ -367,7 +366,6 @@ public class KryoServer implements ServerProvider { Log.info("Connection removed {0}", k); } } - Pools.free(object); } @Override diff --git a/server/src/io/anuke/mindustry/server/MindustryServer.java b/server/src/io/anuke/mindustry/server/MindustryServer.java index 205ebf1fab..6f48c3f46a 100644 --- a/server/src/io/anuke/mindustry/server/MindustryServer.java +++ b/server/src/io/anuke/mindustry/server/MindustryServer.java @@ -28,7 +28,6 @@ public class MindustryServer extends ModuleCore { module(logic = new Logic()); module(world = new World()); module(netServer = new NetServer()); - module(netCommon = new NetCommon()); module(new ServerControl(args)); } } diff --git a/server/src/io/anuke/mindustry/server/ServerControl.java b/server/src/io/anuke/mindustry/server/ServerControl.java index e5f42230ba..7bc5831a76 100644 --- a/server/src/io/anuke/mindustry/server/ServerControl.java +++ b/server/src/io/anuke/mindustry/server/ServerControl.java @@ -229,7 +229,7 @@ public class ServerControl extends Module { return; } - netCommon.sendMessage("[GRAY][[Server]:[] " + arg[0]); + //netCommon.sendMessage("[GRAY][[Server]:[] " + arg[0]); info("&lyServer: &lb{0}", arg[0]); });