diff --git a/annotations/build/libs/annotations-release.jar b/annotations/build/libs/annotations-release.jar index a369a0cff6..683e153024 100644 Binary files a/annotations/build/libs/annotations-release.jar and b/annotations/build/libs/annotations-release.jar differ diff --git a/annotations/src/io/anuke/annotations/AnnotationProcessor.java b/annotations/src/io/anuke/annotations/AnnotationProcessor.java index dc876fcf6e..7e0bb97549 100644 --- a/annotations/src/io/anuke/annotations/AnnotationProcessor.java +++ b/annotations/src/io/anuke/annotations/AnnotationProcessor.java @@ -2,15 +2,17 @@ package io.anuke.annotations; import com.squareup.javapoet.*; import io.anuke.annotations.Annotations.Local; -import io.anuke.annotations.Annotations.Remote; +import io.anuke.annotations.Annotations.RemoteClient; +import io.anuke.annotations.Annotations.RemoteServer; import javax.annotation.processing.*; import javax.lang.model.SourceVersion; import javax.lang.model.element.*; -import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import javax.tools.Diagnostic.Kind; +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; @@ -18,14 +20,16 @@ import java.util.Set; @SupportedSourceVersion(SourceVersion.RELEASE_8) @SupportedAnnotationTypes({ - "io.anuke.annotations.Annotations.Remote", + "io.anuke.annotations.Annotations.RemoteClient", + "io.anuke.annotations.Annotations.RemoteServer", "io.anuke.annotations.Annotations.Local" }) public class AnnotationProcessor extends AbstractProcessor { private static final int maxPacketSize = 128; - private static final String fullClassName = "io.anuke.mindustry.gen.CallEvent"; - private static final String className = fullClassName.substring(1 + fullClassName.lastIndexOf('.')); - private static final String packageName = fullClassName.substring(0, fullClassName.lastIndexOf('.')); + + private static final String clientFullClassName = "io.anuke.mindustry.gen.CallClient"; + private static final String serverFullClassName = "io.anuke.mindustry.gen.CallServer"; + private static final HashMap writeMap = new HashMap(){{ put("Player", new String[][]{ { @@ -57,22 +61,24 @@ public class AnnotationProcessor extends AbstractProcessor { if(done) return false; done = true; - ArrayList elements = new ArrayList<>(); + writeElements(roundEnv, clientFullClassName, RemoteClient.class); + writeElements(roundEnv, serverFullClassName, RemoteServer.class); - for (Element element : roundEnv.getElementsAnnotatedWith(Remote.class)) { - if(!element.getModifiers().contains(Modifier.STATIC)) { - messager.printMessage(Kind.ERROR, "All local/remote methods must be static: ", element); - }else if(element.getKind() != ElementKind.METHOD){ - messager.printMessage(Kind.ERROR, "All local/remote annotations must be on methods: ", element); - }else{ - elements.add(element); - } - } + return true; + } + private void writeElements(RoundEnvironment env, String fullClassName, Class annotation){ try { + boolean client = annotation == RemoteServer.class; + String className = fullClassName.substring(1 + fullClassName.lastIndexOf('.')); + String packageName = fullClassName.substring(0, fullClassName.lastIndexOf('.')); - TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className) - .addModifiers(Modifier.PUBLIC); + Constructor cons = TypeName.class.getDeclaredConstructor(String.class); + cons.setAccessible(true); + + TypeName playerType = cons.newInstance("io.anuke.mindustry.entities.Player"); + + TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className).addModifiers(Modifier.PUBLIC); int id = 0; @@ -85,12 +91,23 @@ public class AnnotationProcessor extends AbstractProcessor { .addParameter(int.class, "id") .returns(void.class); + if(client){ + readMethod.addParameter(playerType, "player"); + } + CodeBlock.Builder writeSwitch = CodeBlock.builder(); boolean started = false; readMethod.addJavadoc("This method reads and executes a method by ID. For internal use only!"); - for (Element e : elements) { + for (Element e : env.getElementsAnnotatedWith(annotation)) { + if(!e.getModifiers().contains(Modifier.STATIC)) { + messager.printMessage(Kind.ERROR, "All local/remote methods must be static: ", e); + }else if(e.getKind() != ElementKind.METHOD){ + messager.printMessage(Kind.ERROR, "All local/remote annotations must be on methods: ", e); + } + + if(e.getAnnotation(annotation) == null) continue; boolean local = e.getAnnotation(Local.class) != null; ExecutableElement exec = (ExecutableElement)e; @@ -99,12 +116,24 @@ public class AnnotationProcessor extends AbstractProcessor { .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(void.class); + if(client){ + if(exec.getParameters().isEmpty()){ + messager.printMessage(Kind.ERROR, "Client invoke methods must have a first parameter of type Player.", e); + return; + } + + VariableElement var = exec.getParameters().get(0); + + if(!var.asType().toString().equals("io.anuke.mindustry.entities.Player")){ + messager.printMessage(Kind.ERROR, "Client invoke methods should have a first parameter of type Player.", e); + } + } + for(VariableElement var : exec.getParameters()){ method.addParameter(TypeName.get(var.asType()), var.getSimpleName().toString()); } if(local){ - //todo int index = 0; StringBuilder results = new StringBuilder(); for(VariableElement var : exec.getParameters()){ @@ -124,11 +153,17 @@ public class AnnotationProcessor extends AbstractProcessor { } started = true; - method.addStatement("$1N packet = new $1N()", "io.anuke.mindustry.net.Packets.InvokePacket"); + method.addStatement("$1N packet = $2N.obtain($1N.class)", "io.anuke.mindustry.net.Packets.InvokePacket", + "com.badlogic.gdx.utils.Pools"); method.addStatement("packet.writeBuffer = TEMP_BUFFER"); method.addStatement("TEMP_BUFFER.position(0)"); - for(VariableElement var : exec.getParameters()){ + ArrayList parameters = new ArrayList<>(exec.getParameters()); + if(client){ + parameters.remove(0); + } + + for(VariableElement var : parameters){ String varName = var.getSimpleName().toString(); String typeName = var.asType().toString(); String bufferName = "TEMP_BUFFER"; @@ -148,7 +183,7 @@ public class AnnotationProcessor extends AbstractProcessor { String[] values = writeMap.get(simpleTypeName)[0]; for(String str : values){ method.addStatement(str.replaceAll("rbuffer", bufferName) - .replaceAll("rvalue", varName)); + .replaceAll("rvalue", varName)); } }else{ messager.printMessage(Kind.ERROR, "No method for writing type: " + typeName, var); @@ -178,13 +213,9 @@ public class AnnotationProcessor extends AbstractProcessor { classBuilder.addMethod(method.build()); - FieldSpec var = FieldSpec.builder(TypeName.INT, "ID_METHOD_" + exec.getSimpleName().toString().toUpperCase()) - .initializer("$1L", id).addModifiers(Modifier.FINAL, Modifier.PRIVATE, Modifier.STATIC).build(); - - classBuilder.addField(var); - int index = 0; StringBuilder results = new StringBuilder(); + for(VariableElement writevar : exec.getParameters()){ results.append(writevar.getSimpleName()); if(index != exec.getParameters().size() - 1) results.append(", "); @@ -195,8 +226,6 @@ public class AnnotationProcessor extends AbstractProcessor { ((TypeElement)e.getEnclosingElement()).getQualifiedName().toString()); id ++; - - //TODO add params from the method and invoke it } if(started){ @@ -209,13 +238,10 @@ public class AnnotationProcessor extends AbstractProcessor { TypeSpec spec = classBuilder.build(); JavaFile.builder(packageName, spec).build().writeTo(filer); - - }catch (Exception e){ + e.printStackTrace(); throw new RuntimeException(e); } - - return true; } private boolean isPrimitive(String type){ diff --git a/annotations/src/io/anuke/annotations/Annotations.java b/annotations/src/io/anuke/annotations/Annotations.java index 6c5ea3d4b4..192b75b829 100644 --- a/annotations/src/io/anuke/annotations/Annotations.java +++ b/annotations/src/io/anuke/annotations/Annotations.java @@ -5,14 +5,33 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Goal: To create a system to send events to the server from the client and vice versa.
+ * These events may optionally also trigger on the caller client/server as well.
+ *
+ * Three annotations are used for this purpose.
+ * {@link RemoteClient}: Marks a method as able to be invoked remotely on a client from a server.
+ * {@link RemoteServer}: Marks a method as able to be invoked remotely on a server from a client.
+ * {@link Local}: Makes this method get invoked locally as well as remotely.
+ *
+ * All RemoteClient methods are put in the class CallClient, and all RemoteServer methods are put in the class CallServer.
+ */ public class Annotations { - /**Marks a method as invokable remotely.*/ + /**Marks a method as invokable remotely from a server on a client.*/ @Target(ElementType.METHOD) @Retention(RetentionPolicy.CLASS) - public @interface Remote{} + public @interface RemoteClient {} - /**Marks a method to be locally invoked as well as remotely invoked.*/ + /**Marks a method as invokable remotely from a client on a server. + * All RemoteServer methods must have their first formal parameter be of type Player. + * This player is the invoker of the method.*/ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.CLASS) + public @interface RemoteServer {} + + /**Marks a method to be locally invoked as well as remotely invoked on the caller + * Must be used with {@link RemoteClient}/{@link RemoteServer} annotations.*/ @Target(ElementType.METHOD) @Retention(RetentionPolicy.CLASS) public @interface Local{} diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index dd0c843b6f..f0ef99fa84 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -10,6 +10,7 @@ import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.entities.BulletType; import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.entities.SyncEntity; +import io.anuke.mindustry.gen.CallServer; import io.anuke.mindustry.io.Platform; import io.anuke.mindustry.io.Version; import io.anuke.mindustry.net.*; @@ -162,6 +163,10 @@ public class NetServer extends Module{ //...don't do anything here as it's already handled by the packet itself }); + Net.handleServer(InvokePacket.class, (id, packet) -> { + CallServer.readPacket(packet.writeBuffer, packet.type, connections.get(id)); + }); + Net.handleServer(EntityShootPacket.class, (id, packet) -> { Player player = connections.get(id); diff --git a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java index fa2ed334f5..eabc7ae82b 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java @@ -27,6 +27,7 @@ import io.anuke.ucore.scene.builders.table; import io.anuke.ucore.scene.ui.*; import io.anuke.ucore.scene.ui.layout.Stack; import io.anuke.ucore.scene.ui.layout.Table; +import io.anuke.ucore.scene.utils.UIUtils; import io.anuke.ucore.util.Bundles; import io.anuke.ucore.input.Input; import io.anuke.ucore.util.Log; @@ -387,15 +388,14 @@ public class MapEditorDialog extends Dialog{ private void doInput(){ //tool select for(int i = 0; i < EditorTool.values().length; i ++){ - int code = i == 0 ? 5 : i; - if(Inputs.keyTap("weapon_" + code)){ + if(Inputs.keyTap(Input.valueOf("NUM_" + (i+1)))){ view.setTool(EditorTool.values()[i]); break; } } //ctrl keys (undo, redo, save) - if(Inputs.keyDown(Input.CONTROL_LEFT)){ + if(UIUtils.ctrl()){ if(Inputs.keyTap(Input.Z)){ view.undo(); } diff --git a/core/src/io/anuke/mindustry/entities/Player.java b/core/src/io/anuke/mindustry/entities/Player.java index 1c423c2234..2eabb63762 100644 --- a/core/src/io/anuke/mindustry/entities/Player.java +++ b/core/src/io/anuke/mindustry/entities/Player.java @@ -10,6 +10,7 @@ import io.anuke.mindustry.content.Weapons; import io.anuke.mindustry.content.fx.ExplosionFx; import io.anuke.mindustry.entities.effect.DamageArea; import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.gen.CallClient; import io.anuke.mindustry.graphics.Palette; import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.NetEvents; @@ -338,7 +339,8 @@ public class Player extends Unit implements BlockPlacer{ currentPlace = null; }else if(distanceTo(check) <= placeDistance){ BuildEntity entity = check.entity(); - entity.progress += 1f / entity.result.health; + + entity.progress += 1f / entity.recipe.cost; rotation = Mathf.slerpDelta(rotation, angleTo(entity), 0.4f); } diff --git a/core/src/io/anuke/mindustry/net/NetEvents.java b/core/src/io/anuke/mindustry/net/NetEvents.java index 31c3b85f1f..db258b56f8 100644 --- a/core/src/io/anuke/mindustry/net/NetEvents.java +++ b/core/src/io/anuke/mindustry/net/NetEvents.java @@ -2,7 +2,8 @@ package io.anuke.mindustry.net; import com.badlogic.gdx.utils.Pools; import io.anuke.annotations.Annotations.Local; -import io.anuke.annotations.Annotations.Remote; +import io.anuke.annotations.Annotations.RemoteClient; +import io.anuke.annotations.Annotations.RemoteServer; import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.entities.SyncEntity; import io.anuke.mindustry.entities.TileEntity; @@ -17,7 +18,7 @@ import static io.anuke.mindustry.Vars.*; public class NetEvents { - @Remote + @RemoteClient @Local public static void friendlyFireChange(boolean enabled){ state.friendlyFire = enabled; @@ -25,6 +26,17 @@ public class NetEvents { if(Net.server()) netCommon.sendMessage(enabled ? "[accent]Friendly fire enabled." : "[accent]Friendly fire disabled."); } + @RemoteServer + public static void notifySomethingFromClient(Player player, int x, float y){ + + } + + @RemoteClient + @Local + public static void notifySomethingFromServerLocal(int y, float x, boolean w){ + + } + public static void handleGameOver(){ Net.send(Pools.obtain(GameOverPacket.class), SendMode.tcp); } @@ -116,7 +128,7 @@ public class NetEvents { Net.send(packet, SendMode.tcp); } - @Remote + @RemoteClient @Local public static void adminSet(Player player, boolean admin){ player.isAdmin = admin; diff --git a/core/src/io/anuke/mindustry/net/Packets.java b/core/src/io/anuke/mindustry/net/Packets.java index 9ebcae2067..2e8fb58b0e 100644 --- a/core/src/io/anuke/mindustry/net/Packets.java +++ b/core/src/io/anuke/mindustry/net/Packets.java @@ -7,7 +7,7 @@ import com.badlogic.gdx.utils.reflect.ReflectionException; import io.anuke.mindustry.Vars; import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.entities.SyncEntity; -import io.anuke.mindustry.gen.CallEvent; +import io.anuke.mindustry.gen.CallClient; import io.anuke.mindustry.io.Version; import io.anuke.mindustry.net.Packet.ImportantPacket; import io.anuke.mindustry.net.Packet.UnimportantPacket; @@ -43,9 +43,11 @@ public class Packets { type = buffer.get(); if(Net.client()){ - CallEvent.readPacket(buffer, type); + CallClient.readPacket(buffer, type); }else{ - buffer.position(buffer.position() + writeLength); + byte[] bytes = new byte[writeLength]; + buffer.get(bytes); + writeBuffer = ByteBuffer.wrap(bytes); } } diff --git a/core/src/io/anuke/mindustry/resource/Item.java b/core/src/io/anuke/mindustry/resource/Item.java index cd40697481..6c1d7a5583 100644 --- a/core/src/io/anuke/mindustry/resource/Item.java +++ b/core/src/io/anuke/mindustry/resource/Item.java @@ -28,6 +28,9 @@ public class Item implements Comparable{ public int hardness = 0; /**the burning color of this item*/ public Color flameColor = Palette.darkFlame.cpy(); + /**base material cost of this item, used for calculating place times + * 1 cost = 1 tick added to build time*/ + public float cost = 1f; public Item(String name, Color color) { this.id = items.size; diff --git a/core/src/io/anuke/mindustry/resource/Recipe.java b/core/src/io/anuke/mindustry/resource/Recipe.java index 4109b3dfc8..cb9e338f60 100644 --- a/core/src/io/anuke/mindustry/resource/Recipe.java +++ b/core/src/io/anuke/mindustry/resource/Recipe.java @@ -13,6 +13,7 @@ public class Recipe { public final Block result; public final ItemStack[] requirements; public final Section section; + public final float cost; public boolean desktopOnly = false, debugOnly = false; @@ -22,6 +23,13 @@ public class Recipe { this.requirements = requirements; this.section = section; + float timeToPlace = 0f; + for(ItemStack stack : requirements){ + timeToPlace += stack.amount * stack.item.cost; + } + + this.cost = timeToPlace; + allRecipes.add(this); recipeMap.put(result, this); } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/AdminsDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/AdminsDialog.java index 0979c798eb..70faef7784 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/AdminsDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/AdminsDialog.java @@ -1,7 +1,7 @@ package io.anuke.mindustry.ui.dialogs; import io.anuke.mindustry.entities.Player; -import io.anuke.mindustry.gen.CallEvent; +import io.anuke.mindustry.gen.CallClient; import io.anuke.mindustry.net.Administration.PlayerInfo; import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.NetConnection; @@ -49,7 +49,7 @@ public class AdminsDialog extends FloatingDialog { for(Player player : playerGroup.all()){ NetConnection c = Net.getConnection(player.clientid); if(c != null){ - CallEvent.adminSet(player, false); + CallClient.adminSet(player, false); break; } } diff --git a/core/src/io/anuke/mindustry/ui/fragments/PlayerListFragment.java b/core/src/io/anuke/mindustry/ui/fragments/PlayerListFragment.java index f632e0d243..3ffa9e2c3d 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/PlayerListFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/PlayerListFragment.java @@ -3,7 +3,7 @@ package io.anuke.mindustry.ui.fragments; import com.badlogic.gdx.utils.ObjectMap; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.entities.Player; -import io.anuke.mindustry.gen.CallEvent; +import io.anuke.mindustry.gen.CallClient; import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.NetConnection; import io.anuke.mindustry.net.NetEvents; @@ -49,7 +49,7 @@ public class PlayerListFragment implements Fragment{ margin(12f); get().addCheck("$text.server.friendlyfire", b -> { - CallEvent.friendlyFireChange(b); +// CallClient.friendlyFireChange(b); }).growX().update(i -> i.setChecked(state.friendlyFire)).disabled(b -> Net.client()).padRight(5); new button("$text.server.bans", () -> { @@ -160,12 +160,12 @@ public class PlayerListFragment implements Fragment{ if(netServer.admins.isAdmin(id, connection.address)){ ui.showConfirm("$text.confirm", "$text.confirmunadmin", () -> { netServer.admins.unAdminPlayer(id); - CallEvent.adminSet(player, false); + CallClient.adminSet(player, false); }); }else{ ui.showConfirm("$text.confirm", "$text.confirmadmin", () -> { netServer.admins.adminPlayer(id, connection.address); - CallEvent.adminSet(player, true); + CallClient.adminSet(player, true); }); } }).update(b ->{