diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml
index 7d4d731df1..9af4f50c90 100644
--- a/android/AndroidManifest.xml
+++ b/android/AndroidManifest.xml
@@ -17,7 +17,8 @@
android:usesCleartextTraffic="true"
android:appCategory="game"
android:label="@string/app_name"
- android:theme="@style/ArcTheme" android:fullBackupContent="@xml/backup_rules">
+ android:theme="@style/ArcTheme"
+ android:fullBackupContent="@xml/backup_rules">
copy{
@@ -131,25 +155,7 @@ task copyAndroidNatives(){
}
task run(type: Exec){
- def path
- def localProperties = project.file("../local.properties")
- if(localProperties.exists()){
- Properties properties = new Properties()
- localProperties.withInputStream{ instr ->
- properties.load(instr)
- }
- def sdkDir = properties.getProperty('sdk.dir')
- if(sdkDir){
- path = sdkDir
- }else{
- path = "$System.env.ANDROID_HOME"
- }
- }else{
- path = "$System.env.ANDROID_HOME"
- }
-
- def adb = path + "/platform-tools/adb"
- commandLine "$adb", 'shell', 'am', 'start', '-n', 'io.anuke.mindustry/mindustry.android.AndroidLauncher'
+ commandLine "${findSdkDir()}/platform-tools/adb", 'shell', 'am', 'start', '-n', 'io.anuke.mindustry/mindustry.android.AndroidLauncher'
}
if(!project.ext.hasSprites()){
diff --git a/android/proguard-rules.pro b/android/proguard-rules.pro
new file mode 100644
index 0000000000..f25e5e79c5
--- /dev/null
+++ b/android/proguard-rules.pro
@@ -0,0 +1,8 @@
+-dontobfuscate
+
+#these are essential packages that should not be "optimized" in any way
+#the main purpose of d8 here is to shrink the absurdly-large google play games libraries
+-keep class mindustry.** { *; }
+-keep class arc.** { *; }
+-keep class net.jpountz.** { *; }
+-keep class rhino.** { *; }
diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml
index d40cf3accd..d42ffe6d4a 100644
--- a/android/res/values/strings.xml
+++ b/android/res/values/strings.xml
@@ -1,6 +1,4 @@
-
Mindustry
-
diff --git a/android/res/values/styles.xml b/android/res/values/styles.xml
index 3fe4d7f6a8..c37d2c6ed9 100644
--- a/android/res/values/styles.xml
+++ b/android/res/values/styles.xml
@@ -1,5 +1,4 @@
-
-
diff --git a/android/src/mindustry/android/AndroidLauncher.java b/android/src/mindustry/android/AndroidLauncher.java
index b46d09c9c0..69543be82e 100644
--- a/android/src/mindustry/android/AndroidLauncher.java
+++ b/android/src/mindustry/android/AndroidLauncher.java
@@ -15,9 +15,11 @@ import arc.func.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import dalvik.system.*;
+import io.anuke.mindustry.*;
import mindustry.*;
import mindustry.game.Saves.*;
import mindustry.io.*;
+import mindustry.mod.*;
import mindustry.net.*;
import mindustry.ui.dialogs.*;
@@ -33,6 +35,9 @@ public class AndroidLauncher extends AndroidApplication{
FileChooser chooser;
Runnable permCallback;
+ Object gpService;
+ Class> serviceClass;
+
@Override
protected void onCreate(Bundle savedInstanceState){
UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
@@ -50,7 +55,7 @@ public class AndroidLauncher extends AndroidApplication{
});
super.onCreate(savedInstanceState);
- if(doubleScaleTablets && isTablet(this.getContext())){
+ if(doubleScaleTablets && isTablet(this)){
Scl.setAddition(0.5f);
}
@@ -63,7 +68,9 @@ public class AndroidLauncher extends AndroidApplication{
@Override
public rhino.Context getScriptContext(){
- return AndroidRhinoContext.enter(getContext().getCacheDir());
+ rhino.Context result = AndroidRhinoContext.enter(((Context)AndroidLauncher.this).getCacheDir());
+ result.setClassShutter(Scripts::allowClass);
+ return result;
}
@Override
@@ -71,8 +78,8 @@ public class AndroidLauncher extends AndroidApplication{
}
@Override
- public ClassLoader loadJar(Fi jar, String mainClass) throws Exception{
- return new DexClassLoader(jar.file().getPath(), getFilesDir().getPath(), null, getClassLoader());
+ public ClassLoader loadJar(Fi jar, ClassLoader parent) throws Exception{
+ return new DexClassLoader(jar.file().getPath(), getFilesDir().getPath(), null, parent);
}
@Override
@@ -165,9 +172,20 @@ public class AndroidLauncher extends AndroidApplication{
try{
//new external folder
- Fi data = Core.files.absolute(getContext().getExternalFilesDir(null).getAbsolutePath());
+ Fi data = Core.files.absolute(((Context)this).getExternalFilesDir(null).getAbsolutePath());
Core.settings.setDataDirectory(data);
+ //delete unused cache folder to free up space
+ try{
+ Fi cache = Core.settings.getDataDirectory().child("cache");
+ if(cache.exists()){
+ cache.deleteDirectory();
+ }
+ }catch(Throwable t){
+ Log.err("Failed to delete cached folder", t);
+ }
+
+
//move to internal storage if there's no file indicating that it moved
if(!Core.files.local("files_moved").exists()){
Log.info("Moving files to external storage...");
@@ -209,6 +227,24 @@ public class AndroidLauncher extends AndroidApplication{
}
}
+ @Override
+ public void onResume(){
+ super.onResume();
+
+ //TODO enable once GPGS is set up on the GP console
+ if(false && BuildConfig.FLAVOR.equals("gp")){
+ try{
+ if(gpService == null){
+ serviceClass = Class.forName("mindustry.android.GPGameService");
+ gpService = serviceClass.getConstructor().newInstance();
+ }
+ serviceClass.getMethod("onResume", Context.class).invoke(gpService, this);
+ }catch(Exception e){
+ Log.err("Failed to update Google Play Services", e);
+ }
+ }
+ }
+
private void checkFiles(Intent intent){
try{
Uri uri = intent.getData();
diff --git a/android/src/mindustry/android/AndroidRhinoContext.java b/android/src/mindustry/android/AndroidRhinoContext.java
index d3e80c7427..0147f118b8 100644
--- a/android/src/mindustry/android/AndroidRhinoContext.java
+++ b/android/src/mindustry/android/AndroidRhinoContext.java
@@ -175,7 +175,7 @@ public class AndroidRhinoContext{
}catch(IOException e){
e.printStackTrace();
}
- android.content.Context context = ((AndroidApplication) Core.app).getContext();
+ android.content.Context context = (android.content.Context)((AndroidApplication)Core.app);
return new DexClassLoader(dexFile.getPath(), VERSION.SDK_INT >= 21 ? context.getCodeCacheDir().getPath() : context.getCacheDir().getAbsolutePath(), null, getParent()).loadClass(name);
}
diff --git a/android/srcgp/mindustry/android/GPGameService.java b/android/srcgp/mindustry/android/GPGameService.java
new file mode 100644
index 0000000000..9a64b6f3d4
--- /dev/null
+++ b/android/srcgp/mindustry/android/GPGameService.java
@@ -0,0 +1,40 @@
+package mindustry.android;
+
+import android.content.*;
+import arc.util.*;
+import com.google.android.gms.auth.api.signin.*;
+import com.google.android.gms.games.*;
+import mindustry.service.*;
+
+public class GPGameService extends GameService{
+ private GoogleSignInAccount account;
+
+ public void onResume(Context context){
+ Log.info("[GooglePlayService] Resuming.");
+
+ GoogleSignInAccount current = GoogleSignIn.getLastSignedInAccount(context);
+
+ GoogleSignInOptions options =
+ new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN)
+ .requestScopes(Games.SCOPE_GAMES_SNAPSHOTS)
+ .build();
+
+ if(GoogleSignIn.hasPermissions(current, options.getScopeArray())){
+ this.account = current;
+ Log.info("Already signed in to Google Play Games.");
+ }else{
+ GoogleSignIn.getClient(context, options).silentSignIn().addOnCompleteListener(complete -> {
+ if(!complete.isSuccessful()){
+ if(complete.getException() != null){
+ Log.err("Failed to sign in to Google Play Games.", complete.getException());
+ }else{
+ Log.warn("Failed to sign in to Google Play Games.");
+ }
+ }else{
+ this.account = complete.getResult();
+ Log.info("Signed in to Google Play Games.");
+ }
+ });
+ }
+ }
+}
diff --git a/annotations/src/main/java/mindustry/annotations/Annotations.java b/annotations/src/main/java/mindustry/annotations/Annotations.java
index 88bd457e05..9960f673eb 100644
--- a/annotations/src/main/java/mindustry/annotations/Annotations.java
+++ b/annotations/src/main/java/mindustry/annotations/Annotations.java
@@ -118,7 +118,7 @@ public class Annotations{
/**
* The region name to load. Variables can be used:
* "@" -> block name
- * "$size" -> block size
+ * "@size" -> block size
* "#" "#1" "#2" -> index number, for arrays
* */
String value();
@@ -177,12 +177,12 @@ public class Annotations{
//region remote
public enum PacketPriority{
+ /** Does not get handled unless client is connected. */
+ low,
/** Gets put in a queue and processed if not connected. */
normal,
/** Gets handled immediately, regardless of connection status. */
high,
- /** Does not get handled unless client is connected. */
- low
}
/** A set of two booleans, one specifying server and one specifying client. */
diff --git a/annotations/src/main/java/mindustry/annotations/BaseProcessor.java b/annotations/src/main/java/mindustry/annotations/BaseProcessor.java
index 74cd4e8bde..310f8e2906 100644
--- a/annotations/src/main/java/mindustry/annotations/BaseProcessor.java
+++ b/annotations/src/main/java/mindustry/annotations/BaseProcessor.java
@@ -2,15 +2,10 @@ package mindustry.annotations;
import arc.files.*;
import arc.struct.*;
-import arc.util.Log;
-import arc.util.Log.*;
import arc.util.*;
+import arc.util.Log.*;
import com.squareup.javapoet.*;
import com.sun.source.util.*;
-import com.sun.tools.javac.model.*;
-import com.sun.tools.javac.processing.*;
-import com.sun.tools.javac.tree.*;
-import com.sun.tools.javac.util.*;
import mindustry.annotations.util.*;
import javax.annotation.processing.*;
@@ -22,7 +17,6 @@ import javax.tools.Diagnostic.*;
import javax.tools.*;
import java.io.*;
import java.lang.annotation.*;
-import java.util.List;
import java.util.*;
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@@ -31,19 +25,16 @@ public abstract class BaseProcessor extends AbstractProcessor{
public static final String packageName = "mindustry.gen";
public static Types typeu;
- public static JavacElements elementu;
+ public static Elements elementu;
public static Filer filer;
public static Messager messager;
public static Trees trees;
- public static TreeMaker maker;
protected int round;
protected int rounds = 1;
protected RoundEnvironment env;
protected Fi rootDirectory;
- protected Context context;
-
public static String getMethodName(Element element){
return ((TypeElement)element.getEnclosingElement()).getQualifiedName().toString() + "." + element.getSimpleName();
}
@@ -186,7 +177,7 @@ public abstract class BaseProcessor extends AbstractProcessor{
Log.err("[CODEGEN ERROR] " + message + ": " + elem);
}
- public void err(String message, Selement elem){
+ public static void err(String message, Selement elem){
err(message, elem.e);
}
@@ -194,15 +185,11 @@ public abstract class BaseProcessor extends AbstractProcessor{
public synchronized void init(ProcessingEnvironment env){
super.init(env);
- JavacProcessingEnvironment javacProcessingEnv = (JavacProcessingEnvironment)env;
-
trees = Trees.instance(env);
typeu = env.getTypeUtils();
- elementu = javacProcessingEnv.getElementUtils();
+ elementu = env.getElementUtils();
filer = env.getFiler();
messager = env.getMessager();
- context = ((JavacProcessingEnvironment)env).getContext();
- maker = TreeMaker.instance(javacProcessingEnv.getContext());
Log.level = LogLevel.info;
diff --git a/annotations/src/main/java/mindustry/annotations/entity/EntityProcess.java b/annotations/src/main/java/mindustry/annotations/entity/EntityProcess.java
index 6067b986b5..9612018e7b 100644
--- a/annotations/src/main/java/mindustry/annotations/entity/EntityProcess.java
+++ b/annotations/src/main/java/mindustry/annotations/entity/EntityProcess.java
@@ -132,6 +132,7 @@ public class EntityProcess extends BaseProcessor{
.build())).addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT).build());
}
+ //generate interface getters and setters for all "standard" fields
for(Svar field : component.fields().select(e -> !e.is(Modifier.STATIC) && !e.is(Modifier.PRIVATE) && !e.has(Import.class))){
String cname = field.name();
@@ -674,11 +675,28 @@ public class EntityProcess extends BaseProcessor{
//build mapping class for sync IDs
TypeSpec.Builder idBuilder = TypeSpec.classBuilder("EntityMapping").addModifiers(Modifier.PUBLIC)
.addField(FieldSpec.builder(TypeName.get(Prov[].class), "idMap", Modifier.PUBLIC, Modifier.STATIC).initializer("new Prov[256]").build())
+
.addField(FieldSpec.builder(ParameterizedTypeName.get(ClassName.get(ObjectMap.class),
tname(String.class), tname(Prov.class)),
"nameMap", Modifier.PUBLIC, Modifier.STATIC).initializer("new ObjectMap<>()").build())
+
+ .addField(FieldSpec.builder(ParameterizedTypeName.get(ClassName.get(IntMap.class), tname(String.class)),
+ "customIdMap", Modifier.PUBLIC, Modifier.STATIC).initializer("new IntMap<>()").build())
+
+ .addMethod(MethodSpec.methodBuilder("register").addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .returns(TypeName.get(int.class))
+ .addParameter(String.class, "name").addParameter(Prov.class, "constructor")
+ .addStatement("int next = arc.util.Structs.indexOf(idMap, v -> v == null)")
+ .addStatement("idMap[next] = constructor")
+ .addStatement("nameMap.put(name, constructor)")
+ .addStatement("customIdMap.put(next, name)")
+ .addStatement("return next")
+ .addJavadoc("Use this method for obtaining a classId for custom modded unit types. Only call this once for each type. Modded types should return this id in their overridden classId method.")
+ .build())
+
.addMethod(MethodSpec.methodBuilder("map").addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(TypeName.get(Prov.class)).addParameter(int.class, "id").addStatement("return idMap[id]").build())
+
.addMethod(MethodSpec.methodBuilder("map").addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(TypeName.get(Prov.class)).addParameter(String.class, "name").addStatement("return nameMap.get(name)").build());
@@ -707,11 +725,6 @@ public class EntityProcess extends BaseProcessor{
}else{
//round 3: generate actual classes and implement interfaces
- //write base classes
- for(TypeSpec.Builder b : baseClasses){
- write(b, imports.asArray());
- }
-
//implement each definition
for(EntityDefinition def : definitions){
@@ -736,6 +749,12 @@ public class EntityProcess extends BaseProcessor{
if(def.legacy) continue;
+ @Nullable TypeSpec.Builder superclass = null;
+
+ if(def.extend != null){
+ superclass = baseClasses.find(b -> (packageName + "." + Reflect.get(b, "name")).equals(def.extend.toString()));
+ }
+
//generate getter/setter for each method
for(Smethod method : inter.methods()){
String var = method.name();
@@ -743,14 +762,36 @@ public class EntityProcess extends BaseProcessor{
//make sure it's a real variable AND that the component doesn't already implement it somewhere with custom logic
if(field == null || methodNames.contains(method.simpleString())) continue;
+ MethodSpec result = null;
+
//getter
if(!method.isVoid()){
- def.builder.addMethod(MethodSpec.overriding(method.e).addStatement("return " + var).build());
+ result = MethodSpec.overriding(method.e).addStatement("return " + var).build();
}
//setter
if(method.isVoid() && !Seq.with(field.annotations).contains(f -> f.type.toString().equals("@mindustry.annotations.Annotations.ReadOnly"))){
- def.builder.addMethod(MethodSpec.overriding(method.e).addStatement("this." + var + " = " + var).build());
+ result = MethodSpec.overriding(method.e).addStatement("this." + var + " = " + var).build();
+ }
+
+ //add getter/setter to parent class, if possible. when this happens, skip adding getters setters *here* because they are defined in the superclass.
+ if(result != null && superclass != null){
+ FieldSpec superField = Seq.with(superclass.fieldSpecs).find(f -> f.name.equals(var));
+
+ //found the right field, try to check for the method already existing now
+ if(superField != null){
+ MethodSpec fr = result;
+ MethodSpec targetMethod = Seq.with(superclass.methodSpecs).find(m -> m.name.equals(var) && m.returnType.equals(fr.returnType));
+ //if the method isn't added yet, add it. in any case, skip.
+ if(targetMethod == null){
+ superclass.addMethod(result);
+ }
+ continue;
+ }
+ }
+
+ if(result != null){
+ def.builder.addMethod(result);
}
}
}
@@ -758,9 +799,16 @@ public class EntityProcess extends BaseProcessor{
write(def.builder, imports.asArray());
}
+ //write base classes last
+ for(TypeSpec.Builder b : baseClasses){
+ write(b, imports.asArray());
+ }
+
+ //TODO nulls were an awful idea
//store nulls
TypeSpec.Builder nullsBuilder = TypeSpec.classBuilder("Nulls").addModifiers(Modifier.PUBLIC).addModifiers(Modifier.FINAL);
- ObjectSet nullList = ObjectSet.with("unit", "blockUnit");
+ //TODO should be dynamic
+ ObjectSet nullList = ObjectSet.with("unit");
//create mock types of all components
for(Stype interf : allInterfaces){
@@ -918,7 +966,7 @@ public class EntityProcess extends BaseProcessor{
}
String createName(Selement> elem){
- Seq comps = types(elem.annotation(EntityDef.class), EntityDef::value).map(this::interfaceToComp);;
+ Seq comps = types(elem.annotation(EntityDef.class), EntityDef::value).map(this::interfaceToComp);
comps.sortComparing(Selement::name);
return comps.toString("", s -> s.name().replace("Comp", ""));
}
diff --git a/annotations/src/main/java/mindustry/annotations/impl/AssetsProcess.java b/annotations/src/main/java/mindustry/annotations/impl/AssetsProcess.java
index 0b5620c11d..c144abc534 100644
--- a/annotations/src/main/java/mindustry/annotations/impl/AssetsProcess.java
+++ b/annotations/src/main/java/mindustry/annotations/impl/AssetsProcess.java
@@ -43,7 +43,7 @@ public class AssetsProcess extends BaseProcessor{
texIcons.each((key, val) -> {
String[] split = val.split("\\|");
- String name = Strings.kebabToCamel(split[1]).replace("Medium", "").replace("Icon", "");
+ String name = Strings.kebabToCamel(split[1]).replace("Medium", "").replace("Icon", "").replace("Ui", "");
if(SourceVersion.isKeyword(name) || name.equals("char")) name += "i";
ichtype.addField(FieldSpec.builder(char.class, name, Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).addJavadoc(String.format("\\u%04x", Integer.parseInt(key))).initializer("'" + ((char)Integer.parseInt(key)) + "'").build());
diff --git a/annotations/src/main/java/mindustry/annotations/impl/CallSuperProcess.java b/annotations/src/main/java/mindustry/annotations/impl/CallSuperProcess.java
deleted file mode 100644
index e28ecf13a0..0000000000
--- a/annotations/src/main/java/mindustry/annotations/impl/CallSuperProcess.java
+++ /dev/null
@@ -1,154 +0,0 @@
-package mindustry.annotations.impl;
-
-import com.sun.source.tree.*;
-import com.sun.source.util.*;
-import com.sun.tools.javac.code.Scope;
-import com.sun.tools.javac.code.*;
-import com.sun.tools.javac.code.Symbol.*;
-import com.sun.tools.javac.code.Type.*;
-import com.sun.tools.javac.tree.*;
-import com.sun.tools.javac.tree.JCTree.*;
-import mindustry.annotations.Annotations.*;
-
-import javax.annotation.processing.*;
-import javax.lang.model.*;
-import javax.lang.model.element.*;
-import javax.tools.Diagnostic.*;
-import java.lang.annotation.*;
-import java.util.*;
-
-@SupportedAnnotationTypes({"java.lang.Override"})
-public class CallSuperProcess extends AbstractProcessor{
- private Trees trees;
-
- @Override
- public void init(ProcessingEnvironment pe){
- super.init(pe);
- trees = Trees.instance(pe);
- }
-
- @Override
- public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv){
- for(Element e : roundEnv.getElementsAnnotatedWith(Override.class)){
- if(e.getAnnotation(OverrideCallSuper.class) != null) return false;
-
- CodeAnalyzerTreeScanner codeScanner = new CodeAnalyzerTreeScanner();
- codeScanner.methodName = e.getSimpleName().toString();
-
- TreePath tp = trees.getPath(e.getEnclosingElement());
- codeScanner.scan(tp, trees);
-
- if(codeScanner.callSuperUsed){
- List list = codeScanner.method.getBody().getStatements();
-
- if(!doesCallSuper(list, codeScanner.methodName)){
- processingEnv.getMessager().printMessage(Kind.ERROR, "Overriding method '" + codeScanner.methodName + "' must explicitly call super method from its parent class.", e);
- }
- }
- }
-
- return false;
- }
-
- private boolean doesCallSuper(List list, String methodName){
- for(Object object : list){
- if(object instanceof JCTree.JCExpressionStatement){
- JCTree.JCExpressionStatement expr = (JCExpressionStatement)object;
- String exprString = expr.toString();
- if(exprString.startsWith("super." + methodName) && exprString.endsWith(");")) return true;
- }
- }
-
- return false;
- }
-
- @Override
- public SourceVersion getSupportedSourceVersion(){
- return SourceVersion.RELEASE_8;
- }
-
- static class CodeAnalyzerTreeScanner extends TreePathScanner