Merge branch 'master' into port-field

This commit is contained in:
Anuken
2025-02-08 19:36:26 -05:00
committed by GitHub
2681 changed files with 151338 additions and 45271 deletions

View File

@@ -1,30 +0,0 @@
---
name: Bug report
about: Create a report to help fix an issue.
title: ''
labels: bug
assignees: ''
---
**Platform**: *Android/iOS/Mac/Windows/Linux*
**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.*
**Steps to reproduce**: *How you happened across the issue, and what exactly you did to make the bug happen.*
**Link(s) to mod(s) used**: *The mod repositories or zip files that are related to the issue, if applicable.*
**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 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.**

76
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,76 @@
name: Bug report
description: The type of device you were playing on
labels: ["bug"]
body:
- type: dropdown
id: platform
attributes:
label: Platforms
description: On what platforms do you know the bug happens?
multiple: false
options:
- Android
- iOS
- Mac
- Windows
- Linux
validations:
required: true
- type: input
id: build
attributes:
label: Build
description: The build number under the title in the main menu.
placeholder: LATEST IS NOT A VERSION, I NEED THE EXACT BUILD NUMBER OF YOUR GAME.
validations:
required: true
- type: textarea
id: issue
attributes:
label: Issue
description: Explain your issue in detail.
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Steps to reproduce
description: How you happened across the issue, and what exactly you did to make the bug happen.
validations:
required: true
- type: textarea
id: mods
attributes:
label: Mods used
description: The mod repositories or zip files that are related to the issue, if applicable.
validations:
required: false
- type: textarea
id: save-file
attributes:
label: Save file
description: The (zipped) save file you were playing on when the bug happened. If this happened in the campaign, specify the sector, and attach the file you get from Settings -> Game Data -> Export Data. For custom games, attach the .msav file exported from the save dialog, zipped.
placeholder: THIS IS REQUIRED FOR ANY ISSUE HAPPENING IN-GAME OR IN MULTIPLAYER, REGARDLESS OF WHETHER YOU THINK IT HAPPENS EVERYWHERE. DO NOT 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.
validations:
required: false
- type: textarea
id: logs
attributes:
label: (Crash) logs
description: Either crash reports from the crash folder, or the file you get when you go into Settings -> Game Data -> Export Crash logs.
placeholder: REQUIRED if you are reporting a crash.
validations:
required: false
- type: checkboxes
id: agreement
attributes:
label: Submission
description: Check the boxes to confirm that you have read the lines below.
options:
- label: I have updated to the latest release (https://github.com/Anuken/Mindustry/releases) to make sure my issue has not been fixed.
required: true
- label: I have searched the closed and open issues to make sure that this problem has not already been reported.
required: true
- label: "I am not using Foo's Client, and have made sure the bug is not caused by mods I have installed."
required: true

5
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,5 @@
If your pull request is **not** translation or serverlist-related, read the list of requirements below and check each box:
- [ ] I have read the [contribution guidelines](https://github.com/Anuken/Mindustry/blob/master/CONTRIBUTING.md).
- [ ] I have ensured that my code compiles, if applicable.
- [ ] I have ensured that any new features in this PR function correctly in-game, if applicable.

View File

@@ -5,48 +5,78 @@ on:
tags: tags:
- 'v*' - 'v*'
permissions: {}
jobs: jobs:
buildJava14: deploy:
permissions:
contents: write # for release creation (svenstaro/upload-release-action)
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up JDK 14 - name: Set up JDK 17
uses: actions/setup-java@v1 uses: actions/setup-java@v1
with: with:
java-version: 14 java-version: 17
- name: Set env - name: Set env
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Add Arc release
run: |
git config --global user.email "actions@github.com"
git config --global user.name "Github Actions"
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: Update JITpack repo
run: |
cd ../
cp -r ./Mindustry ./MindustryJitpack
cd MindustryJitpack
git config --global user.name "Github Actions"
git config --global user.email "actions@github.com"
git clone --depth 1 https://github.com/Anuken/MindustryJitpack.git
rm -rf .git
cp -r ./MindustryJitpack/.git ./.git
rm -rf MindustryJitpack
rm -rf .github
rm README.md
git add .
git commit --allow-empty -m "Updating"
git push https://Anuken:${{ secrets.API_TOKEN_GITHUB }}@github.com/Anuken/MindustryJitpack
git tag ${RELEASE_VERSION}
git push https://Anuken:${{ secrets.API_TOKEN_GITHUB }}@github.com/Anuken/MindustryJitpack
cd ../Mindustry
- name: Create artifacts - name: Create artifacts
run: | run: |
./gradlew desktop:dist server:dist core:javadoc -Pbuildversion=${RELEASE_VERSION:1} ./gradlew desktop:dist server:dist core:mergedJavadoc -Pbuildversion=${RELEASE_VERSION:1}
- name: Update docs - name: Update docs
run: | run: |
cd ../ cd ../
git config --global user.email "cli@github.com" git config --global user.email "cli@github.com"
git config --global user.name "Github Actions" git config --global user.name "Github Actions"
git clone --depth=1 https://github.com/MindustryGame/docs.git git clone --depth=1 https://github.com/MindustryGame/docs.git
cp -a Mindustry/core/build/docs/javadoc/. docs/ cd docs
find . -maxdepth 1 ! -name ".git" ! -name . -exec rm -r {} \;
cd ../
cp -a Mindustry/core/build/javadoc/. docs/
cd docs cd docs
git add . git add .
git commit -m "Update ${RELEASE_VERSION:1}" git commit --allow-empty -m "Update ${RELEASE_VERSION:1}"
git push https://Anuken:${{ secrets.API_TOKEN_GITHUB }}@github.com/MindustryGame/docs git push https://Anuken:${{ secrets.API_TOKEN_GITHUB }}@github.com/MindustryGame/docs
cd ../Mindustry cd ../Mindustry
- 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: Update F-Droid build string - name: Update F-Droid build string
run: | run: |
git clone --depth=1 --branch=master https://github.com/Anuken/MindustryBuilds ../MindustryBuilds git clone --depth=1 --branch=master https://github.com/Anuken/MindustryBuilds ../MindustryBuilds
cd ../MindustryBuilds cd ../MindustryBuilds
echo "Updating version to ${RELEASE_VERSION:1}" echo "Updating version to ${RELEASE_VERSION:1}"
echo versionName=6-fdroid-${RELEASE_VERSION:1}$'\n'versionCode=${RELEASE_VERSION:1} > version_fdroid.txt BNUM=$(($GITHUB_RUN_NUMBER + 1000))
echo versionName=7-fdroid-${RELEASE_VERSION:1}$'\n'versionCode=${BNUM} > version_fdroid.txt
git add . git add .
git commit -m "Updating to build ${RELEASE_VERSION:1}" git commit -m "Updating to build ${RELEASE_VERSION:1}"
git push https://Anuken:${{ secrets.API_TOKEN_GITHUB }}@github.com/Anuken/MindustryBuilds
cd ../Mindustry cd ../Mindustry
- name: Upload client artifacts - name: Upload client artifacts
uses: svenstaro/upload-release-action@v2 uses: svenstaro/upload-release-action@v2

View File

@@ -0,0 +1,10 @@
name: "Validate Gradle Wrapper"
on: [push, pull_request]
jobs:
validation:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: gradle/wrapper-validation-action@v2

View File

@@ -1,21 +1,28 @@
name: Pull Request Tests name: Pull Request Tests
on: [pull_request] on: [pull_request, workflow_dispatch]
permissions:
contents: read # to fetch code (actions/checkout)
jobs: jobs:
buildJava14: testPR:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up JDK 14 - name: Set up JDK 17
uses: actions/setup-java@v1 uses: actions/setup-java@v1
with: with:
java-version: 14 java-version: 17
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Run unit tests
run: ./gradlew tests:test --stacktrace --rerun
- name: Run unit tests and build JAR - name: Run unit tests and build JAR
run: ./gradlew test desktop:dist run: ./gradlew desktop:dist
- name: Upload desktop JAR for testing - name: Upload desktop JAR for testing
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v4
with: with:
name: Desktop JAR (zipped) name: Desktop JAR (zipped)
path: desktop/build/libs/Mindustry.jar path: desktop/build/libs/Mindustry.jar

View File

@@ -2,18 +2,16 @@ name: Tests
on: [push, workflow_dispatch] on: [push, workflow_dispatch]
permissions: {}
jobs: jobs:
buildJava14: runPush:
permissions:
contents: write # for Update bundles
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - 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 - name: Trigger BE build
if: ${{ github.repository == 'Anuken/Mindustry' }} if: ${{ github.repository == 'Anuken/Mindustry' }}
run: | run: |
@@ -21,5 +19,43 @@ jobs:
cd ../MindustryBuilds cd ../MindustryBuilds
BNUM=$(($GITHUB_RUN_NUMBER + 20000)) BNUM=$(($GITHUB_RUN_NUMBER + 20000))
git tag ${BNUM} git tag ${BNUM}
git config --global user.name "Build Uploader" git config --global user.name "Github Actions"
git push https://Anuken:${{ secrets.API_TOKEN_GITHUB }}@github.com/Anuken/MindustryBuilds ${BNUM} git push https://Anuken:${{ secrets.API_TOKEN_GITHUB }}@github.com/Anuken/MindustryBuilds ${BNUM}
- name: Set up JDK 17
uses: actions/setup-java@v1
with:
java-version: 17
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Update bundles
if: ${{ github.repository == 'Anuken/Mindustry' }}
run: |
./gradlew updateBundles
if [ -n "$(git status --porcelain)" ]; then
git config --global user.name "Github Actions"
git config --global user.email "actions@github.com"
git add core/assets/bundles/*
git commit -m "Automatic bundle update"
git push
fi
- name: Update JITpack repo
if: ${{ github.repository == 'Anuken/Mindustry' }}
run: |
git config --global user.name "Github Actions"
git config --global user.email "actions@github.com"
cd ../
cp -r ./Mindustry ./MindustryJitpack
cd MindustryJitpack
git clone --depth 1 https://github.com/Anuken/MindustryJitpack.git
rm -rf .git
cp -r ./MindustryJitpack/.git ./.git
rm -rf MindustryJitpack
rm -rf .github
rm README.md
git add .
git commit --allow-empty -m "Updating"
git push https://Anuken:${{ secrets.API_TOKEN_GITHUB }}@github.com/Anuken/MindustryJitpack
cd ../Mindustry
- name: Run unit tests
run: ./gradlew tests:test --rerun --stacktrace

11
.gitignore vendored
View File

@@ -1,10 +1,12 @@
logs/ logs/
/fastlane/metadata/android/en-US/changelogs/
/core/assets/mindustry-saves/ /core/assets/mindustry-saves/
/core/assets/mindustry-maps/ /core/assets/mindustry-maps/
/core/assets/bundles/output/ /core/assets/bundles/output/
/core/assets/.gifimages/ /core/assets/.gifimages/
/deploy/ /deploy/
/out/ /out/
ios/libs/
/desktop/packr-out/ /desktop/packr-out/
/desktop/packr-export/ /desktop/packr-export/
/desktop/mindustry-saves/ /desktop/mindustry-saves/
@@ -42,7 +44,9 @@ steam_appid.txt
ios/robovm.properties ios/robovm.properties
packr-out/ packr-out/
config/ config/
buildSrc/
*.gif *.gif
/tests/out
/core/assets/basepartnames /core/assets/basepartnames
version.properties version.properties
@@ -101,6 +105,10 @@ com_crashlytics_export_strings.xml
.externalToolBuilders/ .externalToolBuilders/
*.launch *.launch
## VSCode
.vscode/
## NetBeans ## NetBeans
/nbproject/private/ /nbproject/private/
@@ -156,3 +164,6 @@ gradle-app.setting
.DS_Store .DS_Store
Thumbs.db Thumbs.db
android/libs/ android/libs/
# ignored due to frequent branch conflicts.
core/assets/logicids.dat

View File

@@ -12,9 +12,11 @@ Do not submit something without at least running the game to see if it compiles.
If you are submitting a new block, make sure it has a name and description, and that it works correctly in-game. If you are changing existing block mechanics, test them out first. If you are submitting a new block, make sure it has a name and description, and that it works correctly in-game. If you are changing existing block mechanics, test them out first.
### Do not make large changes before discussing them first. ### Do not make large changes before discussing them first.
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*). 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) - either via PM or by posting in the `#pulls` channel.
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. 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 make formatting PRs.
Yes, there are occurrences of trailing spaces, extra newlines, empty indents, and other tiny errors. No, I don't want to merge, view, or get notified by your 1-line PR fixing it. If you're implementing a PR with modification of *actual code*, feel free to fix formatting in the general vicinity of your changes, but please don't waste everyone's time with pointless changes.
## Style Guidelines ## Style Guidelines
@@ -26,7 +28,7 @@ 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. - `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) - 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. - 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 - 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) - 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. - 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.

55
LICENSE
View File

@@ -617,58 +617,3 @@ reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee. copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@@ -1,9 +1,9 @@
![Logo](core/assets-raw/sprites/ui/logo.png) ![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) [![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)](https://discord.gg/mindustry) [![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. The automation tower defense RTS, written in Java.
_[Trello Board](https://trello.com/b/aE2tcUwF/mindustry-40-plans)_ _[Trello Board](https://trello.com/b/aE2tcUwF/mindustry-40-plans)_
_[Wiki](https://mindustrygame.github.io/wiki)_ _[Wiki](https://mindustrygame.github.io/wiki)_
@@ -18,7 +18,7 @@ See [CONTRIBUTING](CONTRIBUTING.md).
Bleeding-edge builds are generated automatically for every commit. You can see them [here](https://github.com/Anuken/MindustryBuilds/releases). 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. 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: First, make sure you have [JDK 17](https://adoptium.net/archive.html?variant=openjdk17&jvmVariant=hotspot) installed. **Other JDK versions will not work.** Open a terminal in the Mindustry directory and run the following commands:
### Windows ### Windows
@@ -38,11 +38,14 @@ Server builds are bundled with each released build (in Releases). If you'd rathe
### 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. 1. Install the Android SDK [here.](https://developer.android.com/studio#command-tools) 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. 2. In the unzipped Android SDK folder, find the cmdline-tools directory. Then create a folder inside of it called `latest` and put all of its contents into the newly created folder.
3. Run `gradlew android:assembleDebug` (or `./gradlew` if on linux/mac). This will create an unsigned APK in `android/build/outputs/apk`. 3. In the same directory run the command `sdkmanager --licenses` (or `./sdkmanager --licenses` if on linux/mac)
4. Set the `ANDROID_HOME` environment variable to point to your unzipped Android SDK directory.
5. Enable developer mode on your device/emulator. If you are on testing on a phone you can follow [these instructions](https://developer.android.com/studio/command-line/adb#Enabling), otherwise you need to google how to enable your emulator's developer mode specifically.
6. Run `gradlew android:assembleDebug` (or `./gradlew` if on linux/mac). This will create an unsigned APK in `android/build/outputs/apk`.
To debug the application on a connected phone, run `gradlew android:installDebug android:run`. To debug the application on a connected device/emulator, run `gradlew android:installDebug android:run`.
### Troubleshooting ### Troubleshooting
@@ -50,6 +53,16 @@ To debug the application on a connected phone, run `gradlew android:installDebug
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.* 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.*
#### Where is the `mindustry.gen` package?
As the name implies, `mindustry.gen` is generated *at build time* based on other code. You will not find source code for this package in the repository, and it should not be edited by hand.
The following is a non-exhaustive list of the "source" of generated code in `mindustry.gen`:
- `Call`, `*Packet` classes: Generated from methods marked with `@Remote`.
- All entity classes (`Unit`, `EffectState`, `Posc`, etc): Generated from component classes in the `mindustry.entities.comp` package, and combined using definitions in `mindustry.content.UnitTypes`.
- `Sounds`, `Musics`, `Tex`, `Icon`, etc: Generated based on files in the respective asset folders.
--- ---
Gradle may take up to several minutes to download files. Be patient. <br> Gradle may take up to several minutes to download files. Be patient. <br>

View File

@@ -1,26 +1,37 @@
# Note: Server list review is currently on pause. No new servers will be merged until v8 is released!
*PRs to edit addresses of existing servers will still be accepted, although very infrequently.*
### Adding a server to the list ### Adding a server to the list
Mindustry now has a public list of servers that everyone can see and connect to. 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_v6.json) in this repository. This is done by letting clients `GET` a [JSON list of servers](https://github.com/Anuken/Mindustry/blob/master/servers_v7.json) in this repository.
You may want to add your server to this list. The steps for getting this done are as follows: You may want to add your server to this list. The steps for getting this done are as follows:
0. **Take note of the fact that modded servers are not allowed on this list.** Such servers confuse users, and there's currently no easy way to fix mod incompatibilities after a failed connection.
1. **Ensure your server is properly moderated.** For the most part, this applies to survival servers, but PvP servers can be affected as well. 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. *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 ["canvas", "logic-display", "large-logic-display"]`.
- Your name or description must reflect the type of server you're hosting. 3. **Set an appropriate MOTD, name and description.** This is set with `config <name/desc/motd> <value>`. "Appropriate" means that:
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. - Your name or description must reflect the type of server you're hosting.
- 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. 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.
- Try to be professional in your text; use common sense. - 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.
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. - Try to be professional in your text; use common sense.
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`). 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. Finally, **submit a pull request** to add your server's IP to the list. 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`).
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. 6. Finally, **submit a pull request** to add your server's IP to the list.
For example, if your server address is `google.com`, you would add a comma after the last entry and insert: This should be fairly straightforward: Press the edit button on the [server file](https://github.com/Anuken/Mindustry/blob/master/servers_v7.json), then add a JSON object with the following format:
```json ```json
{ {
"address": "google.com" "name": "Your Server Group Name",
} "address": ["your.server.address"]
``` }
Then, press the *'submit pull request'* button and I'll take a look at your server. If I have any issues with it, I'll let you know in the PR comments. ```
If your group has multiple servers, simply add extra addresses inside the square brackets, separated by commas. For example: `["address1", "address2"]`
> Note that Mindustry also support SRV records. This allows you to use a subdomain for your server address instead of specifying the port. For example, if you want to use `play.example.com` instead of `example.com:6000`, in the dns settings of your domain, add an SRV record with `_mindustry` as the service, `tcp` as the protocol, `play` as the target and `6000` as the port. You can also setup fallback servers by modifying the weight or priority of the record. Although SRV records are very convenient, keep in mind they are slower than regular addresses. Avoid using them in the server list, but rather as an easy way to share your server address.
Then, press the *'submit pull request'* button and I'll take a look at your server. If I have any issues with it, I'll let you know in the PR comments.

View File

@@ -31,9 +31,11 @@ There are two ways to test the translation bundle:
1) Assuming you have the PC version downloaded, download your bundle file, name it `bundle.properties`, then place it in the same folder as the Mindustry desktop executable and run it. *You should get a popup message in-game confirming that you have loaded an external translation.* 1) Assuming you have the PC version downloaded, download your bundle file, name it `bundle.properties`, then place it in the same folder as the Mindustry desktop executable and run it. *You should get a popup message in-game confirming that you have loaded an external translation.*
2) For advanced users: simply download your fork of mindustry and compile/run the game. 2) For advanced users: simply download your fork of mindustry and compile/run the game.
#### Translating for stores (Steam, Google Play) #### Translating for stores (Steam, ~~Google Play~~)
If you would like to translate the descriptions for Google Play or Steam, see the [Fastlane Metadata folder](https://github.com/Anuken/Mindustry/tree/master/fastlane/metadata) and submit a pull request for files there. On Google Play, you would create or edit the folder with the correct local code; for Steam, I have to update the translations manually, so just name the folder with the language name, and include the same files as the English folder does. NOTE: The Google Play description is in the process of being re-written, please do not translate it.
If you would like to translate the descriptions for ~~Google Play~~ or Steam, see the [Fastlane Metadata folder](https://github.com/Anuken/Mindustry/tree/master/fastlane/metadata) and submit a pull request for files there. On Google Play, you would create or edit the folder with the correct local code; for Steam, I have to update the translations manually, so just name the folder with the language name, and include the same files as the English folder does.
**And that's it.** **And that's it.**

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android">
package="io.anuke.mindustry">
<uses-feature android:glEsVersion="0x00020000" android:required="true"/> <uses-feature android:glEsVersion="0x00020000" android:required="true"/>
<uses-feature android:name="android.hardware.type.pc" android:required="false" /> <uses-feature android:name="android.hardware.type.pc" android:required="false" />
@@ -9,20 +8,22 @@
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<application <application
android:resizeableActivity="false" android:resizeableActivity="true"
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:isGame="true" android:isGame="true"
android:theme="@style/ArcTheme"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
android:appCategory="game" android:appCategory="game"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/ArcTheme" android:fullBackupContent="@xml/backup_rules"> android:fullBackupContent="@xml/backup_rules">
<meta-data android:name="android.max_aspect" android:value="2.1"/> <meta-data android:name="android.max_aspect" android:value="2.1"/>
<activity <activity
android:name="mindustry.android.AndroidLauncher" android:name="mindustry.android.AndroidLauncher"
android:label="@string/app_name" android:label="@string/app_name"
android:screenOrientation="user" android:screenOrientation="user"
android:exported="true"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout"> android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout">
<intent-filter> <intent-filter>

View File

@@ -4,13 +4,10 @@ buildscript{
mavenCentral() mavenCentral()
google() google()
maven{ url "https://oss.sonatype.org/content/repositories/snapshots/" } maven{ url "https://oss.sonatype.org/content/repositories/snapshots/" }
jcenter()
} }
dependencies{ dependencies{
//IMPORTANT NOTICE: any version of the plugin after 3.4.1 will break builds classpath 'com.android.tools.build:gradle:8.2.2'
//it appears abstract methods don't get desugared properly (if at all)
classpath 'com.android.tools.build:gradle:3.4.1'
} }
} }
@@ -20,25 +17,9 @@ configurations{ natives }
repositories{ repositories{
mavenCentral() mavenCentral()
jcenter()
maven{ url "https://maven.google.com" } 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!
def sdkFile = new File((String)findSdkDir(), "/platforms/android-29/android.jar")
if(sdkFile.exists()) compileOnly files(sdkFile.absolutePath)
}
task deploy(type: Copy){ task deploy(type: Copy){
dependsOn "assembleRelease" dependsOn "assembleRelease"
@@ -48,8 +29,9 @@ task deploy(type: Copy){
} }
android{ android{
buildToolsVersion '29.0.3' namespace = "io.anuke.mindustry"
compileSdkVersion 29 buildToolsVersion = '34.0.0'
compileSdk = 34
sourceSets{ sourceSets{
main{ main{
manifest.srcFile 'AndroidManifest.xml' manifest.srcFile 'AndroidManifest.xml'
@@ -63,6 +45,7 @@ android{
androidTest.setRoot('tests') androidTest.setRoot('tests')
} }
packagingOptions{ packagingOptions{
exclude 'META-INF/robovm/ios/robovm.xml' exclude 'META-INF/robovm/ios/robovm.xml'
} }
@@ -74,15 +57,17 @@ android{
applicationId "io.anuke.mindustry" applicationId "io.anuke.mindustry"
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 29 targetSdkVersion 34
versionName versionNameResult versionName versionNameResult
versionCode = (System.getenv("TRAVIS_BUILD_ID") != null ? System.getenv("TRAVIS_BUILD_ID").toInteger() : vcode) versionCode = vcode
if(project.hasProperty("release")){ if(project.hasProperty("release")){
props['androidBuildCode'] = (vcode + 1).toString() props['androidBuildCode'] = (vcode + 1).toString()
} }
props.store(file('../core/assets/version.properties').newWriter(), null) props.store(file('../core/assets/version.properties').newWriter(), null)
multiDexEnabled true
} }
compileOptions{ compileOptions{
@@ -90,7 +75,7 @@ android{
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
flavorDimensions "google" flavorDimensions = ["google"]
signingConfigs{ signingConfigs{
release{ release{
@@ -110,6 +95,18 @@ android{
} }
} }
buildTypes{
all{
//TODO without these lines (r8 enabled), Mindustry crashes with missing default interface method errors.
//WHY THE HELL ARE DEFAULT INTERFACES NOT BEING DESUGARED? WHY DID UPDATING AGP MAKE THIS HAPPEN?
//When I ENABLE shrinking, r8 goes and REMOVES ALL DEFAULT INTERFACE CLASSES, which breaks mods. Why?
//-keep class mindustry.** { *; } should *keep the classes* - WHY IS R8 REMOVING THEM?
minifyEnabled = true
shrinkResources = true
proguardFiles("proguard-rules.pro")
}
}
if(project.hasProperty("RELEASE_STORE_FILE") || System.getenv("CI") == "true"){ if(project.hasProperty("RELEASE_STORE_FILE") || System.getenv("CI") == "true"){
buildTypes{ buildTypes{
release{ release{
@@ -118,9 +115,27 @@ android{
} }
} }
} }
// called every time gradle gets executed, takes the native dependencies of
// the natives configuration, and extracts them to the proper libs/ folders dependencies{
// so they get packed with the APK. 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:$arcHash"
natives "com.github.Anuken.Arc:natives-freetype-android:$arcHash"
def version;
def highestVersion;
new File((String)findSdkDir(), "/platforms").eachFileMatch ~/android-\d+/, {
version = it.name.find(/\d+/).toInteger();
highestVersion = version > highestVersion ? version : highestVersion;
}
def sdkFile = new File((String)findSdkDir(), "/platforms/android-${highestVersion}/android.jar")
if(sdkFile.exists()) compileOnly files(sdkFile.absolutePath)
}
task copyAndroidNatives(){ task copyAndroidNatives(){
configurations.natives.files.each{ jar -> configurations.natives.files.each{ jar ->
copy{ copy{
@@ -132,29 +147,13 @@ task copyAndroidNatives(){
} }
task run(type: Exec){ task run(type: Exec){
def path commandLine "${findSdkDir()}/platform-tools/adb", 'shell', 'am', 'start', '-n', 'io.anuke.mindustry/mindustry.android.AndroidLauncher'
def localProperties = project.file("../local.properties")
if(localProperties.exists()){
Properties properties = new Properties()
localProperties.withInputStream{ instr ->
properties.load(instr)
}
def sdkDir = properties.getProperty('sdk.dir')
if(sdkDir){
path = sdkDir
}else{
path = "$System.env.ANDROID_HOME"
}
}else{
path = "$System.env.ANDROID_HOME"
}
def adb = path + "/platform-tools/adb"
commandLine "$adb", 'shell', 'am', 'start', '-n', 'io.anuke.mindustry/mindustry.android.AndroidLauncher'
} }
if(!project.ext.hasSprites()){ if(!project.ext.hasSprites()){
println "Scheduling sprite pack." tasks.whenTaskAdded{ task ->
run.dependsOn ":tools:pack" if(task.name == 'assembleDebug' || task.name == 'assembleRelease'){
deploy.dependsOn ":tools:pack" task.dependsOn ":tools:pack"
} }
}
}

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

@@ -0,0 +1,12 @@
-dontobfuscate
-keep class mindustry.** { *; }
-keep class arc.** { *; }
-keep class net.jpountz.** { *; }
-keep class rhino.** { *; }
-keep class com.android.dex.** { *; }
-keepattributes Signature,*Annotation*,InnerClasses,EnclosingMethod
-dontwarn javax.naming.**
#-printusage out.txt

View File

@@ -8,4 +8,4 @@
<item name="android:windowContentOverlay">@null</item> <item name="android:windowContentOverlay">@null</item>
<item name="android:windowFullscreen">true</item> <item name="android:windowFullscreen">true</item>
</style> </style>
</resources> </resources>

View File

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

View File

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

View File

@@ -38,20 +38,19 @@ public class AndroidLauncher extends AndroidApplication{
UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler(); UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler((thread, error) -> { Thread.setDefaultUncaughtExceptionHandler((thread, error) -> {
CrashSender.log(error); CrashHandler.log(error);
//try to forward exception to system handler //try to forward exception to system handler
if(handler != null){ if(handler != null){
handler.uncaughtException(thread, error); handler.uncaughtException(thread, error);
}else{ }else{
error.printStackTrace();
Log.err(error); Log.err(error);
System.exit(1); System.exit(1);
} }
}); });
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if(doubleScaleTablets && isTablet(this.getContext())){ if(doubleScaleTablets && isTablet(this)){
Scl.setAddition(0.5f); Scl.setAddition(0.5f);
} }
@@ -64,7 +63,7 @@ public class AndroidLauncher extends AndroidApplication{
@Override @Override
public rhino.Context getScriptContext(){ public rhino.Context getScriptContext(){
return AndroidRhinoContext.enter(getContext().getCacheDir()); return AndroidRhinoContext.enter(getCacheDir());
} }
@Override @Override
@@ -72,9 +71,30 @@ public class AndroidLauncher extends AndroidApplication{
} }
@Override @Override
public Class<?> loadJar(Fi jar, String mainClass) throws Exception{ public ClassLoader loadJar(Fi jar, ClassLoader parent) throws Exception{
DexClassLoader loader = new DexClassLoader(jar.file().getPath(), getFilesDir().getPath(), null, getClassLoader()); //Required to load jar files in Android 14: https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading
return Class.forName(mainClass, true, loader); jar.file().setReadOnly();
return new DexClassLoader(jar.file().getPath(), getFilesDir().getPath(), null, parent){
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
//check for loaded state
Class<?> loadedClass = findLoadedClass(name);
if(loadedClass == null){
try{
//try to load own class first
loadedClass = findClass(name);
}catch(ClassNotFoundException | NoClassDefFoundError e){
//use parent if not found
return parent.loadClass(name);
}
}
if(resolve){
resolveClass(loadedClass);
}
return loadedClass;
}
};
} }
@Override @Override
@@ -83,64 +103,69 @@ public class AndroidLauncher extends AndroidApplication{
} }
void showFileChooser(boolean open, String title, Cons<Fi> cons, String... extensions){ void showFileChooser(boolean open, String title, Cons<Fi> cons, String... extensions){
String extension = extensions[0]; try{
String extension = extensions[0];
if(VERSION.SDK_INT >= VERSION_CODES.Q){ if(VERSION.SDK_INT >= VERSION_CODES.Q){
Intent intent = new Intent(open ? Intent.ACTION_OPEN_DOCUMENT : Intent.ACTION_CREATE_DOCUMENT); Intent intent = new Intent(open ? Intent.ACTION_OPEN_DOCUMENT : Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE); intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType(extension.equals("zip") && !open && extensions.length == 1 ? "application/zip" : "*/*"); intent.setType(extension.equals("zip") && !open && extensions.length == 1 ? "application/zip" : "*/*");
intent.putExtra(Intent.EXTRA_TITLE, "export." + extension);
addResultListener(i -> startActivityForResult(intent, i), (code, in) -> { addResultListener(i -> startActivityForResult(intent, i), (code, in) -> {
if(code == Activity.RESULT_OK && in != null && in.getData() != null){ if(code == Activity.RESULT_OK && in != null && in.getData() != null){
Uri uri = in.getData(); Uri uri = in.getData();
if(uri.getPath().contains("(invalid)")) return; if(uri.getPath().contains("(invalid)")) return;
Core.app.post(() -> Core.app.post(() -> cons.get(new Fi(uri.getPath()){ Core.app.post(() -> Core.app.post(() -> cons.get(new Fi(uri.getPath()){
@Override @Override
public InputStream read(){ public InputStream read(){
try{ try{
return getContentResolver().openInputStream(uri); return getContentResolver().openInputStream(uri);
}catch(IOException e){ }catch(IOException e){
throw new ArcRuntimeException(e); throw new ArcRuntimeException(e);
}
} }
}
@Override @Override
public OutputStream write(boolean append){ public OutputStream write(boolean append){
try{ try{
return getContentResolver().openOutputStream(uri); return getContentResolver().openOutputStream(uri);
}catch(IOException e){ }catch(IOException e){
throw new ArcRuntimeException(e); throw new ArcRuntimeException(e);
}
} }
} })));
}))); }
} });
}); }else if(VERSION.SDK_INT >= VERSION_CODES.M && !(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&
}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)){ checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)){
chooser = new FileChooser(title, file -> Structs.contains(extensions, file.extension().toLowerCase()), open, file -> { chooser = new FileChooser(title, file -> Structs.contains(extensions, file.extension().toLowerCase()), open, file -> {
if(!open){ if(!open){
cons.get(file.parent().child(file.nameWithoutExtension() + "." + extension)); cons.get(file.parent().child(file.nameWithoutExtension() + "." + extension));
}else{ }else{
cons.get(file); cons.get(file);
} }
}); });
ArrayList<String> perms = new ArrayList<>(); ArrayList<String> perms = new ArrayList<>();
if(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){ if(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
perms.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); perms.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
} }
if(checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){ if(checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
perms.add(Manifest.permission.READ_EXTERNAL_STORAGE); perms.add(Manifest.permission.READ_EXTERNAL_STORAGE);
} }
requestPermissions(perms.toArray(new String[0]), PERMISSION_REQUEST_CODE); requestPermissions(perms.toArray(new String[0]), PERMISSION_REQUEST_CODE);
}else{
if(open){
new FileChooser(title, file -> Structs.contains(extensions, file.extension().toLowerCase()), true, cons).show();
}else{ }else{
super.showFileChooser(open, "@open", extension, cons); if(open){
new FileChooser(title, file -> Structs.contains(extensions, file.extension().toLowerCase()), true, cons).show();
}else{
super.showFileChooser(open, "@open", extension, cons);
}
} }
}catch(Throwable error){
Core.app.post(() -> Vars.ui.showException(error));
} }
} }
@@ -162,15 +187,26 @@ public class AndroidLauncher extends AndroidApplication{
}, new AndroidApplicationConfiguration(){{ }, new AndroidApplicationConfiguration(){{
useImmersiveMode = true; useImmersiveMode = true;
hideStatusBar = true; hideStatusBar = true;
stencil = 8; useGL30 = true;
}}); }});
checkFiles(getIntent()); checkFiles(getIntent());
try{ try{
//new external folder //new external folder
Fi data = Core.files.absolute(getContext().getExternalFilesDir(null).getAbsolutePath()); Fi data = Core.files.absolute(((Context)this).getExternalFilesDir(null).getAbsolutePath());
Core.settings.setDataDirectory(data); Core.settings.setDataDirectory(data);
//delete unused cache folder to free up space
try{
Fi cache = Core.settings.getDataDirectory().child("cache");
if(cache.exists()){
cache.deleteDirectory();
}
}catch(Throwable t){
Log.err("Failed to delete cached folder", t);
}
//move to internal storage if there's no file indicating that it moved //move to internal storage if there's no file indicating that it moved
if(!Core.files.local("files_moved").exists()){ if(!Core.files.local("files_moved").exists()){
Log.info("Moving files to external storage..."); Log.info("Moving files to external storage...");

View File

@@ -13,6 +13,7 @@ import com.android.dx.dex.cf.*;
import com.android.dx.dex.file.DexFile; import com.android.dx.dex.file.DexFile;
import com.android.dx.merge.*; import com.android.dx.merge.*;
import dalvik.system.*; import dalvik.system.*;
import mindustry.mod.*;
import rhino.*; import rhino.*;
import java.io.*; import java.io.*;
@@ -30,23 +31,6 @@ public class AndroidRhinoContext{
* @return a context prepared for android * @return a context prepared for android
*/ */
public static Context enter(File cacheDirectory){ public static Context enter(File cacheDirectory){
if(!SecurityController.hasGlobal())
SecurityController.initGlobal(new SecurityController(){
@Override
public GeneratedClassLoader createClassLoader(ClassLoader classLoader, Object o){
return Context.getCurrentContext().createClassLoader(classLoader);
}
@Override
public Object getDynamicSecurityDomain(Object o){
return null;
}
@Override
public Object callWithDomain(Object o, Context context, Callable callable, Scriptable scriptable, Scriptable scriptable1, Object[] objects){
return null;
}
});
AndroidContextFactory factory; AndroidContextFactory factory;
if(!ContextFactory.hasExplicitGlobal()){ if(!ContextFactory.hasExplicitGlobal()){
@@ -175,7 +159,7 @@ public class AndroidRhinoContext{
}catch(IOException e){ }catch(IOException e){
e.printStackTrace(); 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); return new DexClassLoader(dexFile.getPath(), VERSION.SDK_INT >= 21 ? context.getCodeCacheDir().getPath() : context.getCacheDir().getAbsolutePath(), null, getParent()).loadClass(name);
} }

View File

@@ -8,14 +8,12 @@ public class Annotations{
/** Indicates that a method overrides other methods. */ /** Indicates that a method overrides other methods. */
@Target({ElementType.METHOD}) @Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface Replace{ public @interface Replace{}
}
/** Indicates that a method should be final in all implementing classes. */ /** Indicates that a method should be final in all implementing classes. */
@Target({ElementType.METHOD}) @Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface Final{ public @interface Final{}
}
/** Indicates that a field will be interpolated when synced. */ /** Indicates that a field will be interpolated when synced. */
@Target({ElementType.FIELD}) @Target({ElementType.FIELD})
@@ -30,15 +28,18 @@ public class Annotations{
/** Indicates that a field will not be read from the server when syncing the local player state. */ /** Indicates that a field will not be read from the server when syncing the local player state. */
@Target({ElementType.FIELD}) @Target({ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface SyncLocal{ public @interface SyncLocal{}
/** Indicates that a field should not be synced to clients (but may still be non-transient) */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface NoSync{}
}
/** Indicates that a component field is imported from other components. This means it doesn't actually exist. */ /** Indicates that a component field is imported from other components. This means it doesn't actually exist. */
@Target({ElementType.FIELD}) @Target({ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface Import{ public @interface Import{}
}
/** Indicates that a component field is read-only. */ /** Indicates that a component field is read-only. */
@Target({ElementType.FIELD, ElementType.METHOD}) @Target({ElementType.FIELD, ElementType.METHOD})
@@ -105,8 +106,7 @@ public class Annotations{
/** Indicates an internal interface for entity components. */ /** Indicates an internal interface for entity components. */
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface EntityInterface{ public @interface EntityInterface{}
}
//endregion //endregion
//region misc. utility //region misc. utility
@@ -118,7 +118,7 @@ public class Annotations{
/** /**
* The region name to load. Variables can be used: * The region name to load. Variables can be used:
* "@" -> block name * "@" -> block name
* "$size" -> block size * "@size" -> block size
* "#" "#1" "#2" -> index number, for arrays * "#" "#1" "#2" -> index number, for arrays
* */ * */
String value(); String value();
@@ -145,15 +145,12 @@ public class Annotations{
/** Indicates that a method should always call its super version. */ /** Indicates that a method should always call its super version. */
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface CallSuper{ public @interface CallSuper{}
}
/** Annotation that allows overriding CallSuper annotation. To be used on method that overrides method with CallSuper annotation from parent class. */ /** Annotation that allows overriding CallSuper annotation. To be used on method that overrides method with CallSuper annotation from parent class. */
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface OverrideCallSuper{ public @interface OverrideCallSuper{}
}
//endregion //endregion
//region struct //region struct
@@ -161,9 +158,7 @@ public class Annotations{
/** Marks a class as a special value type struct. Class name must end in 'Struct'. */ /** Marks a class as a special value type struct. Class name must end in 'Struct'. */
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface Struct{ public @interface Struct{}
}
/** Marks a field of a struct. Optional. */ /** Marks a field of a struct. Optional. */
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
@@ -177,28 +172,26 @@ public class Annotations{
//region remote //region remote
public enum PacketPriority{ public enum PacketPriority{
/** Does not get handled unless client is connected. */
low,
/** Gets put in a queue and processed if not connected. */ /** Gets put in a queue and processed if not connected. */
normal, normal,
/** Gets handled immediately, regardless of connection status. */ /** Gets handled immediately, regardless of connection status. */
high, high,
/** Does not get handled unless client is connected. */
low
} }
/** A set of two booleans, one specifying server and one specifying client. */ /** A set of two booleans, one specifying server and one specifying client. */
public enum Loc{ public enum Loc{
/** Method can only be invoked on the client from the server. */ /** Server only. */
server(true, false), server(true, false),
/** Method can only be invoked on the server from the client. */ /** Client only. */
client(false, true), client(false, true),
/** Method can be invoked from anywhere */ /** Both server and client. */
both(true, true), both(true, true),
/** Neither server nor client. */ /** Neither server nor client. */
none(false, false); none(false, false);
/** If true, this method can be invoked ON clients FROM servers. */
public final boolean isServer; public final boolean isServer;
/** If true, this method can be invoked ON servers FROM clients. */
public final boolean isClient; public final boolean isClient;
Loc(boolean server, boolean client){ Loc(boolean server, boolean client){
@@ -227,16 +220,16 @@ public class Annotations{
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface Remote{ public @interface Remote{
/** Specifies the locations from which this method can be invoked. */ /** Specifies the locations from which this method can cause remote invocations (This -> Remote) [Default: Server -> Client]. */
Loc targets() default Loc.server; Loc targets() default Loc.server;
/** Specifies which methods are generated. Only affects server-to-client methods. */ /** Specifies which methods are generated. Only affects server-to-client methods (Server -> Client(s)) [Default: Server -> Client & Server -> All Clients]. */
Variant variants() default Variant.all; Variant variants() default Variant.all;
/** The local locations where this method is called locally, when invoked. */ /** The locations where this method is called locally, when invoked locally (This -> This) [Default: No local invocations]. */
Loc called() default Loc.none; Loc called() default Loc.none;
/** Whether to forward this packet to all other clients upon receival. Client only. */ /** Whether the server should forward this packet to all other clients upon receival from a client (Client -> Server -> Other Clients). [Default: Don't Forward Client Invocations] */
boolean forward() default false; boolean forward() default false;
/** /**
@@ -251,8 +244,7 @@ public class Annotations{
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface TypeIOHandler{ public @interface TypeIOHandler{ }
}
//endregion //endregion
} }

View File

@@ -2,15 +2,10 @@ package mindustry.annotations;
import arc.files.*; import arc.files.*;
import arc.struct.*; import arc.struct.*;
import arc.util.Log;
import arc.util.Log.*;
import arc.util.*; import arc.util.*;
import arc.util.Log.*;
import com.squareup.javapoet.*; import com.squareup.javapoet.*;
import com.sun.source.util.*; 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 mindustry.annotations.util.*;
import javax.annotation.processing.*; import javax.annotation.processing.*;
@@ -22,7 +17,6 @@ import javax.tools.Diagnostic.*;
import javax.tools.*; import javax.tools.*;
import java.io.*; import java.io.*;
import java.lang.annotation.*; import java.lang.annotation.*;
import java.util.List;
import java.util.*; import java.util.*;
@SupportedSourceVersion(SourceVersion.RELEASE_8) @SupportedSourceVersion(SourceVersion.RELEASE_8)
@@ -31,19 +25,16 @@ public abstract class BaseProcessor extends AbstractProcessor{
public static final String packageName = "mindustry.gen"; public static final String packageName = "mindustry.gen";
public static Types typeu; public static Types typeu;
public static JavacElements elementu; public static Elements elementu;
public static Filer filer; public static Filer filer;
public static Messager messager; public static Messager messager;
public static Trees trees; public static Trees trees;
public static TreeMaker maker;
protected int round; protected int round;
protected int rounds = 1; protected int rounds = 1;
protected RoundEnvironment env; protected RoundEnvironment env;
protected Fi rootDirectory; protected Fi rootDirectory;
protected Context context;
public static String getMethodName(Element element){ public static String getMethodName(Element element){
return ((TypeElement)element.getEnclosingElement()).getQualifiedName().toString() + "." + element.getSimpleName(); return ((TypeElement)element.getEnclosingElement()).getQualifiedName().toString() + "." + element.getSimpleName();
} }
@@ -100,7 +91,7 @@ public abstract class BaseProcessor extends AbstractProcessor{
} }
public static TypeName tname(String pack, String simple){ public static TypeName tname(String pack, String simple){
return ClassName.get(pack, simple ); return ClassName.get(pack, simple);
} }
public static TypeName tname(String name){ public static TypeName tname(String name){
@@ -132,14 +123,21 @@ public abstract class BaseProcessor extends AbstractProcessor{
} }
public static void write(TypeSpec.Builder builder, Seq<String> imports) throws Exception{ public static void write(TypeSpec.Builder builder, Seq<String> imports) throws Exception{
builder.superinterfaces.sort(Structs.comparing(t -> t.toString()));
builder.methodSpecs.sort(Structs.comparing(m -> m.toString()));
builder.fieldSpecs.sort(Structs.comparing(f -> f.name));
JavaFile file = JavaFile.builder(packageName, builder.build()).skipJavaLangImports(true).build(); JavaFile file = JavaFile.builder(packageName, builder.build()).skipJavaLangImports(true).build();
String writeString;
if(imports != null){ if(imports != null){
imports = imports.map(m -> Seq.with(m.split("\n")).sort().toString("\n"));
imports.sort();
String rawSource = file.toString(); String rawSource = file.toString();
Seq<String> result = new Seq<>(); Seq<String> result = new Seq<>();
for(String s : rawSource.split("\n", -1)){ for(String s : rawSource.split("\n", -1)){
result.add(s); result.add(s);
if (s.startsWith("package ")){ if(s.startsWith("package ")){
result.add(""); result.add("");
for (String i : imports){ for (String i : imports){
result.add(i); result.add(i);
@@ -147,14 +145,15 @@ public abstract class BaseProcessor extends AbstractProcessor{
} }
} }
String out = result.toString("\n"); writeString = result.toString("\n");
JavaFileObject object = filer.createSourceFile(file.packageName + "." + file.typeSpec.name, file.typeSpec.originatingElements.toArray(new Element[0]));
OutputStream stream = object.openOutputStream();
stream.write(out.getBytes());
stream.close();
}else{ }else{
file.writeTo(filer); writeString = file.toString();
} }
JavaFileObject object = filer.createSourceFile(file.packageName + "." + file.typeSpec.name, file.typeSpec.originatingElements.toArray(new Element[0]));
Writer stream = object.openWriter();
stream.write(writeString);
stream.close();
} }
public Seq<Selement> elements(Class<? extends Annotation> type){ public Seq<Selement> elements(Class<? extends Annotation> type){
@@ -186,7 +185,7 @@ public abstract class BaseProcessor extends AbstractProcessor{
Log.err("[CODEGEN ERROR] " + message + ": " + elem); 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); err(message, elem.e);
} }
@@ -194,15 +193,11 @@ public abstract class BaseProcessor extends AbstractProcessor{
public synchronized void init(ProcessingEnvironment env){ public synchronized void init(ProcessingEnvironment env){
super.init(env); super.init(env);
JavacProcessingEnvironment javacProcessingEnv = (JavacProcessingEnvironment)env;
trees = Trees.instance(env); trees = Trees.instance(env);
typeu = env.getTypeUtils(); typeu = env.getTypeUtils();
elementu = javacProcessingEnv.getElementUtils(); elementu = env.getElementUtils();
filer = env.getFiler(); filer = env.getFiler();
messager = env.getMessager(); messager = env.getMessager();
context = ((JavacProcessingEnvironment)env).getContext();
maker = TreeMaker.instance(javacProcessingEnv.getContext());
Log.level = LogLevel.info; Log.level = LogLevel.info;
@@ -219,7 +214,7 @@ public abstract class BaseProcessor extends AbstractProcessor{
String path = Fi.get(filer.getResource(StandardLocation.CLASS_OUTPUT, "no", "no") String path = Fi.get(filer.getResource(StandardLocation.CLASS_OUTPUT, "no", "no")
.toUri().toURL().toString().substring(OS.isWindows ? 6 : "file:".length())) .toUri().toURL().toString().substring(OS.isWindows ? 6 : "file:".length()))
.parent().parent().parent().parent().parent().parent().parent().toString().replace("%20", " "); .parent().parent().parent().parent().parent().parent().parent().toString().replace("%20", " ");
rootDirectory = Fi.get(path); rootDirectory = Fi.get(path).parent();
}catch(IOException e){ }catch(IOException e){
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@@ -89,7 +89,7 @@ public class EntityIO{
st("write.s($L)", revisions.peek().version); st("write.s($L)", revisions.peek().version);
//write uses most recent revision //write uses most recent revision
for(RevisionField field : revisions.peek().fields){ for(RevisionField field : revisions.peek().fields){
io(field.type, "this." + field.name); io(field.type, "this." + field.name, false);
} }
}else{ }else{
//read revision //read revision
@@ -107,7 +107,7 @@ public class EntityIO{
//add code for reading revision //add code for reading revision
for(RevisionField field : rev.fields){ for(RevisionField field : rev.fields){
//if the field doesn't exist, the result will be an empty string, it won't get assigned //if the field doesn't exist, the result will be an empty string, it won't get assigned
io(field.type, presentFields.contains(field.name) ? "this." + field.name + " = " : ""); io(field.type, presentFields.contains(field.name) ? "this." + field.name + " = " : "", false);
} }
} }
@@ -118,14 +118,17 @@ public class EntityIO{
} }
} }
void writeSync(MethodSpec.Builder method, boolean write, Seq<Svar> syncFields, Seq<Svar> allFields) throws Exception{ void writeSync(MethodSpec.Builder method, boolean write, Seq<Svar> allFields) throws Exception{
this.method = method; this.method = method;
this.write = write; this.write = write;
if(write){ if(write){
//write uses most recent revision //write uses most recent revision
for(RevisionField field : revisions.peek().fields){ for(RevisionField field : revisions.peek().fields){
io(field.type, "this." + field.name); Svar var = allFields.find(s -> s.name().equals(field.name));
if(var == null || var.has(NoSync.class)) continue;
io(field.type, "this." + field.name, true);
} }
}else{ }else{
Revision rev = revisions.peek(); Revision rev = revisions.peek();
@@ -138,20 +141,22 @@ public class EntityIO{
//add code for reading revision //add code for reading revision
for(RevisionField field : rev.fields){ for(RevisionField field : rev.fields){
Svar var = allFields.find(s -> s.name().equals(field.name)); Svar var = allFields.find(s -> s.name().equals(field.name));
if(var == null || var.has(NoSync.class)) continue;
boolean sf = var.has(SyncField.class), sl = var.has(SyncLocal.class); boolean sf = var.has(SyncField.class), sl = var.has(SyncLocal.class);
if(sl) cont("if(!islocal)"); if(sl) cont("if(!islocal)");
if(sf){ if(sf){
//TODO adding + targetSuf to the assignment fixes units being interpolated incorrectly during physics, but makes interpolation snap instead.
st(field.name + lastSuf + " = this." + field.name); st(field.name + lastSuf + " = this." + field.name);
} }
io(field.type, "this." + (sf ? field.name + targetSuf : field.name) + " = "); io(field.type, "this." + (sf ? field.name + targetSuf : field.name) + " = ", true);
if(sl){ if(sl){
ncont("else" ); ncont("else" );
io(field.type, ""); io(field.type, "", true);
//just assign the two values so jumping does not occur on de-possession //just assign the two values so jumping does not occur on de-possession
if(sf){ if(sf){
@@ -216,20 +221,20 @@ public class EntityIO{
econt(); econt();
} }
private void io(String type, String field) throws Exception{ private void io(String type, String field, boolean network) throws Exception{
type = type.replace("mindustry.gen.", ""); type = type.replace("mindustry.gen.", "");
type = replacements.get(type, type); type = replacements.get(type, type);
if(BaseProcessor.isPrimitive(type)){ if(BaseProcessor.isPrimitive(type)){
s(type.equals("boolean") ? "bool" : type.charAt(0) + "", field); s(type.equals("boolean") ? "bool" : type.charAt(0) + "", field);
}else if(instanceOf(type, "mindustry.ctype.Content")){ }else if(instanceOf(type, "mindustry.ctype.Content") && !type.equals("mindustry.ai.UnitStance") && !type.equals("mindustry.ai.UnitCommand")){
if(write){ if(write){
s("s", field + ".id"); s("s", field + ".id");
}else{ }else{
st(field + "mindustry.Vars.content.getByID(mindustry.ctype.ContentType.$L, read.s())", BaseProcessor.simpleName(type).toLowerCase().replace("type", "")); st(field + "mindustry.Vars.content.getByID(mindustry.ctype.ContentType.$L, read.s())", BaseProcessor.simpleName(type).toLowerCase().replace("type", ""));
} }
}else if(serializer.writers.containsKey(type) && write){ }else if((serializer.writers.containsKey(type) || (network && serializer.netWriters.containsKey(type))) && write){
st("$L(write, $L)", serializer.writers.get(type), field); st("$L(write, $L)", network ? serializer.getNetWriter(type, null) : serializer.writers.get(type), field);
}else if(serializer.mutatorReaders.containsKey(type) && !write && !field.replace(" = ", "").contains(" ") && !field.isEmpty()){ }else if(serializer.mutatorReaders.containsKey(type) && !write && !field.replace(" = ", "").contains(" ") && !field.isEmpty()){
st("$L$L(read, $L)", field, serializer.mutatorReaders.get(type), field.replace(" = ", "")); st("$L$L(read, $L)", field, serializer.mutatorReaders.get(type), field.replace(" = ", ""));
}else if(serializer.readers.containsKey(type) && !write){ }else if(serializer.readers.containsKey(type) && !write){
@@ -240,7 +245,7 @@ public class EntityIO{
if(write){ if(write){
s("i", field + ".length"); s("i", field + ".length");
cont("for(int INDEX = 0; INDEX < $L.length; INDEX ++)", field); cont("for(int INDEX = 0; INDEX < $L.length; INDEX ++)", field);
io(rawType, field + "[INDEX]"); io(rawType, field + "[INDEX]", network);
}else{ }else{
String fieldName = field.replace(" = ", "").replace("this.", ""); String fieldName = field.replace(" = ", "").replace("this.", "");
String lenf = fieldName + "_LENGTH"; String lenf = fieldName + "_LENGTH";
@@ -249,7 +254,7 @@ public class EntityIO{
st("$Lnew $L[$L]", field, type.replace("[]", ""), lenf); st("$Lnew $L[$L]", field, type.replace("[]", ""), lenf);
} }
cont("for(int INDEX = 0; INDEX < $L; INDEX ++)", lenf); cont("for(int INDEX = 0; INDEX < $L; INDEX ++)", lenf);
io(rawType, field.replace(" = ", "[INDEX] = ")); io(rawType, field.replace(" = ", "[INDEX] = "), network);
} }
econt(); econt();
@@ -261,7 +266,7 @@ public class EntityIO{
if(write){ if(write){
s("i", field + ".size"); s("i", field + ".size");
cont("for(int INDEX = 0; INDEX < $L.size; INDEX ++)", field); cont("for(int INDEX = 0; INDEX < $L.size; INDEX ++)", field);
io(generic, field + ".get(INDEX)"); io(generic, field + ".get(INDEX)", network);
}else{ }else{
String fieldName = field.replace(" = ", "").replace("this.", ""); String fieldName = field.replace(" = ", "").replace("this.", "");
String lenf = fieldName + "_LENGTH"; String lenf = fieldName + "_LENGTH";
@@ -270,7 +275,7 @@ public class EntityIO{
st("$L.clear()", field.replace(" = ", "")); st("$L.clear()", field.replace(" = ", ""));
} }
cont("for(int INDEX = 0; INDEX < $L; INDEX ++)", lenf); cont("for(int INDEX = 0; INDEX < $L; INDEX ++)", lenf);
io(generic, field.replace(" = ", "_ITEM = ").replace("this.", generic + " ")); io(generic, field.replace(" = ", "_ITEM = ").replace("this.", generic + " "), network);
if(!field.isEmpty()){ if(!field.isEmpty()){
String temp = field.replace(" = ", "_ITEM").replace("this.", ""); String temp = field.replace(" = ", "_ITEM").replace("this.", "");
st("if($L != null) $L.add($L)", temp, field.replace(" = ", ""), temp); st("if($L != null) $L.add($L)", temp, field.replace(" = ", ""), temp);

View File

@@ -42,6 +42,7 @@ public class EntityProcess extends BaseProcessor{
Seq<Selement> allDefs = new Seq<>(); Seq<Selement> allDefs = new Seq<>();
Seq<Stype> allInterfaces = new Seq<>(); Seq<Stype> allInterfaces = new Seq<>();
Seq<TypeSpec.Builder> baseClasses = new Seq<>(); Seq<TypeSpec.Builder> baseClasses = new Seq<>();
ObjectSet<TypeSpec.Builder> baseClassIndexers = new ObjectSet<>();
ClassSerializer serializer; ClassSerializer serializer;
{ {
@@ -101,6 +102,8 @@ public class EntityProcess extends BaseProcessor{
inter.addJavadoc("Interface for {@link $L}", component.fullName()); inter.addJavadoc("Interface for {@link $L}", component.fullName());
skipDeprecated(inter);
//implement extra interfaces these components may have, e.g. position //implement extra interfaces these components may have, e.g. position
for(Stype extraInterface : component.interfaces().select(i -> !isCompInterface(i))){ for(Stype extraInterface : component.interfaces().select(i -> !isCompInterface(i))){
//javapoet completely chokes on this if I add `addSuperInterface` or create the type name with TypeName.get //javapoet completely chokes on this if I add `addSuperInterface` or create the type name with TypeName.get
@@ -132,13 +135,14 @@ public class EntityProcess extends BaseProcessor{
.build())).addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT).build()); .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))){ for(Svar field : component.fields().select(e -> !e.is(Modifier.STATIC) && !e.is(Modifier.PRIVATE) && !e.has(Import.class))){
String cname = field.name(); String cname = field.name();
//getter //getter
if(!signatures.contains(cname + "()")){ if(!signatures.contains(cname + "()")){
inter.addMethod(MethodSpec.methodBuilder(cname).addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC) inter.addMethod(MethodSpec.methodBuilder(cname).addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
.addAnnotations(Seq.with(field.annotations()).select(a -> a.toString().contains("Null")).map(AnnotationSpec::get)) .addAnnotations(Seq.with(field.annotations()).select(a -> a.toString().contains("Null") || a.toString().contains("Deprecated")).map(AnnotationSpec::get))
.addJavadoc(field.doc() == null ? "" : field.doc()) .addJavadoc(field.doc() == null ? "" : field.doc())
.returns(field.tname()).build()); .returns(field.tname()).build());
} }
@@ -150,7 +154,7 @@ public class EntityProcess extends BaseProcessor{
.addJavadoc(field.doc() == null ? "" : field.doc()) .addJavadoc(field.doc() == null ? "" : field.doc())
.addParameter(ParameterSpec.builder(field.tname(), field.name()) .addParameter(ParameterSpec.builder(field.tname(), field.name())
.addAnnotations(Seq.with(field.annotations()) .addAnnotations(Seq.with(field.annotations())
.select(a -> a.toString().contains("Null")).map(AnnotationSpec::get)).build()).build()); .select(a -> a.toString().contains("Null") || a.toString().contains("Deprecated")).map(AnnotationSpec::get)).build()).build());
} }
} }
@@ -160,7 +164,7 @@ public class EntityProcess extends BaseProcessor{
//SPECIAL CASE: components with EntityDefs don't get a base class! the generated class becomes the base class itself //SPECIAL CASE: components with EntityDefs don't get a base class! the generated class becomes the base class itself
if(component.annotation(Component.class).base()){ if(component.annotation(Component.class).base()){
Seq<Stype> deps = depends.copy().and(component); Seq<Stype> deps = depends.copy().add(component);
baseClassDeps.get(component, ObjectSet::new).addAll(deps); baseClassDeps.get(component, ObjectSet::new).addAll(deps);
//do not generate base classes when the component will generate one itself //do not generate base classes when the component will generate one itself
@@ -229,9 +233,15 @@ public class EntityProcess extends BaseProcessor{
Stype repr = types.first(); Stype repr = types.first();
String groupType = repr.annotation(Component.class).base() ? baseName(repr) : interfaceName(repr); String groupType = repr.annotation(Component.class).base() ? baseName(repr) : interfaceName(repr);
String name = group.name().startsWith("g") ? group.name().substring(1) : group.name();
boolean collides = an.collide(); boolean collides = an.collide();
groupDefs.add(new GroupDefinition(group.name().startsWith("g") ? group.name().substring(1) : group.name(), groupDefs.add(new GroupDefinition(name,
ClassName.bestGuess(packageName + "." + groupType), types, an.spatial(), an.mapping(), collides)); ClassName.bestGuess(packageName + "." + groupType), types, an.spatial(), an.mapping(), collides));
TypeSpec.Builder accessor = TypeSpec.interfaceBuilder("IndexableEntity__" + name);
accessor.addMethod(MethodSpec.methodBuilder("setIndex__" + name).addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC).addParameter(int.class, "index").returns(void.class).build());
write(accessor);
} }
ObjectMap<String, Selement> usedNames = new ObjectMap<>(); ObjectMap<String, Selement> usedNames = new ObjectMap<>();
@@ -257,6 +267,8 @@ public class EntityProcess extends BaseProcessor{
//get base class type name for extension //get base class type name for extension
Stype baseClassType = baseClasses.any() ? baseClasses.first() : null; Stype baseClassType = baseClasses.any() ? baseClasses.first() : null;
@Nullable TypeName baseClass = baseClasses.any() ? tname(packageName + "." + baseName(baseClassType)) : null; @Nullable TypeName baseClass = baseClasses.any() ? tname(packageName + "." + baseName(baseClassType)) : null;
@Nullable TypeSpec.Builder baseClassBuilder = baseClassType == null ? null : this.baseClasses.find(b -> Reflect.<String>get(b, "name").equals(baseName(baseClassType)));
boolean addIndexToBase = baseClassBuilder != null && baseClassIndexers.add(baseClassBuilder);
//whether the main class is the base itself //whether the main class is the base itself
boolean typeIsBase = baseClassType != null && type.has(Component.class) && type.annotation(Component.class).base(); boolean typeIsBase = baseClassType != null && type.has(Component.class) && type.annotation(Component.class).base();
@@ -273,7 +285,10 @@ public class EntityProcess extends BaseProcessor{
name += "Entity"; name += "Entity";
} }
if(ann.legacy()){ boolean legacy = ann.legacy();
if(legacy){
baseClass = tname(packageName + "." + name);
name += "Legacy" + Strings.capitalize(type.name()); name += "Legacy" + Strings.capitalize(type.name());
} }
@@ -330,7 +345,7 @@ public class EntityProcess extends BaseProcessor{
fbuilder.initializer(varInitializers.get(f.descString())); fbuilder.initializer(varInitializers.get(f.descString()));
} }
fbuilder.addModifiers(f.has(ReadOnly.class) ? Modifier.PROTECTED : Modifier.PUBLIC); fbuilder.addModifiers(f.has(ReadOnly.class) || f.is(Modifier.PRIVATE) ? Modifier.PROTECTED : Modifier.PUBLIC);
fbuilder.addAnnotations(f.annotations().map(AnnotationSpec::get)); fbuilder.addAnnotations(f.annotations().map(AnnotationSpec::get));
FieldSpec spec = fbuilder.build(); FieldSpec spec = fbuilder.build();
@@ -338,7 +353,8 @@ public class EntityProcess extends BaseProcessor{
boolean isVisible = !f.is(Modifier.STATIC) && !f.is(Modifier.PRIVATE) && !f.has(ReadOnly.class); 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 //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); builder.addField(spec);
} }
@@ -348,7 +364,7 @@ public class EntityProcess extends BaseProcessor{
allFields.add(f); allFields.add(f);
//add extra sync fields //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); if(!f.tname().toString().equals("float")) err("All SyncFields must be of type float", f);
syncedFields.add(f); syncedFields.add(f);
@@ -374,17 +390,30 @@ public class EntityProcess extends BaseProcessor{
syncedFields.sortComparing(Selement::name); syncedFields.sortComparing(Selement::name);
//override toString method if(!methods.containsKey("toString()")){
builder.addMethod(MethodSpec.methodBuilder("toString") //override toString method
builder.addMethod(MethodSpec.methodBuilder("toString")
.addAnnotation(Override.class) .addAnnotation(Override.class)
.returns(String.class) .returns(String.class)
.addModifiers(Modifier.PUBLIC) .addModifiers(Modifier.PUBLIC)
.addStatement("return $S + $L", name + "#", "id").build()); .addStatement("return $S + $L", name + "#", "id").build());
}
EntityIO io = new EntityIO(type.name(), builder, allFieldSpecs, serializer, rootDirectory.child("annotations/src/main/resources/revisions").child(type.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 //entities with no sync comp and no serialization gen no code
boolean hasIO = ann.genio() && (components.contains(s -> s.name().contains("Sync")) || ann.serialize()); boolean hasIO = ann.genio() && (components.contains(s -> s.name().contains("Sync")) || ann.serialize());
TypeSpec.Builder indexBuilder = baseClassBuilder == null ? builder : baseClassBuilder;
if(baseClassBuilder == null || addIndexToBase){
//implement indexable interfaces.
for(GroupDefinition def : groups){
indexBuilder.addSuperinterface(tname(packageName + ".IndexableEntity__" + def.name));
indexBuilder.addMethod(MethodSpec.methodBuilder("setIndex__" + def.name).addParameter(int.class, "index").addModifiers(Modifier.PUBLIC).addAnnotation(Override.class)
.addCode("index__$L = index;", def.name).build());
}
}
//add all methods from components //add all methods from components
for(ObjectMap.Entry<String, Seq<Smethod>> entry : methods){ for(ObjectMap.Entry<String, Seq<Smethod>> entry : methods){
if(entry.value.contains(m -> m.has(Replace.class))){ if(entry.value.contains(m -> m.has(Replace.class))){
@@ -402,7 +431,7 @@ public class EntityProcess extends BaseProcessor{
err("Type " + type + " has multiple components implementing non-void method " + entry.key + "."); err("Type " + type + " has multiple components implementing non-void method " + entry.key + ".");
} }
entry.value.sort(Structs.comps(Structs.comparingFloat(m -> m.has(MethodPriority.class) ? m.annotation(MethodPriority.class).value() : 0), Structs.comparing(Selement::name))); entry.value.sort(Structs.comps(Structs.comparingFloat(m -> m.has(MethodPriority.class) ? m.annotation(MethodPriority.class).value() : 0), Structs.comparing(s -> s.up().getSimpleName().toString())));
//representative method //representative method
Smethod first = entry.value.first(); Smethod first = entry.value.first();
@@ -416,6 +445,7 @@ public class EntityProcess extends BaseProcessor{
MethodSpec.Builder mbuilder = MethodSpec.methodBuilder(first.name()).addModifiers(first.is(Modifier.PRIVATE) ? Modifier.PRIVATE : Modifier.PUBLIC); MethodSpec.Builder mbuilder = MethodSpec.methodBuilder(first.name()).addModifiers(first.is(Modifier.PRIVATE) ? Modifier.PRIVATE : Modifier.PUBLIC);
//if(isFinal || entry.value.contains(s -> s.has(Final.class))) mbuilder.addModifiers(Modifier.FINAL); //if(isFinal || entry.value.contains(s -> s.has(Final.class))) mbuilder.addModifiers(Modifier.FINAL);
if(entry.value.contains(s -> s.has(CallSuper.class))) mbuilder.addAnnotation(CallSuper.class); //add callSuper here if necessary if(entry.value.contains(s -> s.has(CallSuper.class))) mbuilder.addAnnotation(CallSuper.class); //add callSuper here if necessary
if(first.has(Nullable.class)) mbuilder.addAnnotation(Nullable.class);
if(first.is(Modifier.STATIC)) mbuilder.addModifiers(Modifier.STATIC); if(first.is(Modifier.STATIC)) mbuilder.addModifiers(Modifier.STATIC);
mbuilder.addTypeVariables(first.typeVariables().map(TypeVariableName::get)); mbuilder.addTypeVariables(first.typeVariables().map(TypeVariableName::get));
mbuilder.returns(first.retn()); mbuilder.returns(first.retn());
@@ -437,21 +467,31 @@ public class EntityProcess extends BaseProcessor{
mbuilder.addStatement("if(added == $L) return", first.name().equals("add")); mbuilder.addStatement("if(added == $L) return", first.name().equals("add"));
for(GroupDefinition def : groups){ for(GroupDefinition def : groups){
//remove/add from each group, assume imported if(first.name().equals("add")){
mbuilder.addStatement("Groups.$L.$L(this)", def.name, first.name()); //remove/add from each group, assume imported
mbuilder.addStatement("index__$L = Groups.$L.addIndex(this)", def.name, def.name);
}else{
//remove/add from each group, assume imported
mbuilder.addStatement("Groups.$L.removeIndex(this, index__$L);", def.name, def.name);
mbuilder.addStatement("index__$L = -1", def.name);
}
} }
} }
boolean specialIO = false;
if(hasIO){ if(hasIO){
//SPECIAL CASE: I/O code //SPECIAL CASE: I/O code
//note that serialization is generated even for non-serializing entities for manual usage //note that serialization is generated even for non-serializing entities for manual usage
if((first.name().equals("read") || first.name().equals("write"))){ if((first.name().equals("read") || first.name().equals("write"))){
io.write(mbuilder, first.name().equals("write")); io.write(mbuilder, first.name().equals("write"));
specialIO = true;
} }
//SPECIAL CASE: sync I/O code //SPECIAL CASE: sync I/O code
if((first.name().equals("readSync") || first.name().equals("writeSync"))){ if((first.name().equals("readSync") || first.name().equals("writeSync"))){
io.writeSync(mbuilder, first.name().equals("writeSync"), syncedFields, allFields); io.writeSync(mbuilder, first.name().equals("writeSync"), allFields);
} }
//SPECIAL CASE: sync I/O code for writing to/from a manual buffer //SPECIAL CASE: sync I/O code for writing to/from a manual buffer
@@ -525,7 +565,9 @@ public class EntityProcess extends BaseProcessor{
mbuilder.addStatement("mindustry.gen.Groups.queueFree(($T)this)", Poolable.class); 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 //add pool reset method and implement Poolable
@@ -533,6 +575,7 @@ public class EntityProcess extends BaseProcessor{
builder.addSuperinterface(Poolable.class); builder.addSuperinterface(Poolable.class);
//implement reset() //implement reset()
MethodSpec.Builder resetBuilder = MethodSpec.methodBuilder("reset").addModifiers(Modifier.PUBLIC); MethodSpec.Builder resetBuilder = MethodSpec.methodBuilder("reset").addModifiers(Modifier.PUBLIC);
allFieldSpecs.sortComparing(s -> s.name);
for(FieldSpec spec : allFieldSpecs){ for(FieldSpec spec : allFieldSpecs){
@Nullable Svar variable = specVariables.get(spec); @Nullable Svar variable = specVariables.get(spec);
if(variable != null && variable.isAny(Modifier.STATIC, Modifier.FINAL)) continue; if(variable != null && variable.isAny(Modifier.STATIC, Modifier.FINAL)) continue;
@@ -560,7 +603,19 @@ public class EntityProcess extends BaseProcessor{
.returns(tname(packageName + "." + name)) .returns(tname(packageName + "." + name))
.addStatement(ann.pooled() ? "return Pools.obtain($L.class, " +name +"::new)" : "return new $L()", name).build()); .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)); skipDeprecated(builder);
if(!legacy){
TypeSpec.Builder fieldBuilder = baseClassBuilder != null ? baseClassBuilder : builder;
if(addIndexToBase || baseClassBuilder == null){
//add group index int variables
for(GroupDefinition def : groups){
fieldBuilder.addField(FieldSpec.builder(int.class, "index__" + def.name, Modifier.PROTECTED, Modifier.TRANSIENT).initializer("-1").build());
}
}
}
definitions.add(new EntityDefinition(packageName + "." + name, builder, type, typeIsBase ? null : baseClass, components, groups, allFieldSpecs, legacy));
} }
//generate groups //generate groups
@@ -575,16 +630,20 @@ public class EntityProcess extends BaseProcessor{
groupsBuilder.addField(ParameterizedTypeName.get( groupsBuilder.addField(ParameterizedTypeName.get(
ClassName.bestGuess("mindustry.entities.EntityGroup"), itype), group.name, Modifier.PUBLIC, Modifier.STATIC); ClassName.bestGuess("mindustry.entities.EntityGroup"), itype), group.name, Modifier.PUBLIC, Modifier.STATIC);
groupInit.addStatement("$L = new $T<>($L.class, $L, $L)", group.name, groupc, itype, group.spatial, group.mapping); groupInit.addStatement("$L = new $T<>($L.class, $L, $L, (e, pos) -> { if(e instanceof $L.IndexableEntity__$L ix) ix.setIndex__$L(pos); })", group.name, groupc, itype, group.spatial, group.mapping, packageName, group.name, group.name);
} }
//write the groups //write the groups
groupsBuilder.addMethod(groupInit.build()); groupsBuilder.addMethod(groupInit.build());
groupsBuilder.addField(boolean.class, "isClearing", Modifier.PUBLIC, Modifier.STATIC);
MethodSpec.Builder groupClear = MethodSpec.methodBuilder("clear").addModifiers(Modifier.PUBLIC, Modifier.STATIC); MethodSpec.Builder groupClear = MethodSpec.methodBuilder("clear").addModifiers(Modifier.PUBLIC, Modifier.STATIC);
groupClear.addStatement("isClearing = true");
for(GroupDefinition group : groupDefs){ for(GroupDefinition group : groupDefs){
groupClear.addStatement("$L.clear()", group.name); groupClear.addStatement("$L.clear()", group.name);
} }
groupClear.addStatement("isClearing = false");
//write clear //write clear
groupsBuilder.addMethod(groupClear.build()); groupsBuilder.addMethod(groupClear.build());
@@ -665,11 +724,28 @@ public class EntityProcess extends BaseProcessor{
//build mapping class for sync IDs //build mapping class for sync IDs
TypeSpec.Builder idBuilder = TypeSpec.classBuilder("EntityMapping").addModifiers(Modifier.PUBLIC) 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(TypeName.get(Prov[].class), "idMap", Modifier.PUBLIC, Modifier.STATIC).initializer("new Prov[256]").build())
.addField(FieldSpec.builder(ParameterizedTypeName.get(ClassName.get(ObjectMap.class), .addField(FieldSpec.builder(ParameterizedTypeName.get(ClassName.get(ObjectMap.class),
tname(String.class), tname(Prov.class)), tname(String.class), tname(Prov.class)),
"nameMap", Modifier.PUBLIC, Modifier.STATIC).initializer("new ObjectMap<>()").build()) "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) .addMethod(MethodSpec.methodBuilder("map").addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(TypeName.get(Prov.class)).addParameter(int.class, "id").addStatement("return idMap[id]").build()) .returns(TypeName.get(Prov.class)).addParameter(int.class, "id").addStatement("return idMap[id]").build())
.addMethod(MethodSpec.methodBuilder("map").addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addMethod(MethodSpec.methodBuilder("map").addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(TypeName.get(Prov.class)).addParameter(String.class, "name").addStatement("return nameMap.get(name)").build()); .returns(TypeName.get(Prov.class)).addParameter(String.class, "name").addStatement("return nameMap.get(name)").build());
@@ -698,11 +774,6 @@ public class EntityProcess extends BaseProcessor{
}else{ }else{
//round 3: generate actual classes and implement interfaces //round 3: generate actual classes and implement interfaces
//write base classes
for(TypeSpec.Builder b : baseClasses){
write(b, imports.asArray());
}
//implement each definition //implement each definition
for(EntityDefinition def : definitions){ for(EntityDefinition def : definitions){
@@ -725,6 +796,14 @@ public class EntityProcess extends BaseProcessor{
def.builder.addSuperinterface(inter.tname()); 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 //generate getter/setter for each method
for(Smethod method : inter.methods()){ for(Smethod method : inter.methods()){
String var = method.name(); String var = method.name();
@@ -732,84 +811,47 @@ 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 //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; if(field == null || methodNames.contains(method.simpleString())) continue;
MethodSpec result = null;
//getter //getter
if(!method.isVoid()){ if(!method.isVoid()){
def.builder.addMethod(MethodSpec.overriding(method.e).addStatement("return " + var).build()); result = MethodSpec.overriding(method.e).addStatement("return " + var).build();
} }
//setter //setter
if(method.isVoid() && !Seq.with(field.annotations).contains(f -> f.type.toString().equals("@mindustry.annotations.Annotations.ReadOnly"))){ 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();
} }
}
}
write(def.builder, imports.asArray()); //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));
//store nulls //found the right field, try to check for the method already existing now
TypeSpec.Builder nullsBuilder = TypeSpec.classBuilder("Nulls").addModifiers(Modifier.PUBLIC).addModifiers(Modifier.FINAL); if(superField != null){
MethodSpec fr = result;
//create mock types of all components MethodSpec targetMethod = Seq.with(superclass.methodSpecs).find(m -> m.name.equals(var) && m.returnType.equals(fr.returnType));
for(Stype interf : allInterfaces){ //if the method isn't added yet, add it. in any case, skip.
//indirect interfaces to implement methods for if(targetMethod == null){
Seq<Stype> dependencies = interf.allInterfaces().and(interf); superclass.addMethod(result);
Seq<Smethod> methods = dependencies.flatMap(Stype::methods); }
methods.sortComparing(Object::toString); continue;
//optionally add superclass
Stype superclass = dependencies.map(this::interfaceToComp).find(s -> s != null && s.annotation(Component.class).base());
//use the base type when the interface being emulated has a base
TypeName type = superclass != null && interfaceToComp(interf).annotation(Component.class).base() ? tname(baseName(superclass)) : interf.tname();
//used method signatures
ObjectSet<String> signatures = new ObjectSet<>();
//create null builder
String baseName = interf.name().substring(0, interf.name().length() - 1);
String className = "Null" + baseName;
TypeSpec.Builder nullBuilder = TypeSpec.classBuilder(className)
.addModifiers(Modifier.FINAL);
nullBuilder.addSuperinterface(interf.tname());
if(superclass != null) nullBuilder.superclass(tname(baseName(superclass)));
for(Smethod method : methods){
String signature = method.toString();
if(signatures.contains(signature)) continue;
Stype compType = interfaceToComp(method.type());
MethodSpec.Builder builder = MethodSpec.overriding(method.e).addModifiers(Modifier.PUBLIC, Modifier.FINAL);
builder.addAnnotation(OverrideCallSuper.class); //just in case
if(!method.isVoid()){
if(method.name().equals("isNull")){
builder.addStatement("return true");
}else if(method.name().equals("id")){
builder.addStatement("return -1");
}else{
Svar variable = compType == null || method.params().size > 0 ? null : compType.fields().find(v -> v.name().equals(method.name()));
String desc = variable == null ? null : variable.descString();
if(variable == null || !varInitializers.containsKey(desc)){
builder.addStatement("return " + getDefault(method.ret().toString()));
}else{
String init = varInitializers.get(desc);
builder.addStatement("return " + (init.equals("{}") ? "new " + variable.mirror().toString() : "") + init);
} }
} }
if(result != null){
def.builder.addMethod(result);
}
} }
nullBuilder.addMethod(builder.build());
signatures.add(signature);
} }
nullsBuilder.addField(FieldSpec.builder(type, Strings.camelize(baseName)).initializer("new " + className + "()").addModifiers(Modifier.FINAL, Modifier.STATIC, Modifier.PUBLIC).build()); write(def.builder, imports.toSeq());
write(nullBuilder);
} }
write(nullsBuilder); //write base classes last
for(TypeSpec.Builder b : baseClasses){
write(b, imports.toSeq());
}
} }
} }
@@ -862,7 +904,7 @@ public class EntityProcess extends BaseProcessor{
out.addAll(getDependencies(comp)); out.addAll(getDependencies(comp));
} }
defComponents.put(type, out.asArray()); defComponents.put(type, out.toSeq());
} }
return defComponents.get(type); return defComponents.get(type);
@@ -889,7 +931,7 @@ public class EntityProcess extends BaseProcessor{
//remove it again just in case //remove it again just in case
out.remove(component); out.remove(component);
componentDependencies.put(component, result.asArray()); componentDependencies.put(component, result.toSeq());
} }
return componentDependencies.get(component); return componentDependencies.get(component);
@@ -900,7 +942,7 @@ public class EntityProcess extends BaseProcessor{
} }
String createName(Selement<?> elem){ 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); comps.sortComparing(Selement::name);
return comps.toString("", s -> s.name().replace("Comp", "")); return comps.toString("", s -> s.name().replace("Comp", ""));
} }
@@ -914,6 +956,11 @@ public class EntityProcess extends BaseProcessor{
throw new IllegalArgumentException("Missing types."); throw new IllegalArgumentException("Missing types.");
} }
void skipDeprecated(TypeSpec.Builder builder){
//deprecations are irrelevant in generated code
builder.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class).addMember("value", "\"deprecation\"").build());
}
class GroupDefinition{ class GroupDefinition{
final String name; final String name;
final ClassName baseType; final ClassName baseType;
@@ -944,9 +991,10 @@ public class EntityProcess extends BaseProcessor{
final Selement naming; final Selement naming;
final String name; final String name;
final @Nullable TypeName extend; final @Nullable TypeName extend;
final boolean legacy;
int classID; 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.builder = builder;
this.name = name; this.name = name;
this.naming = naming; this.naming = naming;
@@ -954,6 +1002,7 @@ public class EntityProcess extends BaseProcessor{
this.components = components; this.components = components;
this.extend = extend; this.extend = extend;
this.fieldSpecs = fieldSpec; this.fieldSpecs = fieldSpec;
this.legacy = legacy;
} }
@Override @Override

View File

@@ -1,5 +1,7 @@
package mindustry.annotations.impl; package mindustry.annotations.impl;
import arc.*;
import arc.audio.*;
import arc.files.*; import arc.files.*;
import arc.scene.style.*; import arc.scene.style.*;
import arc.struct.*; import arc.struct.*;
@@ -20,8 +22,8 @@ public class AssetsProcess extends BaseProcessor{
@Override @Override
public void process(RoundEnvironment env) throws Exception{ public void process(RoundEnvironment env) throws Exception{
processSounds("Sounds", rootDirectory + "/core/assets/sounds", "arc.audio.Sound"); processSounds("Sounds", rootDirectory + "/core/assets/sounds", "arc.audio.Sound", true);
processSounds("Musics", rootDirectory + "/core/assets/music", "arc.audio.Music"); processSounds("Musics", rootDirectory + "/core/assets/music", "arc.audio.Music", false);
processUI(env.getElementsAnnotatedWith(StyleDefaults.class)); processUI(env.getElementsAnnotatedWith(StyleDefaults.class));
} }
@@ -43,7 +45,7 @@ public class AssetsProcess extends BaseProcessor{
texIcons.each((key, val) -> { texIcons.each((key, val) -> {
String[] split = val.split("\\|"); String[] split = val.split("\\|");
String name = Strings.kebabToCamel(split[1]).replace("Medium", "").replace("Icon", ""); String name = Strings.kebabToCamel(split[1]).replace("Medium", "").replace("Icon", "").replace("Ui", "");
if(SourceVersion.isKeyword(name) || name.equals("char")) name += "i"; if(SourceVersion.isKeyword(name) || name.equals("char")) name += "i";
ichtype.addField(FieldSpec.builder(char.class, name, Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).addJavadoc(String.format("\\u%04x", Integer.parseInt(key))).initializer("'" + ((char)Integer.parseInt(key)) + "'").build()); 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());
@@ -55,6 +57,9 @@ public class AssetsProcess extends BaseProcessor{
ichtype.addField(FieldSpec.builder(ParameterizedTypeName.get(ObjectIntMap.class, String.class), ichtype.addField(FieldSpec.builder(ParameterizedTypeName.get(ObjectIntMap.class, String.class),
"codes", Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer("new ObjectIntMap<>()").build()); "codes", Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer("new ObjectIntMap<>()").build());
ichtype.addField(FieldSpec.builder(ParameterizedTypeName.get(IntMap.class, String.class),
"codeToName", Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer("new IntMap<>()").build());
ObjectSet<String> used = new ObjectSet<>(); ObjectSet<String> used = new ObjectSet<>();
for(Jval val : icons.get("glyphs").asArray()){ for(Jval val : icons.get("glyphs").asArray()){
@@ -65,7 +70,9 @@ public class AssetsProcess extends BaseProcessor{
int code = val.getInt("code", 0); int code = val.getInt("code", 0);
iconcAll.append((char)code); 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()); 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); ichinit.addStatement("codes.put($S, $L)", name, code);
ichinit.addStatement("codeToName.put($L, $S)", code, name);
ictype.addField(TextureRegionDrawable.class, name + "Small", Modifier.PUBLIC, Modifier.STATIC); ictype.addField(TextureRegionDrawable.class, name + "Small", Modifier.PUBLIC, Modifier.STATIC);
icload.addStatement(name + "Small = mindustry.ui.Fonts.getGlyph(mindustry.ui.Fonts.def, (char)" + code + ")"); icload.addStatement(name + "Small = mindustry.ui.Fonts.getGlyph(mindustry.ui.Fonts.def, (char)" + code + ")");
@@ -87,18 +94,18 @@ public class AssetsProcess extends BaseProcessor{
filename = filename.substring(0, filename.indexOf(".")); filename = filename.substring(0, filename.indexOf("."));
String sfilen = filename; String sfilen = filename;
String dtype = p.name().endsWith(".9.png") ? "arc.scene.style.NinePatchDrawable" : "arc.scene.style.TextureRegionDrawable"; String dtype = "arc.scene.style.Drawable";
String varname = capitalize(sfilen); String varname = capitalize(sfilen);
if(SourceVersion.isKeyword(varname)) varname += "s"; if(SourceVersion.isKeyword(varname)) varname += "s";
type.addField(ClassName.bestGuess(dtype), varname, Modifier.STATIC, Modifier.PUBLIC); type.addField(ClassName.bestGuess(dtype), varname, Modifier.STATIC, Modifier.PUBLIC);
load.addStatement(varname + " = (" + dtype + ")arc.Core.atlas.drawable($S)", sfilen); load.addStatement(varname + " = arc.Core.atlas.drawable($S)", sfilen);
}); });
for(Element elem : elements){ for(Element elem : elements){
Seq.with(((TypeElement)elem).getEnclosedElements()).each(e -> e.getKind() == ElementKind.FIELD, field -> { Seq.with(elem.getEnclosedElements()).each(e -> e.getKind() == ElementKind.FIELD, field -> {
String fname = field.getSimpleName().toString(); String fname = field.getSimpleName().toString();
if(fname.startsWith("default")){ if(fname.startsWith("default")){
loadStyles.addStatement("arc.Core.scene.addStyle(" + field.asType().toString() + ".class, mindustry.ui.Styles." + fname + ")"); loadStyles.addStatement("arc.Core.scene.addStyle(" + field.asType().toString() + ".class, mindustry.ui.Styles." + fname + ")");
@@ -115,17 +122,40 @@ public class AssetsProcess extends BaseProcessor{
JavaFile.builder(packageName, type.build()).build().writeTo(BaseProcessor.filer); JavaFile.builder(packageName, type.build()).build().writeTo(BaseProcessor.filer);
} }
void processSounds(String classname, String path, String rtype) throws Exception{ void processSounds(String classname, String path, String rtype, boolean genid) throws Exception{
TypeSpec.Builder type = TypeSpec.classBuilder(classname).addModifiers(Modifier.PUBLIC); 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); MethodSpec.Builder loadBegin = MethodSpec.methodBuilder("load").addModifiers(Modifier.PUBLIC, Modifier.STATIC);
CodeBlock.Builder staticb = CodeBlock.builder();
if(genid){
type.addField(FieldSpec.builder(IntMap.class, "idToSound", Modifier.STATIC, Modifier.PRIVATE).initializer("new IntMap()").build());
type.addField(FieldSpec.builder(ObjectIntMap.class, "soundToId", Modifier.STATIC, Modifier.PRIVATE).initializer("new ObjectIntMap()").build());
type.addMethod(MethodSpec.methodBuilder("getSoundId")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addParameter(Sound.class, "sound")
.returns(int.class)
.addStatement("return soundToId.get(sound, -1)").build());
type.addMethod(MethodSpec.methodBuilder("getSound")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addParameter(int.class, "id")
.returns(Sound.class)
.addStatement("return (Sound)idToSound.get(id, () -> Sounds.none)").build());
}
HashSet<String> names = new HashSet<>(); HashSet<String> names = new HashSet<>();
Fi.get(path).walk(p -> { Seq<Fi> files = new Seq<>();
Fi.get(path).walk(files::add);
files.sortComparing(Fi::name);
int id = 0;
for(Fi p : files){
String name = p.nameWithoutExtension(); String name = p.nameWithoutExtension();
if(names.contains(name)){ if(names.contains(name)){
BaseProcessor.err("Duplicate file name: " + p.toString() + "!"); BaseProcessor.err("Duplicate file name: " + p + "!");
}else{ }else{
names.add(name); names.add(name);
} }
@@ -134,20 +164,29 @@ public class AssetsProcess extends BaseProcessor{
String filepath = path.substring(path.lastIndexOf("/") + 1) + p.path().substring(p.path().lastIndexOf(path) + path.length()); String filepath = path.substring(path.lastIndexOf("/") + 1) + p.path().substring(p.path().lastIndexOf(path) + path.length());
String filename = "\"" + filepath + "\""; if(genid){
loadBegin.addStatement("arc.Core.assets.load(" + filename + ", " + rtype + ".class).loaded = a -> " + name + " = (" + rtype + ")a", filepath, filepath.replace(".ogg", ".mp3")); staticb.addStatement("soundToId.put($L, $L)", name, id);
dispose.addStatement("arc.Core.assets.unload(" + filename + ")"); loadBegin.addStatement("$T.assets.load($S, $L.class).loaded = a -> { $L = ($L)a; soundToId.put(a, $L); idToSound.put($L, a); }",
dispose.addStatement(name + " = null"); Core.class, filepath, rtype, name, rtype, id, id);
type.addField(FieldSpec.builder(ClassName.bestGuess(rtype), name, Modifier.STATIC, Modifier.PUBLIC).initializer("new arc.audio." + rtype.substring(rtype.lastIndexOf(".") + 1) + "()").build()); }else{
}); loadBegin.addStatement("$T.assets.load($S, $L.class).loaded = a -> { $L = ($L)a; }", Core.class, filepath, rtype, name, rtype);
}
type.addField(FieldSpec.builder(ClassName.bestGuess(rtype), name, Modifier.STATIC, Modifier.PUBLIC).initializer("new " + rtype + "()").build());
id ++;
}
if(genid){
type.addStaticBlock(staticb.build());
}
if(classname.equals("Sounds")){ if(classname.equals("Sounds")){
type.addField(FieldSpec.builder(ClassName.bestGuess(rtype), "none", Modifier.STATIC, Modifier.PUBLIC).initializer("new arc.audio." + rtype.substring(rtype.lastIndexOf(".") + 1) + "()").build()); type.addField(FieldSpec.builder(ClassName.bestGuess(rtype), "none", Modifier.STATIC, Modifier.PUBLIC).initializer("new " + rtype + "()").build());
} }
type.addMethod(loadBegin.build()); type.addMethod(loadBegin.build());
type.addMethod(dispose.build());
JavaFile.builder(packageName, type.build()).build().writeTo(BaseProcessor.filer); 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

@@ -1,6 +1,7 @@
package mindustry.annotations.impl; package mindustry.annotations.impl;
import arc.struct.*; import arc.struct.*;
import arc.util.*;
import com.squareup.javapoet.*; import com.squareup.javapoet.*;
import mindustry.annotations.Annotations.*; import mindustry.annotations.Annotations.*;
import mindustry.annotations.*; import mindustry.annotations.*;
@@ -63,23 +64,28 @@ public class StructProcess extends BaseProcessor{
int size = varSize(var); int size = varSize(var);
TypeName varType = var.tname(); TypeName varType = var.tname();
String varName = var.name(); String varName = var.name();
boolean isBool = varType == TypeName.BOOLEAN;
//add val param to constructor //add val param to constructor
constructor.addParameter(varType, varName); constructor.addParameter(varType, varName);
//[get] field(structType) : fieldType //[get] field(structType) : fieldType
MethodSpec.Builder getter = MethodSpec.methodBuilder(var.name().toString()) MethodSpec.Builder getter = MethodSpec.methodBuilder(var.name())
.addModifiers(Modifier.STATIC, Modifier.PUBLIC) .addModifiers(Modifier.STATIC, Modifier.PUBLIC)
.returns(varType) .returns(varType)
.addParameter(structType, structParam); .addParameter(structType, structParam);
//[set] field(structType, fieldType) : structType //[set] field(structType, fieldType) : structType
MethodSpec.Builder setter = MethodSpec.methodBuilder(var.name().toString()) MethodSpec.Builder setter = MethodSpec.methodBuilder(var.name())
.addModifiers(Modifier.STATIC, Modifier.PUBLIC) .addModifiers(Modifier.STATIC, Modifier.PUBLIC)
.returns(structType) .returns(structType)
.addParameter(structType, structParam).addParameter(varType, "value"); .addParameter(structType, structParam).addParameter(varType, "value");
//field for offset
classBuilder.addField(FieldSpec.builder(structType, "bitMask" + Strings.capitalize(varName), Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.initializer(!isBool ? "($T)($L)" : "($T)(1L << $L)", structType, isBool ? offset : bitString(offset, size, structTotalSize)).build());
//[getter] //[getter]
if(varType == TypeName.BOOLEAN){ if(isBool){
//bools: single bit, is simplified //bools: single bit, is simplified
getter.addStatement("return ($L & (1L << $L)) != 0", structParam, offset); getter.addStatement("return ($L & (1L << $L)) != 0", structParam, offset);
}else if(varType == TypeName.FLOAT){ }else if(varType == TypeName.FLOAT){
@@ -91,25 +97,25 @@ public class StructProcess extends BaseProcessor{
} }
//[setter] + [constructor building] //[setter] + [constructor building]
if(varType == TypeName.BOOLEAN){ if(isBool){
cons.append(" | (").append(varName).append(" ? ").append("1L << ").append(offset).append("L : 0)"); cons.append(" | (").append(varName).append(" ? ").append("1L << ").append(offset).append("L : 0)");
//bools: single bit, needs special case to clear things //bools: single bit, needs special case to clear things
setter.beginControlFlow("if(value)"); setter.beginControlFlow("if(value)");
setter.addStatement("return ($T)(($L & ~(1L << $LL)))", structType, structParam, offset); setter.addStatement("return ($T)($L | (1L << $LL))", structType, structParam, offset);
setter.nextControlFlow("else"); setter.nextControlFlow("else");
setter.addStatement("return ($T)(($L & ~(1L << $LL)) | (1L << $LL))", structType, structParam, offset, offset); setter.addStatement("return ($T)(($L & ~(1L << $LL)))", structType, structParam, offset);
setter.endControlFlow(); setter.endControlFlow();
}else if(varType == TypeName.FLOAT){ }else if(varType == TypeName.FLOAT){
cons.append(" | (").append("(").append(structType).append(")").append("Float.floatToIntBits(").append(varName).append(") << ").append(offset).append("L)"); cons.append(" | (").append("(").append(structType).append(")").append("Float.floatToIntBits(").append(varName).append(") << ").append(offset).append("L)");
//floats: need conversion //floats: need conversion
setter.addStatement("return ($T)(($L & $L) | (($T)Float.floatToIntBits(value) << $LL))", structType, structParam, bitString(offset, size, structTotalSize), structType, offset); setter.addStatement("return ($T)(($L & (~$L)) | (($T)Float.floatToIntBits(value) << $LL))", structType, structParam, bitString(offset, size, structTotalSize), structType, offset);
}else{ }else{
cons.append(" | (((").append(structType).append(")").append(varName).append(" << ").append(offset).append("L)").append(" & ").append(bitString(offset, size, structTotalSize)).append(")"); cons.append(" | (((").append(structType).append(")").append(varName).append(" << ").append(offset).append("L)").append(" & ").append(bitString(offset, size, structTotalSize)).append(")");
//bytes, shorts, chars, ints //bytes, shorts, chars, ints
setter.addStatement("return ($T)(($L & $L) | (($T)value << $LL))", structType, structParam, bitString(offset, size, structTotalSize), structType, offset); setter.addStatement("return ($T)(($L & (~$L)) | (($T)value << $LL))", structType, structParam, bitString(offset, size, structTotalSize), structType, offset);
} }
doc.append("<br> ").append(varName).append(" [").append(offset).append("..").append(size + offset).append("]\n"); doc.append("<br> ").append(varName).append(" [").append(offset).append("..").append(size + offset).append("]\n");
@@ -124,7 +130,7 @@ public class StructProcess extends BaseProcessor{
classBuilder.addJavadoc(doc.toString()); classBuilder.addJavadoc(doc.toString());
//add constructor final statement + add to class and build //add constructor final statement + add to class and build
constructor.addStatement("return ($T)($L)", structType, cons.toString().substring(3)); constructor.addStatement("return ($T)($L)", structType, cons.substring(3));
classBuilder.addMethod(constructor.build()); classBuilder.addMethod(constructor.build());
JavaFile.builder(packageName, classBuilder.build()).build().writeTo(BaseProcessor.filer); JavaFile.builder(packageName, classBuilder.build()).build().writeTo(BaseProcessor.filer);

View File

@@ -3,7 +3,6 @@ package mindustry.annotations.misc;
import arc.*; import arc.*;
import arc.graphics.g2d.*; import arc.graphics.g2d.*;
import arc.struct.*; import arc.struct.*;
import arc.struct.ObjectMap.*;
import com.squareup.javapoet.*; import com.squareup.javapoet.*;
import mindustry.annotations.Annotations.*; import mindustry.annotations.Annotations.*;
import mindustry.annotations.*; import mindustry.annotations.*;
@@ -18,6 +17,7 @@ public class LoadRegionProcessor extends BaseProcessor{
@Override @Override
public void process(RoundEnvironment env) throws Exception{ public void process(RoundEnvironment env) throws Exception{
TypeSpec.Builder regionClass = TypeSpec.classBuilder("ContentRegions") TypeSpec.Builder regionClass = TypeSpec.classBuilder("ContentRegions")
.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class).addMember("value", "\"deprecation\"").build())
.addModifiers(Modifier.PUBLIC); .addModifiers(Modifier.PUBLIC);
MethodSpec.Builder method = MethodSpec.methodBuilder("loadRegions") MethodSpec.Builder method = MethodSpec.methodBuilder("loadRegions")
.addParameter(tname("mindustry.ctype.MappableContent"), "content") .addParameter(tname("mindustry.ctype.MappableContent"), "content")
@@ -33,10 +33,15 @@ public class LoadRegionProcessor extends BaseProcessor{
fieldMap.get(field.enclosingType(), Seq::new).add(field); fieldMap.get(field.enclosingType(), Seq::new).add(field);
} }
for(Entry<Stype, Seq<Svar>> entry : fieldMap){ Seq<Stype> entries = Seq.with(fieldMap.keys());
method.beginControlFlow("if(content instanceof $T)", entry.key.tname()); entries.sortComparing(e -> e.name());
for(Svar field : entry.value){ for(Stype type : entries){
Seq<Svar> fields = fieldMap.get(type);
fields.sortComparing(s -> s.name());
method.beginControlFlow("if(content instanceof $L)", type.fullName());
for(Svar field : fields){
Load an = field.annotation(Load.class); Load an = field.annotation(Load.class);
//get # of array dimensions //get # of array dimensions
int dims = count(field.mirror().toString(), "[]"); int dims = count(field.mirror().toString(), "[]");
@@ -45,7 +50,7 @@ public class LoadRegionProcessor extends BaseProcessor{
//not an array //not an array
if(dims == 0){ 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)", type.fullName(), field.name(), Core.class, parse(an.value()), fallbackString);
}else{ }else{
//is an array, create length string //is an array, create length string
int[] lengths = an.lengths(); int[] lengths = an.lengths();
@@ -58,7 +63,7 @@ public class LoadRegionProcessor extends BaseProcessor{
StringBuilder lengthString = new StringBuilder(); StringBuilder lengthString = new StringBuilder();
for(int value : lengths) lengthString.append("[").append(value).append("]"); for(int value : lengths) lengthString.append("[").append(value).append("]");
method.addStatement("(($T)content).$L = new $T$L", entry.key.tname(), field.name(), TextureRegion.class, lengthString.toString()); method.addStatement("(($T)content).$L = new $T$L", type.tname(), field.name(), TextureRegion.class, lengthString.toString());
for(int i = 0; i < dims; i++){ for(int i = 0; i < dims; i++){
method.beginControlFlow("for(int INDEX$L = 0; INDEX$L < $L; INDEX$L ++)", i, i, lengths[i], i); method.beginControlFlow("for(int INDEX$L = 0; INDEX$L < $L; INDEX$L ++)", i, i, lengths[i], i);
@@ -69,7 +74,7 @@ public class LoadRegionProcessor extends BaseProcessor{
indexString.append("[INDEX").append(i).append("]"); indexString.append("[INDEX").append(i).append("]");
} }
method.addStatement("(($T)content).$L$L = $T.atlas.find($L$L)", entry.key.tname(), field.name(), indexString.toString(), Core.class, parse(an.value()), fallbackString); method.addStatement("(($T)content).$L$L = $T.atlas.find($L$L)", type.tname(), field.name(), indexString.toString(), Core.class, parse(an.value()), fallbackString);
for(int i = 0; i < dims; i++){ for(int i = 0; i < dims; i++){
method.endControlFlow(); method.endControlFlow();

View File

@@ -26,7 +26,8 @@ public class LogicStatementProcessor extends BaseProcessor{
MethodSpec.Builder reader = MethodSpec.methodBuilder("read") MethodSpec.Builder reader = MethodSpec.methodBuilder("read")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(tname("mindustry.logic.LStatement")) .returns(tname("mindustry.logic.LStatement"))
.addParameter(String[].class, "tokens"); .addParameter(String[].class, "tokens")
.addParameter(int.class, "length");
Seq<Stype> types = types(RegisterStatement.class); Seq<Stype> types = types(RegisterStatement.class);
@@ -76,7 +77,7 @@ public class LogicStatementProcessor extends BaseProcessor{
""); "");
//reading primitives, strings and enums is supported; nothing else is //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, index + 1,
field.name(), field.name(),
field.mirror().toString().equals("java.lang.String") ? field.mirror().toString().equals("java.lang.String") ?

View File

@@ -0,0 +1,386 @@
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);
//temporary data to deserialize later
packet.addField(FieldSpec.builder(byte[].class, "DATA", Modifier.PRIVATE).initializer("NODATA").build());
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
makeWriter(packet, ent, serializer);
makeReader(packet, 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
Seq<Svar> params = ent.element.params();
for(int i = 0; i < params.size; i++){
if(!ent.where.isServer && i == 0){
continue;
}
Svar param = params.get(i);
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 void makeWriter(TypeSpec.Builder typespec, 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.getNetWriter(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();
}
}
typespec.addMethod(builder.build());
}
private static void makeReader(TypeSpec.Builder typespec, MethodEntry ent, ClassSerializer serializer){
MethodSpec.Builder readbuilder = MethodSpec.methodBuilder("read")
.addParameter(Reads.class, "READ")
.addParameter(int.class, "LENGTH")
.addModifiers(Modifier.PUBLIC).addAnnotation(Override.class);
//read only into temporary data buffer
readbuilder.addStatement("DATA = READ.b(LENGTH)");
typespec.addMethod(readbuilder.build());
MethodSpec.Builder builder = MethodSpec.methodBuilder("handled")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class);
//make sure data is present, begin reading it if so
builder.addStatement("BAIS.setBytes(DATA)");
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();
}
}
typespec.addMethod(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; package mindustry.annotations.remote;
import mindustry.annotations.Annotations.*; import mindustry.annotations.Annotations.*;
import mindustry.annotations.util.*;
import javax.lang.model.element.ExecutableElement;
/** Class that repesents a remote method to be constructed and put into a class. */ /** Class that repesents a remote method to be constructed and put into a class. */
public class MethodEntry{ public class MethodEntry{
@@ -10,6 +9,8 @@ public class MethodEntry{
public final String className; public final String className;
/** Fully qualified target method to call. */ /** Fully qualified target method to call. */
public final String targetMethod; 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. */ /** Whether this method can be called on a client/server. */
public final Loc where; public final Loc where;
/** /**
@@ -26,12 +27,13 @@ public class MethodEntry{
/** Unique method ID. */ /** Unique method ID. */
public final int id; public final int id;
/** The element method associated with this entry. */ /** The element method associated with this entry. */
public final ExecutableElement element; public final Smethod element;
/** The assigned packet priority. Only used in clients. */ /** The assigned packet priority. Only used in clients. */
public final PacketPriority priority; public final PacketPriority priority;
public MethodEntry(String className, String targetMethod, Loc where, Variant target, public MethodEntry(String className, String targetMethod, String packetClassName, Loc where, Variant target,
Loc local, boolean unreliable, boolean forward, int id, ExecutableElement element, PacketPriority priority){ Loc local, boolean unreliable, boolean forward, int id, Smethod element, PacketPriority priority){
this.packetClassName = packetClassName;
this.className = className; this.className = className;
this.forward = forward; this.forward = forward;
this.targetMethod = targetMethod; this.targetMethod = targetMethod;

View File

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

@@ -7,6 +7,7 @@ import com.sun.tools.javac.code.Attribute.Enum;
import com.sun.tools.javac.code.Attribute.Error; import com.sun.tools.javac.code.Attribute.Error;
import com.sun.tools.javac.code.Attribute.Visitor; import com.sun.tools.javac.code.Attribute.Visitor;
import com.sun.tools.javac.code.Attribute.*; import com.sun.tools.javac.code.Attribute.*;
import com.sun.tools.javac.code.Scope.*;
import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Symbol.*; import com.sun.tools.javac.code.Symbol.*;
import com.sun.tools.javac.code.Type.ArrayType; import com.sun.tools.javac.code.Type.ArrayType;
@@ -64,36 +65,13 @@ public class AnnotationProxyMaker{
LinkedHashMap map = new LinkedHashMap(); LinkedHashMap map = new LinkedHashMap();
ClassSymbol cl = (ClassSymbol)this.anno.type.tsym; ClassSymbol cl = (ClassSymbol)this.anno.type.tsym;
//try to use Java 8 API for this if possible for(Symbol s : cl.members().getSymbols(LookupKind.NON_RECURSIVE)){
try{ if(s.getKind() == ElementKind.METHOD){
Class entryClass = Class.forName("com.sun.tools.javac.code.Scope$Entry"); MethodSymbol var4 = (MethodSymbol)s;
Object members = cl.members(); Attribute var5 = var4.getDefaultValue();
Field field = members.getClass().getField("elems"); if(var5 != null){
Object elems = field.get(members); map.put(var4, var5);
Field siblingField = entryClass.getField("sibling");
Field symField = entryClass.getField("sym");
for(Object currEntry = elems; currEntry != null; currEntry = siblingField.get(currEntry)){
handleSymbol((Symbol)symField.get(currEntry), map);
}
}catch(Throwable e){
//otherwise try other API
try{
Class lookupClass = Class.forName("com.sun.tools.javac.code.Scope$LookupKind");
Field nonRecField = lookupClass.getField("NON_RECURSIVE");
Object nonRec = nonRecField.get(null);
Scope scope = cl.members();
Method getSyms = scope.getClass().getMethod("getSymbols", lookupClass);
Iterable<Symbol> it = (Iterable<Symbol>)getSyms.invoke(scope, nonRec);
Iterator<Symbol> i = it.iterator();
while(i.hasNext()){
handleSymbol(i.next(), map);
} }
}catch(Throwable death){
//I tried
throw new RuntimeException(death);
} }
} }
@@ -104,17 +82,6 @@ public class AnnotationProxyMaker{
return map; return map;
} }
private void handleSymbol(Symbol sym, LinkedHashMap map){
if(sym.getKind() == ElementKind.METHOD){
MethodSymbol var4 = (MethodSymbol)sym;
Attribute var5 = var4.getDefaultValue();
if(var5 != null){
map.put(var4, var5);
}
}
}
private Object generateValue(MethodSymbol var1, Attribute var2){ private Object generateValue(MethodSymbol var1, Attribute var2){
AnnotationProxyMaker.ValueVisitor var3 = new AnnotationProxyMaker.ValueVisitor(var1); AnnotationProxyMaker.ValueVisitor var3 = new AnnotationProxyMaker.ValueVisitor(var1);
return var3.getValue(var2); return var3.getValue(var2);

View File

@@ -12,6 +12,10 @@ import java.lang.Class;
import java.lang.annotation.*; import java.lang.annotation.*;
import java.lang.reflect.*; import java.lang.reflect.*;
/**
* Wrapper over Element with added utility functions.
* I would have preferred to use extension methods for this, but Java doesn't have any.
* */
public class Selement<T extends Element>{ public class Selement<T extends Element>{
public final T e; public final T e;

View File

@@ -25,7 +25,7 @@ public class Stype extends Selement<TypeElement>{
} }
public Seq<Stype> allInterfaces(){ public Seq<Stype> allInterfaces(){
return interfaces().flatMap(s -> s.allInterfaces().and(s)).distinct(); return interfaces().flatMap(s -> s.allInterfaces().add(s)).distinct();
} }
public Seq<Stype> superclasses(){ public Seq<Stype> superclasses(){
@@ -33,7 +33,7 @@ public class Stype extends Selement<TypeElement>{
} }
public Seq<Stype> allSuperclasses(){ public Seq<Stype> allSuperclasses(){
return superclasses().flatMap(s -> s.allSuperclasses().and(s)).distinct(); return superclasses().flatMap(s -> s.allSuperclasses().add(s)).distinct();
} }
public Stype superclass(){ public Stype superclass(){

View File

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

View File

@@ -16,7 +16,7 @@ public class TypeIOResolver{
* Maps fully qualified class names to their serializers. * Maps fully qualified class names to their serializers.
*/ */
public static ClassSerializer resolve(BaseProcessor processor){ public static ClassSerializer resolve(BaseProcessor processor){
ClassSerializer out = new ClassSerializer(new ObjectMap<>(), new ObjectMap<>(), new ObjectMap<>()); ClassSerializer out = new ClassSerializer(new ObjectMap<>(), new ObjectMap<>(), new ObjectMap<>(), new ObjectMap<>());
for(Stype type : processor.types(TypeIOHandler.class)){ for(Stype type : processor.types(TypeIOHandler.class)){
//look at all TypeIOHandler methods //look at all TypeIOHandler methods
Seq<Smethod> methods = type.methods(); Seq<Smethod> methods = type.methods();
@@ -25,7 +25,10 @@ public class TypeIOResolver{
Seq<Svar> params = meth.params(); Seq<Svar> params = meth.params();
//2 params, second one is type, first is writer //2 params, second one is type, first is writer
if(params.size == 2 && params.first().tname().toString().equals("arc.util.io.Writes")){ if(params.size == 2 && params.first().tname().toString().equals("arc.util.io.Writes")){
out.writers.put(fix(params.get(1).tname().toString()), type.fullName() + "." + meth.name()); //Net suffix indicates that this should only be used for sync operations
ObjectMap<String, String> targetMap = meth.name().endsWith("Net") ? out.netWriters : out.writers;
targetMap.put(fix(params.get(1).tname().toString()), type.fullName() + "." + meth.name());
}else if(params.size == 1 && params.first().tname().toString().equals("arc.util.io.Reads") && !meth.isVoid()){ }else if(params.size == 1 && params.first().tname().toString().equals("arc.util.io.Reads") && !meth.isVoid()){
//1 param, one is reader, returns type //1 param, one is reader, returns type
out.readers.put(fix(meth.retn().toString()), type.fullName() + "." + meth.name()); out.readers.put(fix(meth.retn().toString()), type.fullName() + "." + meth.name());
@@ -47,12 +50,17 @@ public class TypeIOResolver{
/** Information about read/write methods for class types. */ /** Information about read/write methods for class types. */
public static class ClassSerializer{ public static class ClassSerializer{
public final ObjectMap<String, String> writers, readers, mutatorReaders; public final ObjectMap<String, String> writers, readers, mutatorReaders, netWriters;
public ClassSerializer(ObjectMap<String, String> writers, ObjectMap<String, String> readers, ObjectMap<String, String> mutatorReaders){ public ClassSerializer(ObjectMap<String, String> writers, ObjectMap<String, String> readers, ObjectMap<String, String> mutatorReaders, ObjectMap<String, String> netWriters){
this.writers = writers; this.writers = writers;
this.readers = readers; this.readers = readers;
this.mutatorReaders = mutatorReaders; this.mutatorReaders = mutatorReaders;
this.netWriters = netWriters;
}
public String getNetWriter(String type, String fallback){
return netWriters.get(type, writers.get(type, fallback));
} }
} }
} }

View File

@@ -6,9 +6,12 @@ atrax=1
beta=30 beta=30
block=2 block=2
corvus=24 corvus=24
elude=45
flare=3 flare=3
gamma=31 gamma=31
latum=46
mace=4 mace=4
manifold=36
mega=5 mega=5
mindustry.entities.comp.BuildingComp=6 mindustry.entities.comp.BuildingComp=6
mindustry.entities.comp.BulletComp=7 mindustry.entities.comp.BulletComp=7
@@ -19,18 +22,29 @@ mindustry.entities.comp.LaunchCoreComp=11
mindustry.entities.comp.PlayerComp=12 mindustry.entities.comp.PlayerComp=12
mindustry.entities.comp.PosTeam=27 mindustry.entities.comp.PosTeam=27
mindustry.entities.comp.PosTeamDef=28 mindustry.entities.comp.PosTeamDef=28
mindustry.entities.comp.PowerGraphComp=41
mindustry.entities.comp.PowerGraphUpdaterComp=42
mindustry.entities.comp.PuddleComp=13 mindustry.entities.comp.PuddleComp=13
mindustry.entities.comp.WorldLabelComp=35
mindustry.type.Weather.WeatherStateComp=14 mindustry.type.Weather.WeatherStateComp=14
mindustry.world.blocks.campaign.LaunchPad.LaunchPayloadComp=15 mindustry.world.blocks.campaign.LaunchPad.LaunchPayloadComp=15
mindustry.world.blocks.campaign.PayloadLaunchPad.LargeLaunchPayloadComp=34
mindustry.world.blocks.defense.ForceProjector.ForceDrawComp=22 mindustry.world.blocks.defense.ForceProjector.ForceDrawComp=22
missile=39
mono=16 mono=16
nova=17 nova=17
oct=26 oct=26
osc=44
poly=18 poly=18
pulsar=19 pulsar=19
quad=23 quad=23
quasar=32 quasar=32
renale=47
risso=20 risso=20
spiroct=21 spiroct=21
stell=43
timed=38
timedDef=37
toxopid=33 toxopid=33
vanquish=40
vela=25 vela=25

View File

@@ -0,0 +1 @@
{version:1,fields:[{name:health,type:float},{name:items,type:mindustry.world.modules.ItemModule},{name:liquids,type:mindustry.world.modules.LiquidModule},{name:power,type:mindustry.world.modules.PowerModule},{name:team,type:mindustry.game.Team},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:1,fields:[{name:collided,type:arc.struct.IntSeq},{name:damage,type:float},{name:data,type:java.lang.Object},{name:fdata,type:float},{name:lifetime,type:float},{name:owner,type:mindustry.gen.Entityc},{name:rotation,type:float},{name:team,type:mindustry.game.Team},{name:time,type:float},{name:type,type:mindustry.entities.bullet.BulletType},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:2,fields:[{name:collided,type:arc.struct.IntSeq},{name:damage,type:float},{name:data,type:java.lang.Object},{name:fdata,type:float},{name:lifetime,type:float},{name:owner,type:mindustry.gen.Entityc},{name:rotation,type:float},{name:team,type:mindustry.game.Team},{name:time,type:float},{name:type,type:mindustry.entities.bullet.BulletType},{name:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:6,fields:[{name:color,type:arc.graphics.Color},{name:data,type:java.lang.Object},{name:effect,type:mindustry.entities.Effect},{name:lifetime,type:float},{name:offsetPos,type:float},{name:offsetRot,type:float},{name:offsetX,type:float},{name:offsetY,type:float},{name:parent,type:mindustry.gen.Posc},{name:rotWithParent,type:boolean},{name:rotation,type:float},{name:time,type:float},{name:x,type:float},{name:y,type:float}]}

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 @@
{version:1,fields:[{name:admin,type:boolean},{name:boosting,type:boolean},{name:color,type:arc.graphics.Color},{name:lastCommand,type:mindustry.ai.UnitCommand},{name:mouseX,type:float},{name:mouseY,type:float},{name:name,type:java.lang.String},{name:shooting,type:boolean},{name:team,type:mindustry.game.Team},{name:typing,type:boolean},{name:unit,type:Unit},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{fields:[]}

View File

@@ -0,0 +1 @@
{version:1,fields:[{name:amount,type:float},{name:liquid,type:mindustry.type.Liquid},{name:tile,type:mindustry.world.Tile},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{fields:[{name:flags,type:byte},{name:fontSize,type:float},{name:text,type:java.lang.String},{name:x,type:float},{name:y,type:float},{name:z,type:float}]}

View File

@@ -0,0 +1 @@
{version:2,fields:[{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:3,fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:2,fields:[{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:3,fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:2,fields:[{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:3,fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:6,fields:[{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:7,fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:6,fields:[{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:7,fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:6,fields:[{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:7,fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:2,fields:[{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:3,fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:6,fields:[{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:7,fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{fields:[{name:ammo,type:float},{name:building,type:Building},{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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:1,fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,type:float},{name:building,type:Building},{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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:4,fields:[{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:5,fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{fields:[{name:ammo,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:lifetime,type:float},{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:time,type:float},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:1,fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:lifetime,type:float},{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:time,type:float},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:5,fields:[{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:6,fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:4,fields:[{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:5,fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:4,fields:[{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:5,fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:4,fields:[{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:5,fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:2,fields:[{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:3,fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:5,fields:[{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:6,fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:2,fields:[{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:3,fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:6,fields:[{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:7,fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{fields:[{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:1,fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:5,fields:[{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:6,fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:2,fields:[{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{version:3,fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

View File

@@ -0,0 +1 @@
{fields:[{name:ammo,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:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]}

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