diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index bfdc7f59f1..22b6097ec1 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -1,6 +1,10 @@
---
name: Bug report
about: Create a report to help fix an issue.
+title: ''
+labels: ''
+assignees: ''
+
---
**Platform**: *Android/iOS/Mac/Windows/Linux*
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index c270d78354..7a08062e1d 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -1,7 +1,11 @@
---
name: Feature request
-about: Do not make a new issue for feature requests! Instead, post it on FeatHub, see the README.
+about: Do not make a new issue for feature requests! Instead, post in the suggestions
+ repository. See the README.
+title: ''
+labels: invalid
+assignees: ''
---
-**Do not make a new issue for feature requests!** Instead, post it on FeatHub: https://feathub.com/Anuken/Mindustry
+**Do not make a new issue for feature requests!** Instead, post it in suggestions repository: https://github.com/Anuken/Mindustry-Suggestions/issues/new/choose
diff --git a/.github/Mindustry-CodeStyle-IJ.xml b/.github/Mindustry-CodeStyle-IJ.xml
index 94486ff95b..e63661b6d3 100644
--- a/.github/Mindustry-CodeStyle-IJ.xml
+++ b/.github/Mindustry-CodeStyle-IJ.xml
@@ -82,4 +82,4 @@
-
\ No newline at end of file
+
diff --git a/.gitignore b/.gitignore
index 10fca2d7f0..e9a73fdffd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,10 +18,12 @@ logs/
/annotations/out/
/net/build/
/tools/build/
+/core/build/
/tests/build/
/server/build/
changelog
saves/
+/core/assets-raw/fontgen/out/
core/assets/saves/
/core/assets/saves/
steam_appid.txt
@@ -30,6 +32,7 @@ steam_appid.txt
/android/assets/mindustry-maps/
/android/assets/mindustry-saves/
/core/assets/gifexport/
+/annotations/src/main/resources/META-INF/services
/core/assets/version.properties
/core/assets/locales
/ios/src/mindustry/gen/
@@ -40,6 +43,8 @@ config/
changelog
*.gif
/core/assets/saves/
+/out/
+/core/assets-raw/fontgen/out/
version.properties
diff --git a/README.md b/README.md
index 2f0cab8ff3..03152f0cf6 100644
--- a/README.md
+++ b/README.md
@@ -21,13 +21,15 @@ First, make sure you have [JDK 8](https://adoptopenjdk.net/) installed. Open a t
#### Windows
-_Running:_ `gradlew desktop:run`
-_Building:_ `gradlew desktop:dist`
+_Running:_ `gradlew.bat desktop:run`
+_Building:_ `gradlew.bat desktop:dist`
+_Sprite Packing:_ `gradlew.bat tools:pack`
#### Linux/Mac OS
_Running:_ `./gradlew desktop:run`
-_Building:_ `./gradlew desktop:dist`
+_Building:_ `./gradlew desktop:dist`
+_Sprite Packing:_ `./gradlew tools:pack`
#### Server
@@ -49,6 +51,10 @@ If the terminal returns `Permission denied` or `Command not found` on Mac/Linux,
Gradle may take up to several minutes to download files. Be patient.
After building, the output .JAR file should be in `/desktop/build/libs/Mindustry.jar` for desktop builds, and in `/server/build/libs/server-release.jar` for server builds.
+### Feature Requests
+
+Post feature requests and feedback [here](https://github.com/Anuken/Mindustry-Suggestions/issues/new/choose).
+
### Downloads
[
](https://f-droid.org/packages/io.anuke.mindustry/)
-
-### Feature Requests
-
-[](https://feathub.com/Anuken/Mindustry)
diff --git a/SERVERLIST.md b/SERVERLIST.md
new file mode 100644
index 0000000000..e4d2b6a284
--- /dev/null
+++ b/SERVERLIST.md
@@ -0,0 +1,26 @@
+### Adding a server to the list
+
+Mindustry now has a public list of servers that everyone can see and connect to.
+This is done by letting clients `GET` a [JSON list of servers](https://github.com/Anuken/Mindustry/blob/master/servers.json) in this repository.
+
+You may want to add your server to this list. The steps for getting this done are as follows:
+
+1. **Ensure your server is properly moderated.** For the most part, this applies to survival servers, but PvP servers can be affected as well.
+You'll need to either hire some moderators, or make use of (currently non-existent) anti-grief and anti-curse plugins.
+*Consider enabling a rate limit:* `config messageRateLimit 2` will make it so that players can only send messages every 2 seconds, for example.
+2. **Set an appropriate MOTD, name and description.** This is set with `config `. "Appropriate" means that:
+ - Your name or description must reflect the type of server you're hosting.
+ Since new players may be exposed to the server list early on, put in a phrase like "Co-op survival" or "PvP" so players know what they're getting into. Yes, this is also displayed in the server mode info text, but having extra info in the name doesn't hurt.
+ - Make sure players know where to refer to for server support. It should be fairly clear that the server owner is not me, but you.
+ - Try to be professional in your text; use common sense.
+3. **Get some good maps.** *(optional, but highly recommended)*. Add some maps to your server and set the map rotation to custom-only. You can get maps from the Steam workshop by subscribing and exporting them; using the `#maps` channel on Discord is also an option.
+4. **Check your server configuration.** *(optional)* I would recommend adding a message rate limit of 1 second (`config messageRateLimit 1`), and disabling connect/disconnect messages to reduce spam (`config showConnectMessages false`).
+5. Finally, **submit a pull request** to add your server's IP to the list.
+This should be fairly straightforward: Press the edit button on the [server file](https://github.com/Anuken/Mindustry/blob/master/servers.json), then add a JSON object with a single key, indicating your server address.
+For example, if your server address is `google.com`, you would add a comma after the last entry and insert:
+```json
+ {
+ "address": "google.com"
+ }
+```
+Then, press the *'submit pull request'* button and I'll take a look at your server. If I have any issues with it, I'll let you know in the PR comments.
diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml
index b096e11a41..05e0d30698 100644
--- a/android/AndroidManifest.xml
+++ b/android/AndroidManifest.xml
@@ -3,6 +3,7 @@
package="io.anuke.mindustry">
+
diff --git a/android/build.gradle b/android/build.gradle
index 015656f543..e82a957ba5 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -162,4 +162,4 @@ task run(type: Exec){
def adb = path + "/platform-tools/adb"
commandLine "$adb", 'shell', 'am', 'start', '-n', 'io.anuke.mindustry/mindustry.android.AndroidLauncher'
-}
\ No newline at end of file
+}
diff --git a/android/res/mipmap-anydpi-v26/ic_launcher.xml b/android/res/mipmap-anydpi-v26/ic_launcher.xml
index 4ae7d12378..cb73a95729 100644
--- a/android/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/android/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -2,4 +2,4 @@
-
\ No newline at end of file
+
diff --git a/android/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/res/mipmap-anydpi-v26/ic_launcher_round.xml
index 4ae7d12378..cb73a95729 100644
--- a/android/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/android/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -2,4 +2,4 @@
-
\ No newline at end of file
+
diff --git a/android/res/values-v21/styles.xml b/android/res/values-v21/styles.xml
index 699b6a05d7..e1bed057f8 100644
--- a/android/res/values-v21/styles.xml
+++ b/android/res/values-v21/styles.xml
@@ -8,4 +8,4 @@
- @null
- true
-
\ No newline at end of file
+
diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml
index b77280e581..d40cf3accd 100644
--- a/android/res/values/strings.xml
+++ b/android/res/values/strings.xml
@@ -3,4 +3,4 @@
Mindustry
-
\ No newline at end of file
+
diff --git a/android/res/xml/backup_rules.xml b/android/res/xml/backup_rules.xml
index bfa7569b2e..072948235f 100644
--- a/android/res/xml/backup_rules.xml
+++ b/android/res/xml/backup_rules.xml
@@ -1,4 +1,4 @@
-
\ No newline at end of file
+
diff --git a/android/src/mindustry/android/AndroidLauncher.java b/android/src/mindustry/android/AndroidLauncher.java
index be35875dad..1c233438ff 100644
--- a/android/src/mindustry/android/AndroidLauncher.java
+++ b/android/src/mindustry/android/AndroidLauncher.java
@@ -19,6 +19,7 @@ import arc.util.serialization.*;
import mindustry.*;
import mindustry.game.Saves.*;
import mindustry.io.*;
+import mindustry.net.*;
import mindustry.ui.dialogs.*;
import java.io.*;
@@ -80,7 +81,7 @@ public class AndroidLauncher extends AndroidApplication{
if(VERSION.SDK_INT >= VERSION_CODES.Q){
Intent intent = new Intent(open ? Intent.ACTION_OPEN_DOCUMENT : Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType(extension.equals("zip") ? "application/zip" : "*/*");
+ intent.setType(extension.equals("zip") && !open ? "application/zip" : "*/*");
addResultListener(i -> startActivityForResult(intent, i), (code, in) -> {
if(code == Activity.RESULT_OK && in != null && in.getData() != null){
Uri uri = in.getData();
@@ -143,16 +144,17 @@ public class AndroidLauncher extends AndroidApplication{
}, new AndroidApplicationConfiguration(){{
useImmersiveMode = true;
- depth = 0;
hideStatusBar = true;
- //errorHandler = ModCrashHandler::handle;
+ errorHandler = CrashSender::log;
}});
checkFiles(getIntent());
+
//new external folder
Fi data = Core.files.absolute(getContext().getExternalFilesDir(null).getAbsolutePath());
+ Core.settings.setDataDirectory(data);
- //moved to internal storage if there's no file indicating that it moved
+ //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...");
@@ -160,17 +162,16 @@ public class AndroidLauncher extends AndroidApplication{
//current local storage folder
Fi src = Core.files.absolute(Core.files.getLocalStoragePath());
for(Fi fi : src.list()){
- fi.copyTo(data.child(fi.name()));
+ fi.copyTo(data);
}
//create marker
Core.files.local("files_moved").writeString("files moved to " + data);
+ Core.files.local("files_moved_103").writeString("files moved again");
Log.info("Files moved.");
}catch(Throwable t){
Log.err("Failed to move files!");
t.printStackTrace();
}
- }else{
- Core.settings.setDataDirectory(data);
}
}
diff --git a/annotations/build.gradle b/annotations/build.gradle
index d894ed0abf..d87bfffb22 100644
--- a/annotations/build.gradle
+++ b/annotations/build.gradle
@@ -2,5 +2,4 @@ apply plugin: "java"
sourceCompatibility = 1.8
sourceSets.main.java.srcDirs = ["src/main/java/"]
-sourceSets.main.resources.srcDirs = ["src/main/resources/"]
-
+sourceSets.main.resources.srcDirs = ["src/main/resources/"]
\ No newline at end of file
diff --git a/annotations/src/main/java/mindustry/annotations/Annotations.java b/annotations/src/main/java/mindustry/annotations/Annotations.java
index 8124930825..e286ce6694 100644
--- a/annotations/src/main/java/mindustry/annotations/Annotations.java
+++ b/annotations/src/main/java/mindustry/annotations/Annotations.java
@@ -3,10 +3,100 @@ package mindustry.annotations;
import java.lang.annotation.*;
public class Annotations{
+ //region entity interfaces
+
+ public enum DrawLayer{
+ floor,
+ floorOver,
+ groundShadows,
+ groundUnder,
+ ground,
+ flyingShadows,
+ flying,
+ bullets,
+ effects,
+ names,
+ }
+
+ /** Indicates that a method overrides other methods. */
+ @Target({ElementType.METHOD})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Replace{
+ }
+
+ /** Indicates that a component field is imported from other components. */
+ @Target({ElementType.FIELD})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Import{
+ }
+
+ /** Indicates that a component field is read-only. */
+ @Target({ElementType.FIELD, ElementType.METHOD})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ReadOnly{
+ }
+
+ /** Indicates multiple inheritance on a component type. */
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Component{
+ }
+
+ /** Indicates that a method is implemented by the annotation processor. */
+ @Target(ElementType.METHOD)
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface InternalImpl{
+ }
+
+ /** Indicates priority of a method in an entity. Methods with higher priority are done last. */
+ @Target(ElementType.METHOD)
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MethodPriority{
+ float value();
+ }
+
+ /** Indicates that a component def is present on all entities. */
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BaseComponent{
+ }
+
+ /** Creates a group that only examines entities that have all the components listed. */
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface GroupDef{
+ Class[] value();
+ Class[] collide() default {};
+ boolean spatial() default false;
+ boolean mapping() default false;
+ }
+
+ /** Indicates an entity definition. */
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EntityDef{
+ /** List of component interfaces */
+ Class[] value();
+ /** Whether the class is final */
+ boolean isFinal() default true;
+ /** If true, entities are recycled. */
+ boolean pooled() default false;
+ /** Whether to serialize (makes the serialize method return this value) */
+ boolean serialize() default true;
+ /** Whether to generate IO code */
+ boolean genio() default true;
+ }
+
+ /** Indicates an internal interface for entity components. */
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EntityInterface{
+ }
+
+ //endregion
+ //region misc. utility
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
- public @interface StyleDefaults {
+ public @interface StyleDefaults{
}
/** Indicates that a method should always call its super version. */
@@ -16,10 +106,10 @@ public class Annotations{
}
- /** Annotation that allows overriding CallSuper annotation. To be used on method that overrides method with CallSuper annotation from parent class.*/
+ /** Annotation that allows overriding CallSuper annotation. To be used on method that overrides method with CallSuper annotation from parent class. */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
- public @interface OverrideCallSuper {
+ public @interface OverrideCallSuper{
}
/** Marks a class as serializable. */
@@ -29,6 +119,9 @@ public class Annotations{
}
+ //endregion
+ //region struct
+
/** Marks a class as a special value type struct. Class name must end in 'Struct'. */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@@ -44,6 +137,9 @@ public class Annotations{
int value();
}
+ //endregion
+ //region remote
+
public enum PacketPriority{
/** Gets put in a queue and processed if not connected. */
normal,
@@ -122,20 +218,10 @@ public class Annotations{
* This method must return void and have two parameters, the first being of type {@link java.nio.ByteBuffer} and the second
* being the type returned by {@link #value()}.
*/
- @Target(ElementType.METHOD)
+ @Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
- public @interface WriteClass{
- Class> value();
+ public @interface TypeIOHandler{
}
- /**
- * Specifies that this method will be used to read classes of the type returned by {@link #value()}.
- * This method must return the type returned by {@link #value()},
- * and have one parameter, being of type {@link java.nio.ByteBuffer}.
- */
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.SOURCE)
- public @interface ReadClass{
- Class> value();
- }
+ //endregion
}
diff --git a/annotations/src/main/java/mindustry/annotations/AssetsAnnotationProcessor.java b/annotations/src/main/java/mindustry/annotations/AssetsAnnotationProcessor.java
deleted file mode 100644
index 3ccb3d0729..0000000000
--- a/annotations/src/main/java/mindustry/annotations/AssetsAnnotationProcessor.java
+++ /dev/null
@@ -1,173 +0,0 @@
-package mindustry.annotations;
-
-import com.squareup.javapoet.*;
-import mindustry.annotations.Annotations.*;
-
-import javax.annotation.processing.*;
-import javax.lang.model.*;
-import javax.lang.model.element.*;
-import javax.tools.Diagnostic.*;
-import javax.tools.*;
-import java.nio.file.*;
-import java.util.*;
-
-@SupportedSourceVersion(SourceVersion.RELEASE_8)
-@SupportedAnnotationTypes("mindustry.annotations.Annotations.StyleDefaults")
-public class AssetsAnnotationProcessor extends AbstractProcessor{
- /** Name of the base package to put all the generated classes. */
- private static final String packageName = "mindustry.gen";
- private String path;
- private int round;
-
- @Override
- public synchronized void init(ProcessingEnvironment processingEnv){
- super.init(processingEnv);
- //put all relevant utils into utils class
- Utils.typeUtils = processingEnv.getTypeUtils();
- Utils.elementUtils = processingEnv.getElementUtils();
- Utils.filer = processingEnv.getFiler();
- Utils.messager = processingEnv.getMessager();
- }
-
- @Override
- public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv){
- if(round++ != 0) return false; //only process 1 round
-
- try{
- path = Paths.get(Utils.filer.createResource(StandardLocation.CLASS_OUTPUT, "no", "no")
- .toUri().toURL().toString().substring(System.getProperty("os.name").contains("Windows") ? 6 : "file:".length()))
- .getParent().getParent().getParent().getParent().getParent().getParent().toString();
- path = path.replace("%20", " ");
-
- processSounds("Sounds", path + "/assets/sounds", "arc.audio.Sound");
- processSounds("Musics", path + "/assets/music", "arc.audio.Music");
- processUI(roundEnv.getElementsAnnotatedWith(StyleDefaults.class));
-
- return true;
- }catch(Exception e){
- e.printStackTrace();
- throw new RuntimeException(e);
- }
- }
-
- void processUI(Set extends Element> elements) throws Exception{
- String[] iconSizes = {"small", "smaller", "tiny"};
-
- TypeSpec.Builder type = TypeSpec.classBuilder("Tex").addModifiers(Modifier.PUBLIC);
- TypeSpec.Builder ictype = TypeSpec.classBuilder("Icon").addModifiers(Modifier.PUBLIC);
- MethodSpec.Builder load = MethodSpec.methodBuilder("load").addModifiers(Modifier.PUBLIC, Modifier.STATIC);
- MethodSpec.Builder loadStyles = MethodSpec.methodBuilder("loadStyles").addModifiers(Modifier.PUBLIC, Modifier.STATIC);
- MethodSpec.Builder icload = MethodSpec.methodBuilder("load").addModifiers(Modifier.PUBLIC, Modifier.STATIC);
- String resources = path + "/assets-raw/sprites/ui";
- Files.walk(Paths.get(resources)).forEach(p -> {
- if(Files.isDirectory(p) || p.getFileName().toString().equals(".DS_Store")) return;
-
- String filename = p.getFileName().toString();
- filename = filename.substring(0, filename.indexOf("."));
-
- ArrayList names = new ArrayList<>();
- names.add("");
- if(filename.contains("icon")){
- names.addAll(Arrays.asList(iconSizes));
- }
-
- for(String suffix : names){
- suffix = suffix.isEmpty() ? "" : "-" + suffix;
-
- String sfilen = filename + suffix;
- String dtype = p.getFileName().toString().endsWith(".9.png") ? "arc.scene.style.NinePatchDrawable" : "arc.scene.style.TextureRegionDrawable";
-
- String varname = capitalize(sfilen);
- TypeSpec.Builder ttype = type;
- MethodSpec.Builder tload = load;
- if(varname.startsWith("icon")){
- varname = varname.substring("icon".length());
- varname = Character.toLowerCase(varname.charAt(0)) + varname.substring(1);
- ttype = ictype;
- tload = icload;
- if(SourceVersion.isKeyword(varname)) varname += "i";
- }
-
- if(SourceVersion.isKeyword(varname)) varname += "s";
-
- ttype.addField(ClassName.bestGuess(dtype), varname, Modifier.STATIC, Modifier.PUBLIC);
- tload.addStatement(varname + " = ("+dtype+")arc.Core.atlas.drawable($S)", sfilen);
- }
- });
-
- for(Element elem : elements){
- TypeElement t = (TypeElement)elem;
- t.getEnclosedElements().stream().filter(e -> e.getKind() == ElementKind.FIELD).forEach(field -> {
- String fname = field.getSimpleName().toString();
- if(fname.startsWith("default")){
- loadStyles.addStatement("arc.Core.scene.addStyle(" + field.asType().toString() + ".class, mindustry.ui.Styles." + fname + ")");
- }
- });
- }
-
- ictype.addMethod(icload.build());
- JavaFile.builder(packageName, ictype.build()).build().writeTo(Utils.filer);
-
- type.addMethod(load.build());
- type.addMethod(loadStyles.build());
- JavaFile.builder(packageName, type.build()).build().writeTo(Utils.filer);
- }
-
- void processSounds(String classname, String path, String rtype) throws Exception{
- TypeSpec.Builder type = TypeSpec.classBuilder(classname).addModifiers(Modifier.PUBLIC);
- MethodSpec.Builder dispose = MethodSpec.methodBuilder("dispose").addModifiers(Modifier.PUBLIC, Modifier.STATIC);
- MethodSpec.Builder loadBegin = MethodSpec.methodBuilder("load").addModifiers(Modifier.PUBLIC, Modifier.STATIC);
-
- HashSet names = new HashSet<>();
- Files.list(Paths.get(path)).forEach(p -> {
- String fname = p.getFileName().toString();
- String name = p.getFileName().toString();
- name = name.substring(0, name.indexOf("."));
-
- if(names.contains(name)){
- Utils.messager.printMessage(Kind.ERROR, "Duplicate file name: " + p.toString() + "!");
- }else{
- names.add(name);
- }
-
- if(SourceVersion.isKeyword(name)){
- name = name + "s";
- }
-
- String filepath = path.substring(path.lastIndexOf("/") + 1) + "/" + fname;
-
- String filename = "arc.Core.app.getType() != arc.Application.ApplicationType.iOS ? \"" + filepath + "\" : \"" + filepath.replace(".ogg", ".mp3")+"\"";
-
- loadBegin.addStatement("arc.Core.assets.load("+filename +", "+rtype+".class).loaded = a -> " + name + " = ("+rtype+")a", filepath, filepath.replace(".ogg", ".mp3"));
-
- dispose.addStatement("arc.Core.assets.unload(" + filename + ")");
- dispose.addStatement(name + " = null");
- type.addField(FieldSpec.builder(ClassName.bestGuess(rtype), name, Modifier.STATIC, Modifier.PUBLIC).initializer("new arc.audio.mock.Mock" + rtype.substring(rtype.lastIndexOf(".") + 1)+ "()").build());
- });
-
- if(classname.equals("Sounds")){
- type.addField(FieldSpec.builder(ClassName.bestGuess(rtype), "none", Modifier.STATIC, Modifier.PUBLIC).initializer("new arc.audio.mock.Mock" + rtype.substring(rtype.lastIndexOf(".") + 1)+ "()").build());
- }
-
- type.addMethod(loadBegin.build());
- type.addMethod(dispose.build());
- JavaFile.builder(packageName, type.build()).build().writeTo(Utils.filer);
- }
-
- static String capitalize(String s){
- StringBuilder result = new StringBuilder(s.length());
-
- for(int i = 0; i < s.length(); i++){
- char c = s.charAt(i);
- if(c != '_' && c != '-'){
- if(i > 0 && (s.charAt(i - 1) == '_' || s.charAt(i - 1) == '-')){
- result.append(Character.toUpperCase(c));
- }else{
- result.append(c);
- }
- }
- }
-
- return result.toString();
- }
-}
diff --git a/annotations/src/main/java/mindustry/annotations/BaseProcessor.java b/annotations/src/main/java/mindustry/annotations/BaseProcessor.java
new file mode 100644
index 0000000000..37cbff235f
--- /dev/null
+++ b/annotations/src/main/java/mindustry/annotations/BaseProcessor.java
@@ -0,0 +1,228 @@
+package mindustry.annotations;
+
+import arc.files.*;
+import arc.struct.Array;
+import arc.util.*;
+import arc.util.Log.*;
+import com.squareup.javapoet.*;
+import com.sun.source.util.*;
+import mindustry.annotations.util.*;
+
+import javax.annotation.processing.*;
+import javax.lang.model.*;
+import javax.lang.model.element.*;
+import javax.lang.model.type.*;
+import javax.lang.model.util.*;
+import javax.tools.Diagnostic.*;
+import javax.tools.*;
+import java.io.*;
+import java.lang.annotation.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+@SupportedSourceVersion(SourceVersion.RELEASE_8)
+public abstract class BaseProcessor extends AbstractProcessor{
+ /** Name of the base package to put all the generated classes. */
+ public static final String packageName = "mindustry.gen";
+
+ public static Types typeu;
+ public static Elements elementu;
+ public static Filer filer;
+ public static Messager messager;
+ public static Trees trees;
+
+ protected int round;
+ protected int rounds = 1;
+ protected RoundEnvironment env;
+ protected Fi rootDirectory;
+
+ public static String getMethodName(Element element){
+ return ((TypeElement)element.getEnclosingElement()).getQualifiedName().toString() + "." + element.getSimpleName();
+ }
+
+ public static boolean isPrimitive(String type){
+ return type.equals("boolean") || type.equals("byte") || type.equals("short") || type.equals("int")
+ || type.equals("long") || type.equals("float") || type.equals("double") || type.equals("char");
+ }
+
+ public static boolean instanceOf(String type, String other){
+ TypeElement a = elementu.getTypeElement(type);
+ TypeElement b = elementu.getTypeElement(other);
+ return a != null && b != null && typeu.isSubtype(a.asType(), b.asType());
+ }
+
+ public static String getDefault(String value){
+ switch(value){
+ case "float":
+ case "double":
+ case "int":
+ case "long":
+ case "short":
+ case "char":
+ case "byte":
+ return "0";
+ case "boolean":
+ return "false";
+ default:
+ return "null";
+ }
+ }
+
+ //in bytes
+ public static int typeSize(String kind){
+ switch(kind){
+ case "boolean":
+ case "byte":
+ return 1;
+ case "short":
+ return 2;
+ case "float":
+ case "char":
+ case "int":
+ return 4;
+ case "long":
+ return 8;
+ default:
+ throw new IllegalArgumentException("Invalid primitive type: " + kind + "");
+ }
+ }
+
+ public static String simpleName(String str){
+ return str.contains(".") ? str.substring(str.lastIndexOf('.') + 1) : str;
+ }
+
+ public static TypeName tname(String name) throws Exception{
+ Constructor cons = TypeName.class.getDeclaredConstructor(String.class);
+ cons.setAccessible(true);
+ return cons.newInstance(name);
+ }
+
+ public static TypeName tname(Class> c){
+ return ClassName.get(c).box();
+ }
+
+ public static TypeVariableName getTVN(TypeParameterElement element) {
+ String name = element.getSimpleName().toString();
+ List extends TypeMirror> boundsMirrors = element.getBounds();
+
+ List boundsTypeNames = new ArrayList<>();
+ for (TypeMirror typeMirror : boundsMirrors) {
+ boundsTypeNames.add(TypeName.get(typeMirror));
+ }
+
+ return TypeVariableName.get(name, boundsTypeNames.toArray(new TypeName[0]));
+ }
+
+ public static void write(TypeSpec.Builder builder) throws Exception{
+ write(builder, null);
+ }
+
+ public static void write(TypeSpec.Builder builder, Array imports) throws Exception{
+ JavaFile file = JavaFile.builder(packageName, builder.build()).skipJavaLangImports(true).build();
+
+ if(imports != null){
+ String rawSource = file.toString();
+ Array result = new Array<>();
+ for (String s : rawSource.split("\n", -1)) {
+ result.add(s);
+ if (s.startsWith("package ")) {
+ result.add("");
+ for (String i : imports) {
+ result.add(i);
+ }
+ }
+ }
+
+ String out = result.toString("\n");
+ JavaFileObject object = filer.createSourceFile(file.packageName + "." + file.typeSpec.name, file.typeSpec.originatingElements.toArray(new Element[0]));
+ OutputStream stream = object.openOutputStream();
+ stream.write(out.getBytes());
+ stream.close();
+ }else{
+ file.writeTo(filer);
+ }
+ }
+
+ public Array elements(Class extends Annotation> type){
+ return Array.with(env.getElementsAnnotatedWith(type)).map(Selement::new);
+ }
+
+ public Array types(Class extends Annotation> type){
+ return Array.with(env.getElementsAnnotatedWith(type)).select(e -> e instanceof TypeElement)
+ .map(e -> new Stype((TypeElement)e));
+ }
+
+ public Array fields(Class extends Annotation> type){
+ return Array.with(env.getElementsAnnotatedWith(type)).select(e -> e instanceof VariableElement)
+ .map(e -> new Svar((VariableElement)e));
+ }
+
+ public Array methods(Class extends Annotation> type){
+ return Array.with(env.getElementsAnnotatedWith(type)).select(e -> e instanceof ExecutableElement)
+ .map(e -> new Smethod((ExecutableElement)e));
+ }
+
+ public static void err(String message){
+ messager.printMessage(Kind.ERROR, message);
+ Log.err("[CODEGEN ERROR] " +message);
+ }
+
+ public static void err(String message, Element elem){
+ messager.printMessage(Kind.ERROR, message, elem);
+ Log.err("[CODEGEN ERROR] " + message + ": " + elem);
+ }
+
+ public void err(String message, Selement elem){
+ err(message, elem.e);
+ }
+
+ @Override
+ public synchronized void init(ProcessingEnvironment env){
+ super.init(env);
+
+ trees = Trees.instance(env);
+ typeu = env.getTypeUtils();
+ elementu = env.getElementUtils();
+ filer = env.getFiler();
+ messager = env.getMessager();
+
+ Log.setLogLevel(LogLevel.info);
+
+ if(System.getProperty("debug") != null){
+ Log.setLogLevel(LogLevel.debug);
+ }
+ }
+
+ @Override
+ public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv){
+ if(round++ >= rounds) return false; //only process 1 round
+ if(rootDirectory == null){
+ try{
+ String path = Fi.get(filer.getResource(StandardLocation.CLASS_OUTPUT, "no", "no")
+ .toUri().toURL().toString().substring(OS.isWindows ? 6 : "file:".length()))
+ .parent().parent().parent().parent().parent().parent().parent().toString().replace("%20", " ");
+ rootDirectory = Fi.get(path);
+ }catch(IOException e){
+ throw new RuntimeException(e);
+ }
+ }
+
+ this.env = roundEnv;
+ try{
+ process(roundEnv);
+ }catch(Throwable e){
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ return true;
+ }
+
+ @Override
+ public SourceVersion getSupportedSourceVersion(){
+ return SourceVersion.RELEASE_8;
+ }
+
+ public void process(RoundEnvironment env) throws Exception{
+
+ }
+}
diff --git a/annotations/src/main/java/mindustry/annotations/CallSuperAnnotationProcessor.java b/annotations/src/main/java/mindustry/annotations/CallSuperAnnotationProcessor.java
deleted file mode 100644
index 01a3fdb0e1..0000000000
--- a/annotations/src/main/java/mindustry/annotations/CallSuperAnnotationProcessor.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package mindustry.annotations;
-
-import com.sun.source.util.*;
-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.util.*;
-
-@SupportedAnnotationTypes({"java.lang.Override"})
-public class CallSuperAnnotationProcessor extends AbstractProcessor{
- private Trees trees;
-
- @Override
- public void init(ProcessingEnvironment pe){
- super.init(pe);
- trees = Trees.instance(pe);
- }
-
- 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.setMethodName(e.getSimpleName().toString());
-
- TreePath tp = trees.getPath(e.getEnclosingElement());
- codeScanner.scan(tp, trees);
-
- if(codeScanner.isCallSuperUsed()){
- List list = codeScanner.getMethod().getBody().getStatements();
-
- if(!doesCallSuper(list, codeScanner.getMethodName())){
- processingEnv.getMessager().printMessage(Kind.ERROR, "Overriding method '" + codeScanner.getMethodName() + "' 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;
- }
-}
diff --git a/annotations/src/main/java/mindustry/annotations/CodeAnalyzerTreeScanner.java b/annotations/src/main/java/mindustry/annotations/CodeAnalyzerTreeScanner.java
deleted file mode 100644
index 13059b38b8..0000000000
--- a/annotations/src/main/java/mindustry/annotations/CodeAnalyzerTreeScanner.java
+++ /dev/null
@@ -1,110 +0,0 @@
-package mindustry.annotations;
-
-import com.sun.source.tree.*;
-import com.sun.source.util.TreePathScanner;
-import com.sun.source.util.Trees;
-import com.sun.tools.javac.code.Scope;
-import com.sun.tools.javac.code.Symbol;
-import com.sun.tools.javac.code.Symbol.MethodSymbol;
-import com.sun.tools.javac.code.Type.ClassType;
-import com.sun.tools.javac.tree.JCTree.JCIdent;
-import com.sun.tools.javac.tree.JCTree.JCTypeApply;
-import mindustry.annotations.Annotations.CallSuper;
-
-import java.lang.annotation.Annotation;
-
-class CodeAnalyzerTreeScanner extends TreePathScanner