Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Leonwang4234
2021-06-02 08:33:13 -07:00
1489 changed files with 51864 additions and 70927 deletions

View File

@@ -7,11 +7,9 @@ assignees: ''
---
**Note**: Do not report any new bugs directly relating to the v6 campaign. They will not be fixed or considered at this time.
**Platform**: *Android/iOS/Mac/Windows/Linux*
**Build**: *The build number under the title in the main menu. Required.*
**Build**: *The build number under the title in the main menu. Required. "LATEST" IS NOT A VERSION, I NEED THE EXACT BUILD NUMBER OF YOUR GAME.*
**Issue**: *Explain your issue in detail.*
@@ -19,11 +17,14 @@ assignees: ''
**Link(s) to mod(s) used**: *The mod repositories or zip files that are related to the issue, if applicable.*
**Save file**: *The save file you were playing on when the bug happened. REQUIRED for any issue that happens in-game.*
**Save file**: *The (zipped) save file you were playing on when the bug happened. THIS IS REQUIRED FOR ANY ISSUE HAPPENING IN-GAME OR IN MULTIPLAYER, REGARDLESS OF WHETHER YOU THINK IT HAPPENS EVERYWHERE. DO NOT DELETE OR OMIT THIS LINE UNLESS YOU ARE SURE THAT THE ISSUE DOES NOT HAPPEN IN-GAME. IF YOU DO NOT HAVE A SAVE, DON'T WASTE TIME OPENING THIS ISSUE.*
**Crash report**: *The contents of relevant crash report files. REQUIRED if you are reporting a crash.*
If you remove the line above without reading it properly and understanding what it means, I will reap your soul. Even if you're playing on someone's server, you can still save the game to a slot.
**(Crash) logs**: *Either crash reports from the crash folder, or the file you get when you go into Settings -> Game Data -> Export Crash logs. REQUIRED if you are reporting a crash.*
---
*Place an X (no spaces) between the brackets to confirm that you have read the line below.*
- [ ] **I have updated to the latest release (https://github.com/Anuken/Mindustry/releases) to make sure my issue has not been fixed.**
- [ ] **I have searched the closed and open issues to make sure that this problem has not already been reported.**

67
.github/workflows/deployment.yml vendored Normal file
View File

@@ -0,0 +1,67 @@
name: Deployment
on:
push:
tags:
- 'v*'
jobs:
buildJava14:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 14
uses: actions/setup-java@v1
with:
java-version: 14
- name: Set env
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Add Arc release
run: |
git clone --depth=1 --branch=master https://github.com/Anuken/Arc ../Arc
cd ../Arc
git tag ${RELEASE_VERSION}
git push https://Anuken:${{ secrets.API_TOKEN_GITHUB }}@github.com/Anuken/Arc ${RELEASE_VERSION};
cd ../Mindustry
- name: Create artifacts
run: |
./gradlew desktop:dist server:dist core:mergedJavadoc -Pbuildversion=${RELEASE_VERSION:1}
- name: Update docs
run: |
cd ../
git config --global user.email "cli@github.com"
git config --global user.name "Github Actions"
git clone --depth=1 https://github.com/MindustryGame/docs.git
cd docs
find . -maxdepth 1 ! -name ".git" ! -name . -exec rm -r {} \;
cd ../
cp -a Mindustry/core/build/javadoc/. docs/
cd docs
git add .
git commit -m "Update ${RELEASE_VERSION:1}"
git push https://Anuken:${{ secrets.API_TOKEN_GITHUB }}@github.com/MindustryGame/docs
cd ../Mindustry
- name: Update F-Droid build string
run: |
git clone --depth=1 --branch=master https://github.com/Anuken/MindustryBuilds ../MindustryBuilds
cd ../MindustryBuilds
echo "Updating version to ${RELEASE_VERSION:1}"
echo versionName=6-fdroid-${RELEASE_VERSION:1}$'\n'versionCode=${RELEASE_VERSION:1} > version_fdroid.txt
git add .
git commit -m "Updating to build ${RELEASE_VERSION:1}"
git push https://Anuken:${{ secrets.API_TOKEN_GITHUB }}@github.com/Anuken/MindustryBuilds
cd ../Mindustry
- name: Upload client artifacts
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: desktop/build/libs/Mindustry.jar
tag: ${{ github.ref }}
- name: Upload server artifacts
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: server/build/libs/server-release.jar
tag: ${{ github.ref }}

View File

@@ -1,27 +0,0 @@
name: Java CI
on: [push]
jobs:
buildJava14:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up JDK 14
uses: actions/setup-java@v1
with:
java-version: 14
- name: Run unit tests with gradle and Java 14
run: ./gradlew compileJava
buildJava15:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up JDK 15
uses: actions/setup-java@v1
with:
java-version: 15
- name: Run unit tests with gradle and Java 15
run: ./gradlew compileJava

21
.github/workflows/pr.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Pull Request Tests
on: [pull_request, workflow_dispatch]
jobs:
buildJava14:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 14
uses: actions/setup-java@v1
with:
java-version: 14
- name: Run unit tests and build JAR
run: ./gradlew test desktop:dist
- name: Upload desktop JAR for testing
uses: actions/upload-artifact@v2
with:
name: Desktop JAR (zipped)
path: desktop/build/libs/Mindustry.jar

25
.github/workflows/push.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Tests
on: [push, workflow_dispatch]
jobs:
buildJava14:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 14
uses: actions/setup-java@v1
with:
java-version: 14
- name: Run unit tests
run: ./gradlew clean cleanTest test
- name: Trigger BE build
if: ${{ github.repository == 'Anuken/Mindustry' }}
run: |
git clone --depth=1 --branch=master https://github.com/Anuken/MindustryBuilds ../MindustryBuilds
cd ../MindustryBuilds
BNUM=$(($GITHUB_RUN_NUMBER + 20000))
git tag ${BNUM}
git config --global user.name "Build Uploader"
git push https://Anuken:${{ secrets.API_TOKEN_GITHUB }}@github.com/Anuken/MindustryBuilds ${BNUM}

22
.gitignore vendored
View File

@@ -19,12 +19,13 @@ logs/
/annotations/out/
/net/build/
/tools/build/
/core/build/
/tests/build/
/server/build/
changelog
saves/
/core/assets-raw/fontgen/out/
/core/assets-raw/fontgen/icons/
/core/assets-raw/fontgen/icon_parts/
core/assets/saves/
/core/assets/saves/
steam_appid.txt
@@ -35,16 +36,24 @@ steam_appid.txt
/annotations/src/main/resources/META-INF/services
/core/assets/version.properties
/core/assets/locales
/core/assets/cache/
/ios/src/mindustry/gen/
/core/src/mindustry/gen/
ios/robovm.properties
packr-out/
config/
*.gif
/tests/out
/core/assets/basepartnames
version.properties
#sprites
core/assets/sprites/sprites*
core/assets/sprites/fallback/
core/assets/sprites/block_colors.png
.attach_*
## Java
@@ -93,6 +102,10 @@ com_crashlytics_export_strings.xml
.externalToolBuilders/
*.launch
## VSCode
.vscode/
## NetBeans
/nbproject/private/
@@ -143,13 +156,6 @@ nb-configuration.xml
/local.properties
.gradle/
gradle-app.setting
/build/
/android/build/
/core/build/
/desktop/build/
/html/build/
/ios/build/
/ios-moe/build/
## OS Specific
.DS_Store

View File

@@ -1,50 +0,0 @@
jdk:
- openjdk14
dist: xenial
android:
components:
- android-29
- build-tools-29.0.3
script:
- git clone --depth=1 --branch=master https://github.com/Anuken/MindustryBuilds ../MindustryBuilds
- cd ../MindustryBuilds
- echo ${TRAVIS_TAG}
- if [ -n "$TRAVIS_TAG" ]; then echo versionName=5-fdroid-${TRAVIS_TAG:1}$'\n'versionCode=${TRAVIS_TAG:1} > version_fdroid.txt; git add .; git commit -m "Updating to build ${TRAVIS_TAG}"; fi
- git tag ${TRAVIS_BUILD_NUMBER}
- git config --global user.name "Build Uploader"
- if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then git push https://Anuken:${GH_PUSH_TOKEN}@github.com/Anuken/MindustryBuilds ${TRAVIS_BUILD_NUMBER}; git push https://Anuken:${GH_PUSH_TOKEN}@github.com/Anuken/MindustryBuilds; fi
- cd ../Mindustry
- git clone --depth=1 --branch=master https://github.com/Anuken/Arc ../Arc
- if [ -n "$TRAVIS_TAG" ]; then cd ../Arc; git tag ${TRAVIS_TAG}; git push https://Anuken:${GH_PUSH_TOKEN}@github.com/Anuken/Arc ${TRAVIS_TAG}; cd ../Mindustry; fi
- "./gradlew test"
- "./gradlew desktop:dist -Pbuildversion=${TRAVIS_TAG:1}"
- "./gradlew server:dist -Pbuildversion=${TRAVIS_TAG:1}"
- "./gradlew core:javadoc"
- cd ../
- git clone --depth=1 https://github.com/MindustryGame/docs.git
- cp -a Mindustry/core/build/docs/javadoc/. docs/
- cd docs
- if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then git add .; git commit -m "Update ${TRAVIS_BUILD_NUMBER}"; git push https://Anuken:${GH_PUSH_TOKEN}@github.com/MindustryGame/docs; fi
- cd ../Mindustry
deploy:
- provider: releases
skip_cleanup: true
draft: false
api_key:
secure: Cv5wFtWt62/A24EvSEQvMow7gKPbZ3oATEFPuSghhB2TQz1dA40Zee3Qvk4LFlpLrhYo4K0ZSczCZRGpR+hCd8+Dpww52bheYEvWuh3ZQfvu/fXtEx2j5PwP1qMpmIgSxETV/gkD7l9FImdh0VzktYiAvQfmi0bEocG9/D4QwjFpNat7iwBdcMiw1MvAygpdIWRsjiw0RKlB2mWarmoHhQ7Gu7qlU3j50uaEvcrtmU0pBUPggNQwQRv32i9NPvNFxrqqlUjDLIS8JFea99zCkp8BwYqbEvBIMzd+Qip1/stLJJA3+cDUClbsDtg8rAVetzpOrdLEEBmqShFe5MDl2yEHcsgpN9CFsyTaUfvB3P3rVjizvycMm42IsUkXQiarm5xTQ/TIA8Rd8AHiSKuweNCg1Fd5SFaRtKy8JVLXuxyfUccmyje6hhz2L4lS2Wfj3mAG7sqZUCXhWP79EKdGkiPOjKv4CwXEKmuH3BMVqPlNUZJr9Eg3sV1FG0h2l+MVOOnR635qdUbb49sYojYxVruMLX0BH1c4ZCu230m8CUoWA1Em1QNI75ya7+9Y5T6AsgWDVpBvdUo9fWNbdp+VQ0GskFQsJD5wtnxbcbHeFiERAgGBm7z6qt9u9LrQpBH+dsW52ADvYsu3L4nQEa+sdMHwTTwmGY+iUvsxu0DqxGg=
file:
- desktop/build/libs/Mindustry.jar
- server/build/libs/server-release.jar
on:
repo: Anuken/Mindustry
tags: true
#- provider: script
# script: bash update_wiki.sh
# on:
# repo: Anuken/Mindustry
# tags: true
env:
global:
- secure: TqlUl/ojjkCMVOGbCTKz7Cnr4F08UyWzY/CiJ0vvUOGJGZ1qm7XavAlDf5XT0egU4mvr37THubFO8vojbqmrmy0oZnYh3njKFA8axgyZ8PyKkjGHOfd0i6qyEWsOr9H90/2X8r3LwEeLaDFyHpu3wljIGBjweg53g2qwmDwCFa9UR80FJZ+xDB+rD6B3cXT0DTEkCoLZXLqXm0Y3HvBdSuBL1LR/FNb2BSxNq+tNLGiz1kdQZV5erausbbZypBoGxzz63xAnyz2kkFz73A8xQYVTzGbFodTPz7HM13GVZ5s43I03Y+HYyHBgBaSLziO2hi2kzVJccOwzBp7wS4fs1MqsFY5+IeWJ9k+hm89NiYT7+6zlEgoUMlIniny1qLqWTzx7btUeuC/y/h5TVBNgaV+z0jmHycHfeSyq5I+vmX4J8qe3wmaN8TcdqYKU5nIznOTk3CM5Fzu0Bs9vkCkOxmormmcjMFW1RbdOLc/hpZWZggsBA88sNEAI8eq+r5QEeqzeCx8YKoZDjdrsqvgLMc3El3gS9oMGxkn0Y/TEcqs9Tc4BXtTkqIA68hD0DYzlAxYjVbbkAI9Hh9lHNvV3Dr/oCkGXQ/HflM143kj1L3tSBZpqeqQE2XhngB5nqpS3OZTmZbMTQ8qD2luU18yaTGMLF5tJS/fdKPRx0gQ1kL8=
- secure: VEskj/0TVX2o7iUVXuVPysj/VSWmPhDl57SrT7/nBNN/P/8N5jFAvx8PMzG7qT0S5FzKxuV20psE4WylUGRKdeRtK7/QNBV7T3YqYYM6BUB1VeRpxe5hLxTeuBK3izglFO8DkdDqjUtzQSjzkoYT75ilROjhBrBUPhVek7UlbBHbaklPWYFXHnJmYS1FpZTdzqIj+Y0Gd1PSL2MzK4X74aAHl0qaDgsTwYwtKs7IAz+kFaTZBRpi9VjQHAFhDlkDR3jo9wQjH8/F6x0lCgV/FulSc37Okdb40sLFG98xcEA6gWh1NPMkz8CulUdVE7mj7SJNxLbNvoMNrWOVRjmEsn59p/9LiNC1F9ncFz9vjQjAmi7rMFFGHGxe5nn8cIAkpTvHQQkZoWHAA9SNJTDMMf09m2pRy/vvzx+a6NVxyC9iNrhLlnBg4gxAqRh0S6NU0uL+fuygKixn7rqlnb7KMT7bAbfcuV+dng6c8V7hYKDCh7sJbH8iJump1xkwoM7ecnU8fxJF/oKOr/fbk0Bfxu+Q9qYLrV1+DEdm93Vl2Thq+DBKmI66jRGSva6HeCLFo81PEiEjP1nLv75+kvVfOqVqJrZD1BrvoG2eWT/3hVLrN2kEIWWlpvQVC7FL11yWmYtAuOBh/vfhI76zKr+YTS6ccG9rqW4XeYjJytshe8M=

View File

@@ -15,9 +15,6 @@ If you are submitting a new block, make sure it has a name and description, and
If you are interested in adding a large mechanic/feature or changing large amounts of code, first contact me (Anuken) via [Discord](https://discord.gg/mindustry) (preferred method) or via e-mail (*anukendev@gmail.com*).
For most changes, this should not be necessary. I just want to know if you're doing something big so I can offer advice and/or make sure you're not wasting your time on it.
### Do not include packed sprites in your pull request.
When making a pull request that changes or adds new sprites, do not add the modified atlas & `spritesX.png` files to your final pull request. These are a frequent source of conflicts.
## Style Guidelines
@@ -29,8 +26,8 @@ This means:
- `camelCase`, **even for constants or enums**. Why? Because `SCREAMING_CASE` is ugly, annoying to type and does not achieve anything useful. Constants are *less* dangerous than variables, not more. Any reasonable IDE should highlight them for you anyway.
- No underscores for anything. (Yes, I know `Bindings` violates this principle, but that's for legacy reasons and really should be cleaned up some day)
- Do not use braceless `if/else` statements. `if(x) statement else statement2` should **never** be done. In very specific situations, having braceless if-statements on one line is allowed: `if(cond) return;` would be valid.
- Prefer single-line javadoc `/** @return for example */` instead of multiline javadoc whenver possible
- Short method/variable names (multipleLongWords should be avoided if it's possible to so reasonably, especially for variables)
- Prefer single-line javadoc `/** @return for example */` instead of multiline javadoc whenever possible
- Short method/variable names (multipleLongWords should be avoided if it's possible to do so reasonably, especially for variables)
- Use wildcard imports - `import some.package.*` - for everything. This makes incorrect class usage more obvious (*e.g. arc.util.Timer vs java.util.Timer*) and leads to cleaner-looking code.
Import [this style file](.github/Mindustry-CodeStyle-IJ.xml) into IntelliJ to get correct formatting when developing Mindustry.

52
ISSUES.md Normal file
View File

@@ -0,0 +1,52 @@
# Why was my issue closed?
This document goes over some common causes for issue closures.
## You did not fill in the template
I can't debug the problem unless you provide the information the template asks for.
If you cannot put in the effort to fill out a template, then don't expect me to put in the effort to fix it.
## Your issue was already reported
If the problem in your issue has already been encountered before, it will be closed - especially if your report doesn't provide any new information.
Make sure you search the *closed* issues before making an issue.
I do not link the specific issue(s) that report the same problem, because searching takes time - if you're interested in finding them, you should be able to do so without my help.
To be clear: I do **not** expect users to look at *all* previous issues, or do a comprehensive stack trace analysis to see if their crash was already reported.
## Your issue was already fixed
The problem you reported has been addressed. Note that this does **not** mean that the latest stable version of Mindustry has the fix!
It simply means that I have committed (or am about to commit) a patch that fixes it *on the current development branch*.
## Your issue is missing a crash report or log
If the game crashes without a specific cause, and you don't send me a log, I can't fix it. There is no way for me to know what went wrong.
During a normal crash, the game should tell you where the log is saved. If not, you should still be able to look in the game's crash folder on most operating systems, or export the logs in *Settings -> Game Data -> Export Crash Logs*.
## Your issue is missing saves or screenshots
Even if you think your problem happens everywhere and saves/screenshots are redundant, this is frequently not the case.
If I cannot reproduce the problem on my own saves and you have not linked any of your own, then the problem is likely to be save-specific. If you do not send me any, the problem cannot be investigated further.
## Your issue is related to an external program
If Mindustry causes something else to crash or misbehave, I am very unlikely to fix it. Unless the problem is serious, widespread and/or clearly a bug *in Mindustry*, it is not my responsibility.
Similarly, if you use another (invasive) program to change how Mindustry works, and something goes wrong, that is not my problem. Don't do it.
## Your issue is caused by mods
Crashes and bugs related to installed mods should be reported in the relevant mod repository, not here.
*Note that problems with the Mindustry modding API are a separate problem, and do not apply.*
## I cannot reproduce your issue
If I follow your instructions and am repeatedly unable to reproduce the problem you've reported, then it is very unlikely to be fixed.
Either the problem is device-specific, or there is not enough information given for me to be able to reproduce it.
I may attempt to change some code if I think it will make the issue less likely to occur, but without knowing for sure, the issue cannot be considered truly "fixed".
As I cannot make any further progress on the problem, there is no reason to keep it open. If it is a common bug/crash, other people will come along with information that may shed some light on the issue.

View File

@@ -1,7 +1,7 @@
![Logo](core/assets-raw/sprites/ui/logo.png)
[![Build Status](https://travis-ci.org/Anuken/Mindustry.svg?branch=master)](https://travis-ci.org/Anuken/Mindustry)
[![Discord](https://img.shields.io/discord/391020510269669376.svg)](https://discord.gg/mindustry)
[![Build Status](https://github.com/Anuken/Mindustry/workflows/Tests/badge.svg?event=push)](https://github.com/Anuken/Mindustry/actions)
[![Discord](https://img.shields.io/discord/391020510269669376.svg?logo=discord&logoColor=white&logoWidth=20&labelColor=7289DA&label=Discord&color=17cf48)](https://discord.gg/mindustry)
A sandbox tower defense game written in Java.
@@ -9,34 +9,34 @@ _[Trello Board](https://trello.com/b/aE2tcUwF/mindustry-40-plans)_
_[Wiki](https://mindustrygame.github.io/wiki)_
_[Javadoc](https://mindustrygame.github.io/docs/)_
### Contributing
## Contributing
See [CONTRIBUTING](CONTRIBUTING.md).
### Building
## Building
Bleeding-edge builds are generated automatically for every commit. You can see them [here](https://github.com/Anuken/MindustryBuilds/releases).
If you'd rather compile on your own, follow these instructions.
First, make sure you have [JDK 14](https://adoptopenjdk.net/) installed. Open a terminal in the root directory, `cd` to the Mindustry folder and run the following commands:
#### Windows
### Windows
_Running:_ `gradlew desktop:run`
_Building:_ `gradlew desktop:dist`
_Sprite Packing:_ `gradlew tools:pack`
#### Linux/Mac OS
### Linux/Mac OS
_Running:_ `./gradlew desktop:run`
_Building:_ `./gradlew desktop:dist`
_Sprite Packing:_ `./gradlew tools:pack`
#### Server
### Server
Server builds are bundled with each released build (in Releases). If you'd rather compile on your own, replace 'desktop' with 'server', e.g. `gradlew server:dist`.
#### Android
### Android
1. Install the Android SDK [here.](https://developer.android.com/studio#downloads) Make sure you're downloading the "Command line tools only", as Android Studio is not required.
2. Set the `ANDROID_HOME` environment variable to point to your unzipped Android SDK directory.
@@ -44,7 +44,9 @@ Server builds are bundled with each released build (in Releases). If you'd rathe
To debug the application on a connected phone, run `gradlew android:installDebug android:run`.
##### Troubleshooting
### Troubleshooting
#### Permission Denied
If the terminal returns `Permission denied` or `Command not found` on Mac/Linux, run `chmod +x ./gradlew` before running `./gradlew`. *This is a one-time procedure.*
@@ -53,24 +55,11 @@ 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. <br>
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
## Feature Requests
Post feature requests and feedback [here](https://github.com/Anuken/Mindustry-Suggestions/issues/new/choose).
### Downloads
## Downloads
[<img src="https://static.itch.io/images/badge.svg"
alt="Get it on Itch.io"
height="60">](https://anuke.itch.io/mindustry)
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png"
alt="Get it on Google Play"
height="80">](https://play.google.com/store/apps/details?id=io.anuke.mindustry)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/io.anuke.mindustry/)
[<img src="https://flathub.org/assets/badges/flathub-badge-en.svg"
alt="Download On Flathub"
height="60">](https://flathub.org/apps/details/com.github.Anuken.Mindustry)
| [![](https://static.itch.io/images/badge.svg)](https://anuke.itch.io/mindustry) | [![](https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png)](https://play.google.com/store/apps/details?id=io.anuke.mindustry) | [![](https://fdroid.gitlab.io/artwork/badge/get-it-on.png)](https://f-droid.org/packages/io.anuke.mindustry) | [![](https://flathub.org/assets/badges/flathub-badge-en.svg)](https://flathub.org/apps/details/com.github.Anuken.Mindustry)
|--- |--- |--- |--- |

View File

@@ -1,22 +1,23 @@
### 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.
This is done by letting clients `GET` a [JSON list of servers](https://github.com/Anuken/Mindustry/blob/master/servers_v6.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.
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 <name/desc/motd> <value>`. "Appropriate" means that:
2. Make sure that your server is able to handle inappropriate content - this includes NSFW display/sorter art and abusive messages. **Servers that allow such content will be removed immediately.** Consider banning display blocks if it is a problem for your server: `rules add bannedBlocks ["logic-display", "large-logic-display"]`.
3. **Set an appropriate MOTD, name and description.** This is set with `config <name/desc/motd> <value>`. "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.
4. **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.
5. **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`).
6. 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_v6.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
{

View File

@@ -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">
<meta-data android:name="android.max_aspect" android:value="2.1"/>
<activity
android:name="mindustry.android.AndroidLauncher"

View File

@@ -4,7 +4,6 @@ buildscript{
mavenCentral()
google()
maven{ url "https://oss.sonatype.org/content/repositories/snapshots/" }
jcenter()
}
dependencies{
@@ -20,22 +19,8 @@ configurations{ natives }
repositories{
mavenCentral()
jcenter()
maven{ url "https://maven.google.com" }
}
dependencies{
implementation project(":core")
implementation arcModule("backends:backend-android")
implementation 'com.jakewharton.android.repackaged:dalvik-dx:9.0.0_r3'
natives "com.github.Anuken.Arc:natives-android:${getArcHash()}"
natives "com.github.Anuken.Arc:natives-freetype-android:${getArcHash()}"
natives "com.github.Anuken.Arc:natives-box2d-android:${getArcHash()}"
//android dependencies magically disappear during compilation, thanks gradle!
if(new File(projectDir.parent, '../Arc').exists()) compileOnly fileTree(dir: '../../Arc/backends/backend-android/libs', include: ['*.jar'])
jcenter() //remove later once google/JetBrains fixes the dependency
}
task deploy(type: Copy){
@@ -47,8 +32,8 @@ task deploy(type: Copy){
}
android{
buildToolsVersion '29.0.3'
compileSdkVersion 29
buildToolsVersion '30.0.2'
compileSdkVersion 30
sourceSets{
main{
manifest.srcFile 'AndroidManifest.xml'
@@ -59,9 +44,13 @@ android{
assets.srcDirs = ['assets', 'src/main/assets', '../core/assets/']
jniLibs.srcDirs = ['libs']
}
gp{
java.srcDirs = ['srcgp']
}
androidTest.setRoot('tests')
}
packagingOptions{
exclude 'META-INF/robovm/ios/robovm.xml'
}
@@ -73,10 +62,10 @@ android{
applicationId "io.anuke.mindustry"
minSdkVersion 14
targetSdkVersion 29
targetSdkVersion 30
versionName versionNameResult
versionCode vcode
versionCode = (System.getenv("TRAVIS_BUILD_ID") != null ? System.getenv("TRAVIS_BUILD_ID").toInteger() : vcode)
if(project.hasProperty("release")){
props['androidBuildCode'] = (vcode + 1).toString()
@@ -98,23 +87,63 @@ android{
storePassword RELEASE_STORE_PASSWORD
keyAlias RELEASE_KEY_ALIAS
keyPassword RELEASE_KEY_PASSWORD
}else if(System.getenv("CI") == "true"){
storeFile = file("../../bekeystore.jks")
storePassword = System.getenv("keystore_password")
keyAlias = System.getenv("keystore_alias")
keyPassword = System.getenv("keystore_alias_password")
}else{
println("No keystore property found. Releases will be unsigned.")
}
}
}
if(project.hasProperty("RELEASE_STORE_FILE")) {
buildTypes {
release {
buildTypes{
all{
minifyEnabled = true
shrinkResources = true
proguardFiles("proguard-rules.pro")
}
}
if(project.hasProperty("RELEASE_STORE_FILE") || System.getenv("CI") == "true"){
buildTypes{
release{
signingConfig signingConfigs.release
}
}
}
// Specifies one flavor dimension.
flavorDimensions "version"
productFlavors{
standard{
}
gp{
applicationIdSuffix ".gp"
versionNameSuffix "-gp"
}
}
}
// called every time gradle gets executed, takes the native dependencies of
// the natives configuration, and extracts them to the proper libs/ folders
// so they get packed with the APK.
dependencies{
implementation project(":core")
implementation arcModule("backends:backend-android")
implementation 'com.jakewharton.android.repackaged:dalvik-dx:9.0.0_r3'
natives "com.github.Anuken.Arc:natives-android:${getArcHash()}"
natives "com.github.Anuken.Arc:natives-freetype-android:${getArcHash()}"
gpImplementation "com.google.android.gms:play-services-games:21.0.0"
gpImplementation "com.google.android.gms:play-services-auth:19.0.0"
//android dependencies magically disappear during compilation, thanks gradle!
def sdkFile = new File((String)findSdkDir(), "/platforms/android-29/android.jar")
if(sdkFile.exists()) compileOnly files(sdkFile.absolutePath)
}
task copyAndroidNatives(){
configurations.natives.files.each{ jar ->
copy{
@@ -126,23 +155,13 @@ 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"
commandLine "${findSdkDir()}/platform-tools/adb", 'shell', 'am', 'start', '-n', 'io.anuke.mindustry/mindustry.android.AndroidLauncher'
}
if(!project.ext.hasSprites()){
tasks.whenTaskAdded{ task ->
if(task.name == 'assembleDebug' || task.name == 'assembleRelease'){
task.dependsOn ":tools:pack"
}
}else{
path = "$System.env.ANDROID_HOME"
}
def adb = path + "/platform-tools/adb"
commandLine "$adb", 'shell', 'am', 'start', '-n', 'io.anuke.mindustry/mindustry.android.AndroidLauncher'
}

8
android/proguard-rules.pro vendored Normal file
View File

@@ -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.** { *; }

View File

@@ -1,6 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Mindustry</string>
</resources>

View File

@@ -1,5 +1,4 @@
<resources>
<style name="ArcTheme" parent="android:Theme">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
@@ -8,5 +7,4 @@
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowFullscreen">true</item>
</style>
</resources>

View File

@@ -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.*;
@@ -27,13 +29,15 @@ import java.util.*;
import static mindustry.Vars.*;
public class AndroidLauncher extends AndroidApplication{
public static final int PERMISSION_REQUEST_CODE = 1;
boolean doubleScaleTablets = true;
FileChooser chooser;
Runnable permCallback;
Object gpService;
Class<?> serviceClass;
@Override
protected void onCreate(Bundle savedInstanceState){
UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
@@ -45,13 +49,13 @@ public class AndroidLauncher extends AndroidApplication{
if(handler != null){
handler.uncaughtException(thread, error);
}else{
error.printStackTrace();
Log.err(error);
System.exit(1);
}
});
super.onCreate(savedInstanceState);
if(doubleScaleTablets && isTablet(this.getContext())){
if(doubleScaleTablets && isTablet(this)){
Scl.setAddition(0.5f);
}
@@ -64,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
@@ -72,17 +78,16 @@ public class AndroidLauncher extends AndroidApplication{
}
@Override
public Class<?> loadJar(Fi jar, String mainClass) throws Exception{
DexClassLoader loader = new DexClassLoader(jar.file().getPath(), getFilesDir().getPath(), null, getClassLoader());
return Class.forName(mainClass, true, loader);
public ClassLoader loadJar(Fi jar, ClassLoader parent) throws Exception{
return new DexClassLoader(jar.file().getPath(), getFilesDir().getPath(), null, parent);
}
@Override
public void showFileChooser(boolean open, String extension, Cons<Fi> cons){
showFileChooser(open, cons, extension);
public void showFileChooser(boolean open, String title, String extension, Cons<Fi> cons){
showFileChooser(open, title, cons, extension);
}
void showFileChooser(boolean open, Cons<Fi> cons, String... extensions){
void showFileChooser(boolean open, String title, Cons<Fi> cons, String... extensions){
String extension = extensions[0];
if(VERSION.SDK_INT >= VERSION_CODES.Q){
@@ -119,7 +124,7 @@ public class AndroidLauncher extends AndroidApplication{
});
}else if(VERSION.SDK_INT >= VERSION_CODES.M && !(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&
checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)){
chooser = new FileChooser(open ? "@open" : "@save", file -> Structs.contains(extensions, file.extension().toLowerCase()), open, file -> {
chooser = new FileChooser(title, file -> Structs.contains(extensions, file.extension().toLowerCase()), open, file -> {
if(!open){
cons.get(file.parent().child(file.nameWithoutExtension() + "." + extension));
}else{
@@ -137,16 +142,16 @@ public class AndroidLauncher extends AndroidApplication{
requestPermissions(perms.toArray(new String[0]), PERMISSION_REQUEST_CODE);
}else{
if(open){
new FileChooser("@open", file -> Structs.contains(extensions, file.extension().toLowerCase()), true, cons).show();
new FileChooser(title, file -> Structs.contains(extensions, file.extension().toLowerCase()), true, cons).show();
}else{
super.showFileChooser(open, extension, cons);
super.showFileChooser(open, "@open", extension, cons);
}
}
}
@Override
public void showMultiFileChooser(Cons<Fi> cons, String... extensions){
showFileChooser(true, cons, extensions);
showFileChooser(true, "@open", cons, extensions);
}
@Override
@@ -162,33 +167,47 @@ public class AndroidLauncher extends AndroidApplication{
}, new AndroidApplicationConfiguration(){{
useImmersiveMode = true;
hideStatusBar = true;
stencil = 8;
}});
checkFiles(getIntent());
try{
//new external folder
Fi data = Core.files.absolute(((Context)this).getExternalFilesDir(null).getAbsolutePath());
Core.settings.setDataDirectory(data);
//new external folder
Fi data = Core.files.absolute(getContext().getExternalFilesDir(null).getAbsolutePath());
Core.settings.setDataDirectory(data);
//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...");
//delete unused cache folder to free up space
try{
//current local storage folder
Fi src = Core.files.absolute(Core.files.getLocalStoragePath());
for(Fi fi : src.list()){
fi.copyTo(data);
Fi cache = Core.settings.getDataDirectory().child("cache");
if(cache.exists()){
cache.deleteDirectory();
}
//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();
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...");
try{
//current local storage folder
Fi src = Core.files.absolute(Core.files.getLocalStoragePath());
for(Fi fi : src.list()){
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();
}
}
}catch(Exception e){
//print log but don't crash
Log.err(e);
}
}
@@ -208,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();

View File

@@ -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);
}

View File

@@ -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.");
}
});
}
}
}

View File

@@ -1,2 +1,2 @@
sourceSets.main.java.srcDirs = ["src/main/java/"]
sourceSets.main.resources.srcDirs = ["src/main/resources/"]
sourceSets.main.resources.srcDirs = ["src/main/resources/"]

View File

@@ -98,6 +98,8 @@ public class Annotations{
boolean serialize() default true;
/** Whether to generate IO code. This is for advanced usage only. */
boolean genio() default true;
/** Whether I made a massive mistake by merging two different class branches */
boolean legacy() default false;
}
/** Indicates an internal interface for entity components. */
@@ -116,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();
@@ -175,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. */

View File

@@ -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();
}
@@ -115,12 +106,12 @@ public abstract class BaseProcessor extends AbstractProcessor{
return ClassName.get(c).box();
}
public static TypeVariableName getTVN(TypeParameterElement element) {
public static TypeVariableName getTVN(TypeParameterElement element){
String name = element.getSimpleName().toString();
List<? extends TypeMirror> boundsMirrors = element.getBounds();
List<TypeName> boundsTypeNames = new ArrayList<>();
for (TypeMirror typeMirror : boundsMirrors) {
for(TypeMirror typeMirror : boundsMirrors){
boundsTypeNames.add(TypeName.get(typeMirror));
}
@@ -137,11 +128,11 @@ public abstract class BaseProcessor extends AbstractProcessor{
if(imports != null){
String rawSource = file.toString();
Seq<String> result = new Seq<>();
for (String s : rawSource.split("\n", -1)) {
for(String s : rawSource.split("\n", -1)){
result.add(s);
if (s.startsWith("package ")) {
if (s.startsWith("package ")){
result.add("");
for (String i : imports) {
for (String i : imports){
result.add(i);
}
}
@@ -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,20 +185,16 @@ 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.setLogLevel(LogLevel.info);
Log.level = LogLevel.info;
if(System.getProperty("debug") != null){
Log.setLogLevel(LogLevel.debug);
Log.level = LogLevel.debug;
}
}
@@ -219,7 +206,7 @@ public abstract class BaseProcessor extends AbstractProcessor{
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);
rootDirectory = Fi.get(path).parent();
}catch(IOException e){
throw new RuntimeException(e);
}
@@ -229,7 +216,7 @@ public abstract class BaseProcessor extends AbstractProcessor{
try{
process(roundEnv);
}catch(Throwable e){
e.printStackTrace();
Log.err(e);
throw new RuntimeException(e);
}
return true;

View File

@@ -3,7 +3,6 @@ package mindustry.annotations.entity;
import arc.files.*;
import arc.func.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import arc.util.io.*;
import arc.util.pooling.Pool.*;
@@ -133,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();
@@ -241,7 +241,6 @@ public class EntityProcess extends BaseProcessor{
//look at each definition
for(Selement<?> type : allDefs){
EntityDef ann = type.annotation(EntityDef.class);
boolean isFinal = ann.isFinal();
//all component classes (not interfaces)
Seq<Stype> components = allComponents(type);
@@ -275,6 +274,13 @@ public class EntityProcess extends BaseProcessor{
name += "Entity";
}
boolean legacy = ann.legacy();
if(legacy){
baseClass = tname(packageName + "." + name);
name += "Legacy" + Strings.capitalize(type.name());
}
//skip double classes
if(usedNames.containsKey(name)){
extraNames.get(usedNames.get(name), ObjectSet::new).add(type.name());
@@ -336,7 +342,8 @@ public class EntityProcess extends BaseProcessor{
boolean isVisible = !f.is(Modifier.STATIC) && !f.is(Modifier.PRIVATE) && !f.has(ReadOnly.class);
//add the field only if it isn't visible or it wasn't implemented by the base class
if(!isShadowed || !isVisible){
//legacy classes have no extra fields
if((!isShadowed || !isVisible) && !legacy){
builder.addField(spec);
}
@@ -346,7 +353,7 @@ public class EntityProcess extends BaseProcessor{
allFields.add(f);
//add extra sync fields
if(f.has(SyncField.class) && isSync){
if(f.has(SyncField.class) && isSync && !legacy){
if(!f.tname().toString().equals("float")) err("All SyncFields must be of type float", f);
syncedFields.add(f);
@@ -379,7 +386,7 @@ public class EntityProcess extends BaseProcessor{
.addModifiers(Modifier.PUBLIC)
.addStatement("return $S + $L", name + "#", "id").build());
EntityIO io = new EntityIO(type.name(), builder, allFieldSpecs, serializer, rootDirectory.child("annotations/src/main/resources/revisions").child(name));
EntityIO io = new EntityIO(type.name(), builder, allFieldSpecs, serializer, rootDirectory.child("annotations/src/main/resources/revisions").child(type.name()));
//entities with no sync comp and no serialization gen no code
boolean hasIO = ann.genio() && (components.contains(s -> s.name().contains("Sync")) || ann.serialize());
@@ -440,11 +447,14 @@ public class EntityProcess extends BaseProcessor{
}
}
boolean specialIO = false;
if(hasIO){
//SPECIAL CASE: I/O code
//note that serialization is generated even for non-serializing entities for manual usage
if((first.name().equals("read") || first.name().equals("write"))){
io.write(mbuilder, first.name().equals("write"));
specialIO = true;
}
//SPECIAL CASE: sync I/O code
@@ -523,7 +533,9 @@ public class EntityProcess extends BaseProcessor{
mbuilder.addStatement("mindustry.gen.Groups.queueFree(($T)this)", Poolable.class);
}
builder.addMethod(mbuilder.build());
if(!legacy || specialIO){
builder.addMethod(mbuilder.build());
}
}
//add pool reset method and implement Poolable
@@ -558,7 +570,7 @@ public class EntityProcess extends BaseProcessor{
.returns(tname(packageName + "." + name))
.addStatement(ann.pooled() ? "return Pools.obtain($L.class, " +name +"::new)" : "return new $L()", name).build());
definitions.add(new EntityDefinition(packageName + "." + name, builder, type, typeIsBase ? null : baseClass, components, groups, allFieldSpecs));
definitions.add(new EntityDefinition(packageName + "." + name, builder, type, typeIsBase ? null : baseClass, components, groups, allFieldSpecs, legacy));
}
//generate groups
@@ -663,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());
@@ -696,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){
@@ -723,6 +747,14 @@ public class EntityProcess extends BaseProcessor{
def.builder.addSuperinterface(inter.tname());
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();
@@ -730,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);
}
}
}
@@ -745,8 +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);
//TODO should be dynamic
ObjectSet<String> nullList = ObjectSet.with("unit");
//create mock types of all components
for(Stype interf : allInterfaces){
@@ -765,6 +827,12 @@ public class EntityProcess extends BaseProcessor{
//create null builder
String baseName = interf.name().substring(0, interf.name().length() - 1);
//prevent Nulls bloat
if(!nullList.contains(Strings.camelize(baseName))){
continue;
}
String className = "Null" + baseName;
TypeSpec.Builder nullBuilder = TypeSpec.classBuilder(className)
.addModifiers(Modifier.FINAL);
@@ -898,7 +966,7 @@ public class EntityProcess extends BaseProcessor{
}
String createName(Selement<?> elem){
Seq<Stype> comps = types(elem.annotation(EntityDef.class), EntityDef::value).map(this::interfaceToComp);;
Seq<Stype> comps = types(elem.annotation(EntityDef.class), EntityDef::value).map(this::interfaceToComp);
comps.sortComparing(Selement::name);
return comps.toString("", s -> s.name().replace("Comp", ""));
}
@@ -942,9 +1010,10 @@ public class EntityProcess extends BaseProcessor{
final Selement naming;
final String name;
final @Nullable TypeName extend;
final boolean legacy;
int classID;
public EntityDefinition(String name, Builder builder, Selement naming, TypeName extend, Seq<Stype> components, Seq<GroupDefinition> groups, Seq<FieldSpec> fieldSpec){
public EntityDefinition(String name, Builder builder, Selement naming, TypeName extend, Seq<Stype> components, Seq<GroupDefinition> groups, Seq<FieldSpec> fieldSpec, boolean legacy){
this.builder = builder;
this.name = name;
this.naming = naming;
@@ -952,6 +1021,7 @@ public class EntityProcess extends BaseProcessor{
this.components = components;
this.extend = extend;
this.fieldSpecs = fieldSpec;
this.legacy = legacy;
}
@Override

View File

@@ -32,27 +32,40 @@ public class AssetsProcess extends BaseProcessor{
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);
CodeBlock.Builder ichinit = CodeBlock.builder();
String resources = rootDirectory + "/core/assets-raw/sprites/ui";
Jval icons = Jval.read(Fi.get(rootDirectory + "/core/assets-raw/fontgen/config.json").readString());
ObjectMap<String, String> texIcons = new OrderedMap<>();
PropertiesUtils.load(texIcons, Fi.get(rootDirectory + "/core/assets/icons/icons.properties").reader());
StringBuilder iconcAll = new StringBuilder();
texIcons.each((key, val) -> {
String[] split = val.split("\\|");
String name = Strings.kebabToCamel(split[1]).replace("Medium", "").replace("Icon", "");
if(SourceVersion.isKeyword(name) || name.equals("char")) name = name + "i";
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).initializer("(char)" + key).build());
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());
});
ictype.addField(FieldSpec.builder(ParameterizedTypeName.get(ObjectMap.class, String.class, TextureRegionDrawable.class),
"icons", Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer("new ObjectMap<>()").build());
ichtype.addField(FieldSpec.builder(ParameterizedTypeName.get(ObjectIntMap.class, String.class),
"codes", Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer("new ObjectIntMap<>()").build());
ObjectSet<String> used = new ObjectSet<>();
for(Jval val : icons.get("glyphs").asArray()){
String name = capitalize(val.getString("css", ""));
if(!val.getBool("selected", true) || !used.add(name)) continue;
int code = val.getInt("code", 0);
ichtype.addField(FieldSpec.builder(char.class, name, Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer("(char)" + code).build());
iconcAll.append((char)code);
ichtype.addField(FieldSpec.builder(char.class, name, Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).addJavadoc(String.format("\\u%04x", code)).initializer("'" + ((char)code) + "'").build());
ichinit.addStatement("codes.put($S, $L)", name, code);
ictype.addField(TextureRegionDrawable.class, name + "Small", Modifier.PUBLIC, Modifier.STATIC);
icload.addStatement(name + "Small = mindustry.ui.Fonts.getGlyph(mindustry.ui.Fonts.def, (char)" + code + ")");
@@ -64,6 +77,9 @@ public class AssetsProcess extends BaseProcessor{
icload.addStatement("icons.put($S, " + name + "Small)", name + "Small");
}
ichtype.addField(FieldSpec.builder(String.class, "all", Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer("$S", iconcAll.toString()).build());
ichtype.addStaticBlock(ichinit.build());
Fi.get(resources).walk(p -> {
if(!p.extEquals("png")) return;
@@ -78,7 +94,7 @@ public class AssetsProcess extends BaseProcessor{
if(SourceVersion.isKeyword(varname)) varname += "s";
type.addField(ClassName.bestGuess(dtype), varname, Modifier.STATIC, Modifier.PUBLIC);
load.addStatement(varname + " = ("+dtype+")arc.Core.atlas.drawable($S)", sfilen);
load.addStatement(varname + " = (" + dtype + ")arc.Core.atlas.drawable($S)", sfilen);
});
for(Element elem : elements){
@@ -101,12 +117,10 @@ public class AssetsProcess extends BaseProcessor{
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<String> names = new HashSet<>();
Fi.get(path).walk(p -> {
String fname = p.name();
String name = p.nameWithoutExtension();
if(names.contains(name)){
@@ -115,27 +129,21 @@ public class AssetsProcess extends BaseProcessor{
names.add(name);
}
if(SourceVersion.isKeyword(name)){
name = name + "s";
}
if(SourceVersion.isKeyword(name)) name += "s";
String filepath = path.substring(path.lastIndexOf("/") + 1) + "/" + fname;
String filepath = path.substring(path.lastIndexOf("/") + 1) + p.path().substring(p.path().lastIndexOf(path) + path.length());
String filename = "arc.Core.app.getType() != arc.Application.ApplicationType.iOS ? \"" + filepath + "\" : \"" + filepath.replace(".ogg", ".mp3")+"\"";
String filename = "\"" + filepath + "\"";
loadBegin.addStatement("arc.Core.assets.load(" + filename + ", " + rtype + ".class).loaded = a -> " + name + " = (" + rtype + ")a", 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.mock.Mock" + rtype.substring(rtype.lastIndexOf(".") + 1)+ "()").build());
type.addField(FieldSpec.builder(ClassName.bestGuess(rtype), name, Modifier.STATIC, Modifier.PUBLIC).initializer("new arc.audio." + 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.mock.Mock" + rtype.substring(rtype.lastIndexOf(".") + 1)+ "()").build());
type.addField(FieldSpec.builder(ClassName.bestGuess(rtype), "none", Modifier.STATIC, Modifier.PUBLIC).initializer("new arc.audio." + rtype.substring(rtype.lastIndexOf(".") + 1) + "()").build());
}
type.addMethod(loadBegin.build());
type.addMethod(dispose.build());
JavaFile.builder(packageName, type.build()).build().writeTo(BaseProcessor.filer);
}

View File

@@ -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<Object, Trees>{
String methodName;
MethodTree method;
boolean callSuperUsed;
@Override
public Object visitClass(ClassTree classTree, Trees trees){
Tree extendTree = classTree.getExtendsClause();
if(extendTree instanceof JCTypeApply){ //generic classes case
JCTypeApply generic = (JCTypeApply)extendTree;
extendTree = generic.clazz;
}
if(extendTree instanceof JCIdent){
JCIdent tree = (JCIdent)extendTree;
if(tree == null || tree.sym == null) return super.visitClass(classTree, trees);
com.sun.tools.javac.code.Scope members = tree.sym.members();
if(checkScope(members))
return super.visitClass(classTree, trees);
if(checkSuperTypes((ClassType)tree.type))
return super.visitClass(classTree, trees);
}
callSuperUsed = false;
return super.visitClass(classTree, trees);
}
public boolean checkSuperTypes(ClassType type){
if(type.supertype_field != null && type.supertype_field.tsym != null){
if(checkScope(type.supertype_field.tsym.members()))
return true;
else
return checkSuperTypes((ClassType)type.supertype_field);
}
return false;
}
@SuppressWarnings("unchecked")
public boolean checkScope(Scope members){
Iterable<Symbol> it;
try{
it = (Iterable<Symbol>)members.getClass().getMethod("getElements").invoke(members);
}catch(Throwable t){
try{
it = (Iterable<Symbol>)members.getClass().getMethod("getSymbols").invoke(members);
}catch(Exception e){
throw new RuntimeException(e);
}
}
for(Symbol s : it){
if(s instanceof MethodSymbol){
MethodSymbol ms = (MethodSymbol)s;
if(ms.getSimpleName().toString().equals(methodName)){
Annotation annotation = ms.getAnnotation(CallSuper.class);
if(annotation != null){
callSuperUsed = true;
return true;
}
}
}
}
return false;
}
@Override
public Object visitMethod(MethodTree methodTree, Trees trees){
if(methodTree.getName().toString().equals(methodName))
method = methodTree;
return super.visitMethod(methodTree, trees);
}
}
}

View File

@@ -18,6 +18,7 @@ public class LoadRegionProcessor extends BaseProcessor{
@Override
public void process(RoundEnvironment env) throws Exception{
TypeSpec.Builder regionClass = TypeSpec.classBuilder("ContentRegions")
.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class).addMember("value", "\"deprecation\"").build())
.addModifiers(Modifier.PUBLIC);
MethodSpec.Builder method = MethodSpec.methodBuilder("loadRegions")
.addParameter(tname("mindustry.ctype.MappableContent"), "content")
@@ -34,7 +35,7 @@ public class LoadRegionProcessor extends BaseProcessor{
}
for(Entry<Stype, Seq<Svar>> entry : fieldMap){
method.beginControlFlow("if(content instanceof $T)", entry.key.tname());
method.beginControlFlow("if(content instanceof $L)", entry.key.fullName());
for(Svar field : entry.value){
Load an = field.annotation(Load.class);
@@ -45,7 +46,7 @@ public class LoadRegionProcessor extends BaseProcessor{
//not an array
if(dims == 0){
method.addStatement("(($T)content).$L = $T.atlas.find($L$L)", entry.key.tname(), field.name(), Core.class, parse(an.value()), fallbackString);
method.addStatement("(($L)content).$L = $T.atlas.find($L$L)", entry.key.fullName(), field.name(), Core.class, parse(an.value()), fallbackString);
}else{
//is an array, create length string
int[] lengths = an.lengths();

View File

@@ -26,7 +26,8 @@ public class LogicStatementProcessor extends BaseProcessor{
MethodSpec.Builder reader = MethodSpec.methodBuilder("read")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(tname("mindustry.logic.LStatement"))
.addParameter(String[].class, "tokens");
.addParameter(String[].class, "tokens")
.addParameter(int.class, "length");
Seq<Stype> types = types(RegisterStatement.class);
@@ -43,9 +44,9 @@ public class LogicStatementProcessor extends BaseProcessor{
String name = c.annotation(RegisterStatement.class).value();
if(beganWrite){
writer.nextControlFlow("else if(obj instanceof $T)", c.mirror());
writer.nextControlFlow("else if(obj.getClass() == $T.class)", c.mirror());
}else{
writer.beginControlFlow("if(obj instanceof $T)", c.mirror());
writer.beginControlFlow("if(obj.getClass() == $T.class)", c.mirror());
beganWrite = true;
}
@@ -53,6 +54,7 @@ public class LogicStatementProcessor extends BaseProcessor{
writer.addStatement("out.append($S)", name);
Seq<Svar> fields = c.fields();
fields.addAll(c.superclass().fields());
String readSt = "if(tokens[0].equals($S))";
if(beganRead){
@@ -75,7 +77,7 @@ public class LogicStatementProcessor extends BaseProcessor{
"");
//reading primitives, strings and enums is supported; nothing else is
reader.addStatement("if(tokens.length > $L) result.$L = $L(tokens[$L])",
reader.addStatement("if(length > $L) result.$L = $L(tokens[$L])",
index + 1,
field.name(),
field.mirror().toString().equals("java.lang.String") ?

View File

@@ -0,0 +1,363 @@
package mindustry.annotations.remote;
import arc.struct.*;
import arc.util.io.*;
import com.squareup.javapoet.*;
import mindustry.annotations.Annotations.*;
import mindustry.annotations.*;
import mindustry.annotations.util.*;
import mindustry.annotations.util.TypeIOResolver.*;
import javax.lang.model.element.*;
import java.io.*;
import static mindustry.annotations.BaseProcessor.*;
/** Generates code for writing remote invoke packets on the client and server. */
public class CallGenerator{
/** Generates all classes in this list. */
public static void generate(ClassSerializer serializer, Seq<MethodEntry> methods) throws IOException{
//create builder
TypeSpec.Builder callBuilder = TypeSpec.classBuilder(RemoteProcess.callLocation).addModifiers(Modifier.PUBLIC);
MethodSpec.Builder register = MethodSpec.methodBuilder("registerPackets")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC);
//go through each method entry in this class
for(MethodEntry ent : methods){
//builder for the packet type
TypeSpec.Builder packet = TypeSpec.classBuilder(ent.packetClassName)
.addModifiers(Modifier.PUBLIC);
packet.superclass(tname("mindustry.net.Packet"));
//return the correct priority
if(ent.priority != PacketPriority.normal){
packet.addMethod(MethodSpec.methodBuilder("getPriority")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class).returns(int.class).addStatement("return $L", ent.priority.ordinal())
.build());
}
//implement read & write methods
packet.addMethod(makeWriter(ent, serializer));
packet.addMethod(makeReader(ent, serializer));
//generate handlers
if(ent.where.isClient){
packet.addMethod(writeHandleMethod(ent, false));
}
if(ent.where.isServer){
packet.addMethod(writeHandleMethod(ent, true));
}
//register packet
register.addStatement("mindustry.net.Net.registerPacket($L.$L::new)", packageName, ent.packetClassName);
//add fields to the type
for(Svar param : ent.element.params()){
packet.addField(param.tname(), param.name(), Modifier.PUBLIC);
}
//write the 'send event to all players' variant: always happens for clients, but only happens if 'all' is enabled on the server method
if(ent.where.isClient || ent.target.isAll){
writeCallMethod(callBuilder, ent, true, false);
}
//write the 'send event to one player' variant, which is only applicable on the server
if(ent.where.isServer && ent.target.isOne){
writeCallMethod(callBuilder, ent, false, false);
}
//write the forwarded method version
if(ent.where.isServer && ent.forward){
writeCallMethod(callBuilder, ent, true, true);
}
//write the completed packet class
JavaFile.builder(packageName, packet.build()).build().writeTo(BaseProcessor.filer);
}
callBuilder.addMethod(register.build());
//build and write resulting class
TypeSpec spec = callBuilder.build();
JavaFile.builder(packageName, spec).build().writeTo(BaseProcessor.filer);
}
private static MethodSpec makeWriter(MethodEntry ent, ClassSerializer serializer){
MethodSpec.Builder builder = MethodSpec.methodBuilder("write")
.addParameter(Writes.class, "WRITE")
.addModifiers(Modifier.PUBLIC).addAnnotation(Override.class);
Seq<Svar> params = ent.element.params();
for(int i = 0; i < params.size; i++){
//first argument is skipped as it is always the player caller
if(!ent.where.isServer && i == 0){
continue;
}
Svar var = params.get(i);
//name of parameter
String varName = var.name();
//name of parameter type
String typeName = var.mirror().toString();
//special case: method can be called from anywhere to anywhere
//thus, only write the player when the SERVER is writing data, since the client is the only one who reads the player anyway
boolean writePlayerSkipCheck = ent.where == Loc.both && i == 0;
if(writePlayerSkipCheck){ //write begin check
builder.beginControlFlow("if(mindustry.Vars.net.server())");
}
if(BaseProcessor.isPrimitive(typeName)){ //check if it's a primitive, and if so write it
builder.addStatement("WRITE.$L($L)", typeName.equals("boolean") ? "bool" : typeName.charAt(0) + "", varName);
}else{
//else, try and find a serializer
String ser = serializer.writers.get(typeName.replace("mindustry.gen.", ""), SerializerResolver.locate(ent.element.e, var.mirror(), true));
if(ser == null){ //make sure a serializer exists!
BaseProcessor.err("No method to write class type: '" + typeName + "'", var);
}
//add statement for writing it
builder.addStatement(ser + "(WRITE, " + varName + ")");
}
if(writePlayerSkipCheck){ //write end check
builder.endControlFlow();
}
}
return builder.build();
}
private static MethodSpec makeReader(MethodEntry ent, ClassSerializer serializer){
MethodSpec.Builder builder = MethodSpec.methodBuilder("read")
.addParameter(Reads.class, "READ")
.addModifiers(Modifier.PUBLIC).addAnnotation(Override.class);
Seq<Svar> params = ent.element.params();
//go through each parameter
for(int i = 0; i < params.size; i++){
Svar var = params.get(i);
//first argument is skipped as it is always the player caller
if(!ent.where.isServer && i == 0){
continue;
}
//special case: method can be called from anywhere to anywhere
//thus, only read the player when the CLIENT is receiving data, since the client is the only one who cares about the player anyway
boolean writePlayerSkipCheck = ent.where == Loc.both && i == 0;
if(writePlayerSkipCheck){ //write begin check
builder.beginControlFlow("if(mindustry.Vars.net.client())");
}
//full type name of parameter
String typeName = var.mirror().toString();
//name of parameter
String varName = var.name();
//capitalized version of type name for reading primitives
String pname = typeName.equals("boolean") ? "bool" : typeName.charAt(0) + "";
//write primitives automatically
if(BaseProcessor.isPrimitive(typeName)){
builder.addStatement("$L = READ.$L()", varName, pname);
}else{
//else, try and find a serializer
String ser = serializer.readers.get(typeName.replace("mindustry.gen.", ""), SerializerResolver.locate(ent.element.e, var.mirror(), false));
if(ser == null){ //make sure a serializer exists!
BaseProcessor.err("No read method to read class type '" + typeName + "' in method " + ent.targetMethod + "; " + serializer.readers, var);
}
//add statement for reading it
builder.addStatement("$L = $L(READ)", varName, ser);
}
if(writePlayerSkipCheck){ //write end check
builder.endControlFlow();
}
}
return builder.build();
}
/** Creates a specific variant for a method entry. */
private static void writeCallMethod(TypeSpec.Builder classBuilder, MethodEntry ent, boolean toAll, boolean forwarded){
Smethod elem = ent.element;
Seq<Svar> params = elem.params();
//create builder
MethodSpec.Builder method = MethodSpec.methodBuilder(elem.name() + (forwarded ? "__forward" : "")) //add except suffix when forwarding
.addModifiers(Modifier.STATIC)
.returns(void.class);
//forwarded methods aren't intended for use, and are not public
if(!forwarded){
method.addModifiers(Modifier.PUBLIC);
}
//validate client methods to make sure
if(ent.where.isClient){
if(params.isEmpty()){
BaseProcessor.err("Client invoke methods must have a first parameter of type Player", elem);
return;
}
if(!params.get(0).mirror().toString().contains("Player")){
BaseProcessor.err("Client invoke methods should have a first parameter of type Player", elem);
return;
}
}
//if toAll is false, it's a 'send to one player' variant, so add the player as a parameter
if(!toAll){
method.addParameter(ClassName.bestGuess("mindustry.net.NetConnection"), "playerConnection");
}
//add sender to ignore
if(forwarded){
method.addParameter(ClassName.bestGuess("mindustry.net.NetConnection"), "exceptConnection");
}
//call local method if applicable, shouldn't happen when forwarding method as that already happens by default
if(!forwarded && ent.local != Loc.none){
//add in local checks
if(ent.local != Loc.both){
method.beginControlFlow("if(" + getCheckString(ent.local) + " || !mindustry.Vars.net.active())");
}
//concatenate parameters
int index = 0;
StringBuilder results = new StringBuilder();
for(Svar var : params){
//special case: calling local-only methods uses the local player
if(index == 0 && ent.where == Loc.client){
results.append("mindustry.Vars.player");
}else{
results.append(var.name());
}
if(index != params.size - 1) results.append(", ");
index++;
}
//add the statement to call it
method.addStatement("$N." + elem.name() + "(" + results + ")",
((TypeElement)elem.up()).getQualifiedName().toString());
if(ent.local != Loc.both){
method.endControlFlow();
}
}
//start control flow to check if it's actually client/server so no netcode is called
method.beginControlFlow("if(" + getCheckString(ent.where) + ")");
//add statement to create packet from pool
method.addStatement("$1T packet = new $1T()", tname("mindustry.gen." + ent.packetClassName));
method.addTypeVariables(Seq.with(elem.e.getTypeParameters()).map(BaseProcessor::getTVN));
for(int i = 0; i < params.size; i++){
//first argument is skipped as it is always the player caller
if((!ent.where.isServer) && i == 0){
continue;
}
Svar var = params.get(i);
method.addParameter(var.tname(), var.name());
//name of parameter
String varName = var.name();
//special case: method can be called from anywhere to anywhere
//thus, only write the player when the SERVER is writing data, since the client is the only one who reads it
boolean writePlayerSkipCheck = ent.where == Loc.both && i == 0;
if(writePlayerSkipCheck){ //write begin check
method.beginControlFlow("if(mindustry.Vars.net.server())");
}
method.addStatement("packet.$L = $L", varName, varName);
if(writePlayerSkipCheck){ //write end check
method.endControlFlow();
}
}
String sendString;
if(forwarded){ //forward packet
if(!ent.local.isClient){ //if the client doesn't get it called locally, forward it back after validation
sendString = "mindustry.Vars.net.send(";
}else{
sendString = "mindustry.Vars.net.sendExcept(exceptConnection, ";
}
}else if(toAll){ //send to all players / to server
sendString = "mindustry.Vars.net.send(";
}else{ //send to specific client from server
sendString = "playerConnection.send(";
}
//send the actual packet
method.addStatement(sendString + "packet, " + (!ent.unreliable) + ")");
//end check for server/client
method.endControlFlow();
//add method to class, finally
classBuilder.addMethod(method.build());
}
private static String getCheckString(Loc loc){
return
loc.isClient && loc.isServer ? "mindustry.Vars.net.server() || mindustry.Vars.net.client()" :
loc.isClient ? "mindustry.Vars.net.client()" :
loc.isServer ? "mindustry.Vars.net.server()" : "false";
}
/** Generates handleServer / handleClient methods. */
public static MethodSpec writeHandleMethod(MethodEntry ent, boolean isClient){
//create main method builder
MethodSpec.Builder builder = MethodSpec.methodBuilder(isClient ? "handleClient" : "handleServer")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.returns(void.class);
Smethod elem = ent.element;
Seq<Svar> params = elem.params();
if(!isClient){
//add player parameter
builder.addParameter(ClassName.get("mindustry.net", "NetConnection"), "con");
//skip if player is invalid
builder.beginControlFlow("if(con.player == null || con.kicked)");
builder.addStatement("return");
builder.endControlFlow();
//make sure to use the actual player who sent the packet
builder.addStatement("mindustry.gen.Player player = con.player");
}
//execute the relevant method before the forward
//if it throws a ValidateException, the method won't be forwarded
builder.addStatement("$N." + elem.name() + "(" + params.toString(", ", s -> s.name()) + ")", ((TypeElement)elem.up()).getQualifiedName().toString());
//call forwarded method, don't forward on the client reader
if(ent.forward && ent.where.isServer && !isClient){
//call forwarded method
builder.addStatement("$L.$L.$L__forward(con, $L)", packageName, ent.className, elem.name(), params.toString(", ", s -> s.name()));
}
return builder.build();
}
}

View File

@@ -1,15 +0,0 @@
package mindustry.annotations.remote;
import java.util.ArrayList;
/** Represents a class witha list method entries to include in it. */
public class ClassEntry{
/** All methods in this generated class. */
public final ArrayList<MethodEntry> methods = new ArrayList<>();
/** Simple class name. */
public final String name;
public ClassEntry(String name){
this.name = name;
}
}

View File

@@ -1,8 +1,7 @@
package mindustry.annotations.remote;
import mindustry.annotations.Annotations.*;
import javax.lang.model.element.ExecutableElement;
import mindustry.annotations.util.*;
/** Class that repesents a remote method to be constructed and put into a class. */
public class MethodEntry{
@@ -10,6 +9,8 @@ public class MethodEntry{
public final String className;
/** Fully qualified target method to call. */
public final String targetMethod;
/** Simple name of the generated packet class. */
public final String packetClassName;
/** Whether this method can be called on a client/server. */
public final Loc where;
/**
@@ -26,12 +27,13 @@ public class MethodEntry{
/** Unique method ID. */
public final int id;
/** The element method associated with this entry. */
public final ExecutableElement element;
public final Smethod element;
/** The assigned packet priority. Only used in clients. */
public final PacketPriority priority;
public MethodEntry(String className, String targetMethod, Loc where, Variant target,
Loc local, boolean unreliable, boolean forward, int id, ExecutableElement element, PacketPriority priority){
public MethodEntry(String className, String targetMethod, String packetClassName, Loc where, Variant target,
Loc local, boolean unreliable, boolean forward, int id, Smethod element, PacketPriority priority){
this.packetClassName = packetClassName;
this.className = className;
this.forward = forward;
this.targetMethod = targetMethod;

View File

@@ -1,7 +1,7 @@
package mindustry.annotations.remote;
import arc.struct.*;
import com.squareup.javapoet.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.annotations.*;
import mindustry.annotations.util.*;
@@ -9,7 +9,6 @@ import mindustry.annotations.util.TypeIOResolver.*;
import javax.annotation.processing.*;
import javax.lang.model.element.*;
import java.util.*;
/** The annotation processor for generating remote method call code. */
@@ -18,106 +17,58 @@ import java.util.*;
"mindustry.annotations.Annotations.TypeIOHandler"
})
public class RemoteProcess extends BaseProcessor{
/** Maximum size of each event packet. */
public static final int maxPacketSize = 8192;
/** Warning on top of each autogenerated file. */
public static final String autogenWarning = "Autogenerated file. Do not modify!\n";
/** Name of class that handles reading and invoking packets on the server. */
private static final String readServerName = "RemoteReadServer";
/** Name of class that handles reading and invoking packets on the client. */
private static final String readClientName = "RemoteReadClient";
/** Simple class name of generated class name. */
private static final String callLocation = "Call";
//class serializers
private ClassSerializer serializer;
//all elements with the Remote annotation
private Seq<Smethod> elements;
//map of all classes to generate by name
private HashMap<String, ClassEntry> classMap;
//list of all method entries
private Seq<MethodEntry> methods;
//list of all method entries
private Seq<ClassEntry> classes;
{
rounds = 2;
}
public static final String callLocation = "Call";
@Override
public void process(RoundEnvironment roundEnv) throws Exception{
//round 1: find all annotations, generate *writers*
if(round == 1){
//get serializers
serializer = TypeIOResolver.resolve(this);
//last method ID used
int lastMethodID = 0;
//find all elements with the Remote annotation
elements = methods(Remote.class);
//map of all classes to generate by name
classMap = new HashMap<>();
//list of all method entries
methods = new Seq<>();
//list of all method entries
classes = new Seq<>();
//get serializers
//class serializers
ClassSerializer serializer = TypeIOResolver.resolve(this);
//last method ID used
int lastMethodID = 0;
//find all elements with the Remote annotation
//all elements with the Remote annotation
Seq<Smethod> elements = methods(Remote.class);
//list of all method entries
Seq<MethodEntry> methods = new Seq<>();
Seq<Smethod> orderedElements = elements.copy();
orderedElements.sortComparing(Object::toString);
Seq<Smethod> orderedElements = elements.copy();
orderedElements.sortComparing(Selement::toString);
//create methods
for(Smethod element : orderedElements){
Remote annotation = element.annotation(Remote.class);
//create methods
for(Smethod element : orderedElements){
Remote annotation = element.annotation(Remote.class);
//check for static
if(!element.is(Modifier.STATIC) || !element.is(Modifier.PUBLIC)){
err("All @Remote methods must be public and static: ", element);
}
//can't generate none methods
if(annotation.targets() == Loc.none){
err("A @Remote method's targets() cannot be equal to 'none':", element);
}
//get and create class entry if needed
if(!classMap.containsKey(callLocation)){
ClassEntry clas = new ClassEntry(callLocation);
classMap.put(callLocation, clas);
classes.add(clas);
}
ClassEntry entry = classMap.get(callLocation);
//create and add entry
MethodEntry method = new MethodEntry(entry.name, BaseProcessor.getMethodName(element.e), annotation.targets(), annotation.variants(),
annotation.called(), annotation.unreliable(), annotation.forward(), lastMethodID++, element.e, annotation.priority());
entry.methods.add(method);
methods.add(method);
//check for static
if(!element.is(Modifier.STATIC) || !element.is(Modifier.PUBLIC)){
err("All @Remote methods must be public and static", element);
}
//create read/write generators
RemoteWriteGenerator writegen = new RemoteWriteGenerator(serializer);
//can't generate none methods
if(annotation.targets() == Loc.none){
err("A @Remote method's targets() cannot be equal to 'none'", element);
}
//generate the methods to invoke (write)
writegen.generateFor(classes, packageName);
}else if(round == 2){ //round 2: generate all *readers*
RemoteReadGenerator readgen = new RemoteReadGenerator(serializer);
String packetName = Strings.capitalize(element.name()) + "CallPacket";
int[] index = {1};
//generate server readers
readgen.generateFor(methods.select(method -> method.where.isClient), readServerName, packageName, true);
//generate client readers
readgen.generateFor(methods.select(method -> method.where.isServer), readClientName, packageName, false);
while(methods.contains(m -> m.packetClassName.equals(packetName + (index[0] == 1 ? "" : index[0])))){
index[0] ++;
}
//create class for storing unique method hash
TypeSpec.Builder hashBuilder = TypeSpec.classBuilder("MethodHash").addModifiers(Modifier.PUBLIC);
hashBuilder.addJavadoc(autogenWarning);
hashBuilder.addField(FieldSpec.builder(int.class, "HASH", Modifier.STATIC, Modifier.PUBLIC, Modifier.FINAL)
.initializer("$1L", Arrays.hashCode(methods.map(m -> m.element).toArray())).build());
//create and add entry
MethodEntry method = new MethodEntry(
callLocation, BaseProcessor.getMethodName(element.e), packetName + (index[0] == 1 ? "" : index[0]),
annotation.targets(), annotation.variants(),
annotation.called(), annotation.unreliable(), annotation.forward(), lastMethodID++,
element, annotation.priority()
);
//build and write resulting hash class
TypeSpec spec = hashBuilder.build();
JavaFile.builder(packageName, spec).build().writeTo(BaseProcessor.filer);
methods.add(method);
}
//generate the methods to invoke, as well as the packet classes
CallGenerator.generate(serializer, methods);
}
}

View File

@@ -1,129 +0,0 @@
package mindustry.annotations.remote;
import arc.struct.*;
import arc.util.io.*;
import com.squareup.javapoet.*;
import mindustry.annotations.*;
import mindustry.annotations.util.TypeIOResolver.*;
import javax.lang.model.element.*;
/** Generates code for reading remote invoke packets on the client and server. */
public class RemoteReadGenerator{
private final ClassSerializer serializers;
/** Creates a read generator that uses the supplied serializer setup. */
public RemoteReadGenerator(ClassSerializer serializers){
this.serializers = serializers;
}
/**
* Generates a class for reading remote invoke packets.
* @param entries List of methods to use.
* @param className Simple target class name.
* @param packageName Full target package name.
* @param needsPlayer Whether this read method requires a reference to the player sender.
*/
public void generateFor(Seq<MethodEntry> entries, String className, String packageName, boolean needsPlayer) throws Exception{
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className).addModifiers(Modifier.PUBLIC);
classBuilder.addJavadoc(RemoteProcess.autogenWarning);
//create main method builder
MethodSpec.Builder readMethod = MethodSpec.methodBuilder("readPacket")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addParameter(Reads.class, "read") //buffer to read form
.addParameter(int.class, "id") //ID of method type to read
.returns(void.class);
if(needsPlayer){
//add player parameter
readMethod.addParameter(ClassName.get(packageName, "Player"), "player");
}
CodeBlock.Builder readBlock = CodeBlock.builder(); //start building block of code inside read method
boolean started = false; //whether an if() statement has been written yet
for(MethodEntry entry : entries){
//write if check for this entry ID
if(!started){
started = true;
readBlock.beginControlFlow("if(id == " + entry.id + ")");
}else{
readBlock.nextControlFlow("else if(id == " + entry.id + ")");
}
readBlock.beginControlFlow("try");
//concatenated list of variable names for method invocation
StringBuilder varResult = new StringBuilder();
//go through each parameter
for(int i = 0; i < entry.element.getParameters().size(); i++){
VariableElement var = entry.element.getParameters().get(i);
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
String typeName = var.asType().toString();
//name of parameter
String varName = var.getSimpleName().toString();
//captialized version of type name for reading primitives
String pname = typeName.equals("boolean") ? "bool" : typeName.charAt(0) + "";
//write primitives automatically
if(BaseProcessor.isPrimitive(typeName)){
readBlock.addStatement("$L $L = read.$L()", typeName, varName, pname);
}else{
//else, try and find a serializer
String ser = serializers.readers.get(typeName.replace("mindustry.gen.", ""), SerializerResolver.locate(entry.element, var.asType(), false));
if(ser == null){ //make sure a serializer exists!
BaseProcessor.err("No read method to read class type '" + typeName + "' in method " + entry.targetMethod + "; " + serializers.readers, var);
return;
}
//add statement for reading it
readBlock.addStatement(typeName + " " + varName + " = " + ser + "(read)");
}
//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(", ");
}
}
//execute the relevant method before the forward
//if it throws a ValidateException, the method won't be forwarded
readBlock.addStatement("$N." + entry.element.getSimpleName() + "(" + varResult.toString() + ")", ((TypeElement)entry.element.getEnclosingElement()).getQualifiedName().toString());
//call forwarded method, don't forward on the client reader
if(entry.forward && entry.where.isServer && needsPlayer){
//call forwarded method
readBlock.addStatement(packageName + "." + entry.className + "." + entry.element.getSimpleName() +
"__forward(player.con" + (varResult.length() == 0 ? "" : ", ") + varResult.toString() + ")");
}
readBlock.nextControlFlow("catch (java.lang.Exception e)");
readBlock.addStatement("throw new java.lang.RuntimeException(\"Failed to to read remote method '" + entry.element.getSimpleName() + "'!\", e)");
readBlock.endControlFlow();
}
//end control flow if necessary
if(started){
readBlock.nextControlFlow("else");
readBlock.addStatement("throw new $1N(\"Invalid read method ID: \" + id + \"\")", RuntimeException.class.getName()); //handle invalid method IDs
readBlock.endControlFlow();
}
//add block and method to class
readMethod.addCode(readBlock.build());
classBuilder.addMethod(readMethod.build());
//build and write resulting class
TypeSpec spec = classBuilder.build();
JavaFile.builder(packageName, spec).build().writeTo(BaseProcessor.filer);
}
}

View File

@@ -1,228 +0,0 @@
package mindustry.annotations.remote;
import arc.struct.*;
import arc.util.io.*;
import com.squareup.javapoet.*;
import mindustry.annotations.Annotations.*;
import mindustry.annotations.*;
import mindustry.annotations.util.TypeIOResolver.*;
import javax.lang.model.element.*;
import java.io.*;
/** Generates code for writing remote invoke packets on the client and server. */
public class RemoteWriteGenerator{
private final ClassSerializer serializers;
/** Creates a write generator that uses the supplied serializer setup. */
public RemoteWriteGenerator(ClassSerializer serializers){
this.serializers = serializers;
}
/** Generates all classes in this list. */
public void generateFor(Seq<ClassEntry> entries, String packageName) throws IOException{
for(ClassEntry entry : entries){
//create builder
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(entry.name).addModifiers(Modifier.PUBLIC);
classBuilder.addJavadoc(RemoteProcess.autogenWarning);
//add temporary write buffer
classBuilder.addField(FieldSpec.builder(ReusableByteOutStream.class, "OUT", Modifier.STATIC, Modifier.PRIVATE, Modifier.FINAL)
.initializer("new ReusableByteOutStream($L)", RemoteProcess.maxPacketSize).build());
//add writer for that buffer
classBuilder.addField(FieldSpec.builder(Writes.class, "WRITE", Modifier.STATIC, Modifier.PRIVATE, Modifier.FINAL)
.initializer("new Writes(new $T(OUT))", DataOutputStream.class).build());
//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.where.isClient || methodEntry.target.isAll){
writeMethodVariant(classBuilder, methodEntry, true, 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);
}
}
//build and write resulting class
TypeSpec spec = classBuilder.build();
JavaFile.builder(packageName, spec).build().writeTo(BaseProcessor.filer);
}
}
/** Creates a specific variant for a method entry. */
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() + (forwarded ? "__forward" : "")) //add except suffix when forwarding
.addModifiers(Modifier.STATIC)
.returns(void.class);
//forwarded methods aren't intended for use, and are not public
if(!forwarded){
method.addModifiers(Modifier.PUBLIC);
}
//validate client methods to make sure
if(methodEntry.where.isClient){
if(elem.getParameters().isEmpty()){
BaseProcessor.err("Client invoke methods must have a first parameter of type Player", elem);
return;
}
if(!elem.getParameters().get(0).asType().toString().contains("Player")){
BaseProcessor.err("Client invoke methods should have a first parameter of type Player", elem);
return;
}
}
//if toAll is false, it's a 'send to one player' variant, so add the player as a parameter
if(!toAll){
method.addParameter(ClassName.bestGuess("mindustry.net.NetConnection"), "playerConnection");
}
//add sender to ignore
if(forwarded){
method.addParameter(ClassName.bestGuess("mindustry.net.NetConnection"), "exceptConnection");
}
//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) + " || !mindustry.Vars.net.active())");
}
//concatenate parameters
int index = 0;
StringBuilder results = new StringBuilder();
for(VariableElement var : elem.getParameters()){
//special case: calling local-only methods uses the local player
if(index == 0 && methodEntry.where == Loc.client){
results.append("mindustry.Vars.player");
}else{
results.append(var.getSimpleName());
}
if(index != elem.getParameters().size() - 1) results.append(", ");
index++;
}
//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(" + getCheckString(methodEntry.where) + ")");
//add statement to create packet from pool
method.addStatement("$1N packet = $2N.obtain($1N.class, $1N::new)", "mindustry.net.Packets.InvokePacket", "arc.util.pooling.Pools");
//assign priority
method.addStatement("packet.priority = (byte)" + methodEntry.priority.ordinal());
//assign method ID
method.addStatement("packet.type = (byte)" + methodEntry.id);
//reset stream
method.addStatement("OUT.reset()");
method.addTypeVariables(Seq.with(elem.getTypeParameters()).map(BaseProcessor::getTVN));
for(int i = 0; i < elem.getParameters().size(); i++){
//first argument is skipped as it is always the player caller
if((!methodEntry.where.isServer/* || methodEntry.mode == Loc.both*/) && i == 0){
continue;
}
VariableElement var = elem.getParameters().get(i);
try{
//add parameter to method
method.addParameter(TypeName.get(var.asType()), var.getSimpleName().toString());
}catch(Throwable t){
throw new RuntimeException("Error parsing method " + methodEntry.targetMethod);
}
//name of parameter
String varName = var.getSimpleName().toString();
//name of parameter type
String typeName = var.asType().toString();
//captialized version of type name for writing primitives
String capName = typeName.equals("byte") ? "" : Character.toUpperCase(typeName.charAt(0)) + typeName.substring(1);
//special case: method can be called from anywhere to anywhere
//thus, only write the player when the SERVER is writing data, since the client is the only one who reads it
boolean writePlayerSkipCheck = methodEntry.where == Loc.both && i == 0;
if(writePlayerSkipCheck){ //write begin check
method.beginControlFlow("if(mindustry.Vars.net.server())");
}
if(BaseProcessor.isPrimitive(typeName)){ //check if it's a primitive, and if so write it
method.addStatement("WRITE.$L($L)", typeName.equals("boolean") ? "bool" : typeName.charAt(0) + "", varName);
}else{
//else, try and find a serializer
String ser = serializers.writers.get(typeName.replace("mindustry.gen.", ""), SerializerResolver.locate(elem, var.asType(), true));
if(ser == null){ //make sure a serializer exists!
BaseProcessor.err("No @WriteClass method to write class type: '" + typeName + "'", var);
return;
}
//add statement for writing it
method.addStatement(ser + "(WRITE, " + varName + ")");
}
if(writePlayerSkipCheck){ //write end check
method.endControlFlow();
}
}
//assign packet bytes
method.addStatement("packet.bytes = OUT.getBytes()");
//assign packet length
method.addStatement("packet.length = OUT.size()");
String sendString;
if(forwarded){ //forward packet
if(!methodEntry.local.isClient){ //if the client doesn't get it called locally, forward it back after validation
sendString = "mindustry.Vars.net.send(";
}else{
sendString = "mindustry.Vars.net.sendExcept(exceptConnection, ";
}
}else if(toAll){ //send to all players / to server
sendString = "mindustry.Vars.net.send(";
}else{ //send to specific client from server
sendString = "playerConnection.send(";
}
//send the actual packet
method.addStatement(sendString + "packet, " +
(methodEntry.unreliable ? "mindustry.net.Net.SendMode.udp" : "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 ? "mindustry.Vars.net.server() || mindustry.Vars.net.client()" :
loc.isClient ? "mindustry.Vars.net.client()" :
loc.isServer ? "mindustry.Vars.net.server()" : "false";
}
}

View File

@@ -1,7 +1,7 @@
package mindustry.annotations.util;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import com.squareup.javapoet.*;
import com.sun.tools.javac.code.Attribute.*;
import mindustry.annotations.*;
@@ -19,7 +19,8 @@ public class Selement<T extends Element>{
this.e = e;
}
public @Nullable String doc(){
@Nullable
public String doc(){
return BaseProcessor.elementu.getDocComment(e);
}

View File

@@ -1,7 +1,6 @@
package mindustry.annotations.util;
import com.sun.source.tree.*;
import com.sun.tools.javac.tree.JCTree.*;
import mindustry.annotations.*;
import javax.lang.model.element.*;
@@ -16,10 +15,6 @@ public class Svar extends Selement<VariableElement>{
return up().asType().toString() + "#" + super.toString().replace("mindustry.gen.", "");
}
public JCVariableDecl jtree(){
return (JCVariableDecl)BaseProcessor.elementu.getTree(e);
}
public Stype enclosingType(){
return new Stype((TypeElement)up());
}

View File

@@ -1,10 +1,13 @@
#Maps entity names to IDs. Autogenerated.
alpha=0
arkyid=29
atrax=1
beta=30
block=2
corvus=24
flare=3
gamma=31
mace=4
mega=5
mindustry.entities.comp.BuildingComp=6
@@ -14,9 +17,12 @@ mindustry.entities.comp.EffectStateComp=9
mindustry.entities.comp.FireComp=10
mindustry.entities.comp.LaunchCoreComp=11
mindustry.entities.comp.PlayerComp=12
mindustry.entities.comp.PosTeam=27
mindustry.entities.comp.PosTeamDef=28
mindustry.entities.comp.PuddleComp=13
mindustry.type.Weather.WeatherStateComp=14
mindustry.world.blocks.campaign.LaunchPad.LaunchPayloadComp=15
mindustry.world.blocks.campaign.PayloadLaunchPad.LargeLaunchPayloadComp=34
mindustry.world.blocks.defense.ForceProjector.ForceDrawComp=22
mono=16
nova=17
@@ -24,6 +30,8 @@ oct=26
poly=18
pulsar=19
quad=23
quasar=32
risso=20
spiroct=21
toxopid=33
vela=25

View File

@@ -1 +0,0 @@
{fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:health,type:float},{name:isShooting,type:boolean},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:payloads,type:arc.struct.Seq<mindustry.world.blocks.payloads.Payload>},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View File

@@ -1 +0,0 @@
{fields:[{name:ammo,type:float,size:4},{name:armor,type:float,size:4},{name:controller,type:mindustry.entities.units.UnitController,size:-1},{name:deactivated,type:boolean,size:1},{name:elevation,type:float,size:4},{name:health,type:float,size:4},{name:isShooting,type:boolean,size:1},{name:mounts,type:"mindustry.entities.units.WeaponMount[]",size:-1},{name:rotation,type:float,size:4},{name:shield,type:float,size:4},{name:spawnedByCore,type:boolean,size:1},{name:stack,type:mindustry.type.ItemStack,size:-1},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>,size:-1},{name:team,type:mindustry.game.Team,size:-1},{name:type,type:mindustry.type.UnitType,size:-1},{name:x,type:float,size:4},{name:y,type:float,size:4}]}

View File

@@ -1 +0,0 @@
{version:1,fields:[{name:ammo,type:float,size:4},{name:armor,type:float,size:4},{name:controller,type:mindustry.entities.units.UnitController,size:-1},{name:elevation,type:float,size:4},{name:health,type:float,size:4},{name:isShooting,type:boolean,size:1},{name:mounts,type:"mindustry.entities.units.WeaponMount[]",size:-1},{name:rotation,type:float,size:4},{name:shield,type:float,size:4},{name:spawnedByCore,type:boolean,size:1},{name:stack,type:mindustry.type.ItemStack,size:-1},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>,size:-1},{name:team,type:mindustry.game.Team,size:-1},{name:type,type:mindustry.type.UnitType,size:-1},{name:x,type:float,size:4},{name:y,type:float,size:4}]}

View File

@@ -1 +0,0 @@
{fields:[{name:ammo,type:float,size:4},{name:armor,type:float,size:4},{name:baseRotation,type:float,size:4},{name:controller,type:mindustry.entities.units.UnitController,size:-1},{name:deactivated,type:boolean,size:1},{name:elevation,type:float,size:4},{name:health,type:float,size:4},{name:isShooting,type:boolean,size:1},{name:mounts,type:"mindustry.entities.units.WeaponMount[]",size:-1},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>,size:-1},{name:rotation,type:float,size:4},{name:shield,type:float,size:4},{name:spawnedByCore,type:boolean,size:1},{name:stack,type:mindustry.type.ItemStack,size:-1},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>,size:-1},{name:team,type:mindustry.game.Team,size:-1},{name:type,type:mindustry.type.UnitType,size:-1},{name:x,type:float,size:4},{name:y,type:float,size:4}]}

View File

@@ -1 +0,0 @@
{fields:[{name:ammo,type:float,size:4},{name:armor,type:float,size:4},{name:controller,type:mindustry.entities.units.UnitController,size:-1},{name:deactivated,type:boolean,size:1},{name:elevation,type:float,size:4},{name:health,type:float,size:4},{name:isShooting,type:boolean,size:1},{name:mineTile,type:mindustry.world.Tile,size:-1},{name:mounts,type:"mindustry.entities.units.WeaponMount[]",size:-1},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>,size:-1},{name:rotation,type:float,size:4},{name:shield,type:float,size:4},{name:spawnedByCore,type:boolean,size:1},{name:stack,type:mindustry.type.ItemStack,size:-1},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>,size:-1},{name:team,type:mindustry.game.Team,size:-1},{name:type,type:mindustry.type.UnitType,size:-1},{name:x,type:float,size:4},{name:y,type:float,size:4}]}

View File

@@ -1 +0,0 @@
{fields:[{name:ammo,type:float,size:4},{name:armor,type:float,size:4},{name:baseRotation,type:float,size:4},{name:controller,type:mindustry.entities.units.UnitController,size:-1},{name:deactivated,type:boolean,size:1},{name:elevation,type:float,size:4},{name:health,type:float,size:4},{name:isShooting,type:boolean,size:1},{name:mounts,type:"mindustry.entities.units.WeaponMount[]",size:-1},{name:rotation,type:float,size:4},{name:shield,type:float,size:4},{name:spawnedByCore,type:boolean,size:1},{name:stack,type:mindustry.type.ItemStack,size:-1},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>,size:-1},{name:team,type:mindustry.game.Team,size:-1},{name:type,type:mindustry.type.UnitType,size:-1},{name:x,type:float,size:4},{name:y,type:float,size:4}]}

View File

@@ -1 +0,0 @@
{version:1,fields:[{name:ammo,type:float,size:4},{name:armor,type:float,size:4},{name:baseRotation,type:float,size:4},{name:controller,type:mindustry.entities.units.UnitController,size:-1},{name:elevation,type:float,size:4},{name:health,type:float,size:4},{name:isShooting,type:boolean,size:1},{name:mounts,type:"mindustry.entities.units.WeaponMount[]",size:-1},{name:rotation,type:float,size:4},{name:shield,type:float,size:4},{name:spawnedByCore,type:boolean,size:1},{name:stack,type:mindustry.type.ItemStack,size:-1},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>,size:-1},{name:team,type:mindustry.game.Team,size:-1},{name:type,type:mindustry.type.UnitType,size:-1},{name:x,type:float,size:4},{name:y,type:float,size:4}]}

View File

@@ -0,0 +1 @@
{fields:[{name:lifetime,type:float},{name:payload,type:mindustry.world.blocks.payloads.Payload},{name:team,type:mindustry.game.Team},{name:time,type:float},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{fields:[{name:team,type:mindustry.game.Team},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:1,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:1,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:1,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:2,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:3,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:4,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:5,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:2,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:3,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:4,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:5,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:2,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:3,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:4,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:5,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:1,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:2,fields:[{name:ammo,type:float},{name:armor,type:float},{name:baseRotation,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:3,fields:[{name:ammo,type:float},{name:armor,type:float},{name:baseRotation,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:4,fields:[{name:ammo,type:float},{name:armor,type:float},{name:baseRotation,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:5,fields:[{name:ammo,type:float},{name:armor,type:float},{name:baseRotation,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:2,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:payloads,type:arc.struct.Seq<mindustry.world.blocks.payloads.Payload>},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:3,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:payloads,type:arc.struct.Seq<mindustry.world.blocks.payloads.Payload>},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:x,type:float},{name:y,type:float}]}

Some files were not shown because too many files have changed in this diff Show More