Merge remote-tracking branch 'upstream/master'
This commit is contained in:
11
.github/ISSUE_TEMPLATE/bug_report.md
vendored
11
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -7,11 +7,9 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Note**: Do not report any new bugs directly relating to the v6 campaign. They will not be fixed or considered at this time.
|
||||
|
||||
**Platform**: *Android/iOS/Mac/Windows/Linux*
|
||||
|
||||
**Build**: *The build number under the title in the main menu. Required.*
|
||||
**Build**: *The build number under the title in the main menu. Required. "LATEST" IS NOT A VERSION, I NEED THE EXACT BUILD NUMBER OF YOUR GAME.*
|
||||
|
||||
**Issue**: *Explain your issue in detail.*
|
||||
|
||||
@@ -19,11 +17,14 @@ assignees: ''
|
||||
|
||||
**Link(s) to mod(s) used**: *The mod repositories or zip files that are related to the issue, if applicable.*
|
||||
|
||||
**Save file**: *The save file you were playing on when the bug happened. REQUIRED for any issue that happens in-game.*
|
||||
**Save file**: *The (zipped) save file you were playing on when the bug happened. THIS IS REQUIRED FOR ANY ISSUE HAPPENING IN-GAME OR IN MULTIPLAYER, REGARDLESS OF WHETHER YOU THINK IT HAPPENS EVERYWHERE. DO NOT DELETE OR OMIT THIS LINE UNLESS YOU ARE SURE THAT THE ISSUE DOES NOT HAPPEN IN-GAME. IF YOU DO NOT HAVE A SAVE, DON'T WASTE TIME OPENING THIS ISSUE.*
|
||||
|
||||
**Crash report**: *The contents of relevant crash report files. REQUIRED if you are reporting a crash.*
|
||||
If you remove the line above without reading it properly and understanding what it means, I will reap your soul. Even if you're playing on someone's server, you can still save the game to a slot.
|
||||
|
||||
**(Crash) logs**: *Either crash reports from the crash folder, or the file you get when you go into Settings -> Game Data -> Export Crash logs. REQUIRED if you are reporting a crash.*
|
||||
|
||||
---
|
||||
|
||||
*Place an X (no spaces) between the brackets to confirm that you have read the line below.*
|
||||
- [ ] **I have updated to the latest release (https://github.com/Anuken/Mindustry/releases) to make sure my issue has not been fixed.**
|
||||
- [ ] **I have searched the closed and open issues to make sure that this problem has not already been reported.**
|
||||
|
||||
67
.github/workflows/deployment.yml
vendored
Normal file
67
.github/workflows/deployment.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
name: Deployment
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
buildJava14:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 14
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 14
|
||||
- name: Set env
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||
- name: Add Arc release
|
||||
run: |
|
||||
git clone --depth=1 --branch=master https://github.com/Anuken/Arc ../Arc
|
||||
cd ../Arc
|
||||
git tag ${RELEASE_VERSION}
|
||||
git push https://Anuken:${{ secrets.API_TOKEN_GITHUB }}@github.com/Anuken/Arc ${RELEASE_VERSION};
|
||||
cd ../Mindustry
|
||||
- name: Create artifacts
|
||||
run: |
|
||||
./gradlew desktop:dist server:dist core:mergedJavadoc -Pbuildversion=${RELEASE_VERSION:1}
|
||||
- name: Update docs
|
||||
run: |
|
||||
cd ../
|
||||
git config --global user.email "cli@github.com"
|
||||
git config --global user.name "Github Actions"
|
||||
git clone --depth=1 https://github.com/MindustryGame/docs.git
|
||||
cd docs
|
||||
find . -maxdepth 1 ! -name ".git" ! -name . -exec rm -r {} \;
|
||||
cd ../
|
||||
cp -a Mindustry/core/build/javadoc/. docs/
|
||||
cd docs
|
||||
git add .
|
||||
git commit -m "Update ${RELEASE_VERSION:1}"
|
||||
git push https://Anuken:${{ secrets.API_TOKEN_GITHUB }}@github.com/MindustryGame/docs
|
||||
cd ../Mindustry
|
||||
- name: Update F-Droid build string
|
||||
run: |
|
||||
git clone --depth=1 --branch=master https://github.com/Anuken/MindustryBuilds ../MindustryBuilds
|
||||
cd ../MindustryBuilds
|
||||
echo "Updating version to ${RELEASE_VERSION:1}"
|
||||
echo versionName=6-fdroid-${RELEASE_VERSION:1}$'\n'versionCode=${RELEASE_VERSION:1} > version_fdroid.txt
|
||||
git add .
|
||||
git commit -m "Updating to build ${RELEASE_VERSION:1}"
|
||||
git push https://Anuken:${{ secrets.API_TOKEN_GITHUB }}@github.com/Anuken/MindustryBuilds
|
||||
cd ../Mindustry
|
||||
- name: Upload client artifacts
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: desktop/build/libs/Mindustry.jar
|
||||
tag: ${{ github.ref }}
|
||||
- name: Upload server artifacts
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: server/build/libs/server-release.jar
|
||||
tag: ${{ github.ref }}
|
||||
|
||||
27
.github/workflows/gradle.yml
vendored
27
.github/workflows/gradle.yml
vendored
@@ -1,27 +0,0 @@
|
||||
name: Java CI
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
buildJava14:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Set up JDK 14
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 14
|
||||
- name: Run unit tests with gradle and Java 14
|
||||
run: ./gradlew compileJava
|
||||
buildJava15:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Set up JDK 15
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 15
|
||||
- name: Run unit tests with gradle and Java 15
|
||||
run: ./gradlew compileJava
|
||||
21
.github/workflows/pr.yml
vendored
Normal file
21
.github/workflows/pr.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: Pull Request Tests
|
||||
|
||||
on: [pull_request, workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
buildJava14:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 14
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 14
|
||||
- name: Run unit tests and build JAR
|
||||
run: ./gradlew test desktop:dist
|
||||
- name: Upload desktop JAR for testing
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Desktop JAR (zipped)
|
||||
path: desktop/build/libs/Mindustry.jar
|
||||
25
.github/workflows/push.yml
vendored
Normal file
25
.github/workflows/push.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Tests
|
||||
|
||||
on: [push, workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
buildJava14:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 14
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 14
|
||||
- name: Run unit tests
|
||||
run: ./gradlew clean cleanTest test
|
||||
- name: Trigger BE build
|
||||
if: ${{ github.repository == 'Anuken/Mindustry' }}
|
||||
run: |
|
||||
git clone --depth=1 --branch=master https://github.com/Anuken/MindustryBuilds ../MindustryBuilds
|
||||
cd ../MindustryBuilds
|
||||
BNUM=$(($GITHUB_RUN_NUMBER + 20000))
|
||||
git tag ${BNUM}
|
||||
git config --global user.name "Build Uploader"
|
||||
git push https://Anuken:${{ secrets.API_TOKEN_GITHUB }}@github.com/Anuken/MindustryBuilds ${BNUM}
|
||||
22
.gitignore
vendored
22
.gitignore
vendored
@@ -19,12 +19,13 @@ logs/
|
||||
/annotations/out/
|
||||
/net/build/
|
||||
/tools/build/
|
||||
/core/build/
|
||||
/tests/build/
|
||||
/server/build/
|
||||
changelog
|
||||
saves/
|
||||
/core/assets-raw/fontgen/out/
|
||||
/core/assets-raw/fontgen/icons/
|
||||
/core/assets-raw/fontgen/icon_parts/
|
||||
core/assets/saves/
|
||||
/core/assets/saves/
|
||||
steam_appid.txt
|
||||
@@ -35,16 +36,24 @@ steam_appid.txt
|
||||
/annotations/src/main/resources/META-INF/services
|
||||
/core/assets/version.properties
|
||||
/core/assets/locales
|
||||
/core/assets/cache/
|
||||
/ios/src/mindustry/gen/
|
||||
/core/src/mindustry/gen/
|
||||
ios/robovm.properties
|
||||
packr-out/
|
||||
config/
|
||||
*.gif
|
||||
/tests/out
|
||||
|
||||
/core/assets/basepartnames
|
||||
version.properties
|
||||
|
||||
#sprites
|
||||
|
||||
core/assets/sprites/sprites*
|
||||
core/assets/sprites/fallback/
|
||||
core/assets/sprites/block_colors.png
|
||||
|
||||
.attach_*
|
||||
## Java
|
||||
|
||||
@@ -93,6 +102,10 @@ com_crashlytics_export_strings.xml
|
||||
.externalToolBuilders/
|
||||
*.launch
|
||||
|
||||
## VSCode
|
||||
|
||||
.vscode/
|
||||
|
||||
## NetBeans
|
||||
|
||||
/nbproject/private/
|
||||
@@ -143,13 +156,6 @@ nb-configuration.xml
|
||||
/local.properties
|
||||
.gradle/
|
||||
gradle-app.setting
|
||||
/build/
|
||||
/android/build/
|
||||
/core/build/
|
||||
/desktop/build/
|
||||
/html/build/
|
||||
/ios/build/
|
||||
/ios-moe/build/
|
||||
|
||||
## OS Specific
|
||||
.DS_Store
|
||||
|
||||
50
.travis.yml
50
.travis.yml
@@ -1,50 +0,0 @@
|
||||
jdk:
|
||||
- openjdk14
|
||||
dist: xenial
|
||||
android:
|
||||
components:
|
||||
- android-29
|
||||
- build-tools-29.0.3
|
||||
|
||||
script:
|
||||
- git clone --depth=1 --branch=master https://github.com/Anuken/MindustryBuilds ../MindustryBuilds
|
||||
- cd ../MindustryBuilds
|
||||
- echo ${TRAVIS_TAG}
|
||||
- if [ -n "$TRAVIS_TAG" ]; then echo versionName=5-fdroid-${TRAVIS_TAG:1}$'\n'versionCode=${TRAVIS_TAG:1} > version_fdroid.txt; git add .; git commit -m "Updating to build ${TRAVIS_TAG}"; fi
|
||||
- git tag ${TRAVIS_BUILD_NUMBER}
|
||||
- git config --global user.name "Build Uploader"
|
||||
- if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then git push https://Anuken:${GH_PUSH_TOKEN}@github.com/Anuken/MindustryBuilds ${TRAVIS_BUILD_NUMBER}; git push https://Anuken:${GH_PUSH_TOKEN}@github.com/Anuken/MindustryBuilds; fi
|
||||
- cd ../Mindustry
|
||||
- git clone --depth=1 --branch=master https://github.com/Anuken/Arc ../Arc
|
||||
- if [ -n "$TRAVIS_TAG" ]; then cd ../Arc; git tag ${TRAVIS_TAG}; git push https://Anuken:${GH_PUSH_TOKEN}@github.com/Anuken/Arc ${TRAVIS_TAG}; cd ../Mindustry; fi
|
||||
- "./gradlew test"
|
||||
- "./gradlew desktop:dist -Pbuildversion=${TRAVIS_TAG:1}"
|
||||
- "./gradlew server:dist -Pbuildversion=${TRAVIS_TAG:1}"
|
||||
- "./gradlew core:javadoc"
|
||||
- cd ../
|
||||
- git clone --depth=1 https://github.com/MindustryGame/docs.git
|
||||
- cp -a Mindustry/core/build/docs/javadoc/. docs/
|
||||
- cd docs
|
||||
- if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then git add .; git commit -m "Update ${TRAVIS_BUILD_NUMBER}"; git push https://Anuken:${GH_PUSH_TOKEN}@github.com/MindustryGame/docs; fi
|
||||
- cd ../Mindustry
|
||||
deploy:
|
||||
- provider: releases
|
||||
skip_cleanup: true
|
||||
draft: false
|
||||
api_key:
|
||||
secure: Cv5wFtWt62/A24EvSEQvMow7gKPbZ3oATEFPuSghhB2TQz1dA40Zee3Qvk4LFlpLrhYo4K0ZSczCZRGpR+hCd8+Dpww52bheYEvWuh3ZQfvu/fXtEx2j5PwP1qMpmIgSxETV/gkD7l9FImdh0VzktYiAvQfmi0bEocG9/D4QwjFpNat7iwBdcMiw1MvAygpdIWRsjiw0RKlB2mWarmoHhQ7Gu7qlU3j50uaEvcrtmU0pBUPggNQwQRv32i9NPvNFxrqqlUjDLIS8JFea99zCkp8BwYqbEvBIMzd+Qip1/stLJJA3+cDUClbsDtg8rAVetzpOrdLEEBmqShFe5MDl2yEHcsgpN9CFsyTaUfvB3P3rVjizvycMm42IsUkXQiarm5xTQ/TIA8Rd8AHiSKuweNCg1Fd5SFaRtKy8JVLXuxyfUccmyje6hhz2L4lS2Wfj3mAG7sqZUCXhWP79EKdGkiPOjKv4CwXEKmuH3BMVqPlNUZJr9Eg3sV1FG0h2l+MVOOnR635qdUbb49sYojYxVruMLX0BH1c4ZCu230m8CUoWA1Em1QNI75ya7+9Y5T6AsgWDVpBvdUo9fWNbdp+VQ0GskFQsJD5wtnxbcbHeFiERAgGBm7z6qt9u9LrQpBH+dsW52ADvYsu3L4nQEa+sdMHwTTwmGY+iUvsxu0DqxGg=
|
||||
file:
|
||||
- desktop/build/libs/Mindustry.jar
|
||||
- server/build/libs/server-release.jar
|
||||
on:
|
||||
repo: Anuken/Mindustry
|
||||
tags: true
|
||||
#- provider: script
|
||||
# script: bash update_wiki.sh
|
||||
# on:
|
||||
# repo: Anuken/Mindustry
|
||||
# tags: true
|
||||
env:
|
||||
global:
|
||||
- secure: TqlUl/ojjkCMVOGbCTKz7Cnr4F08UyWzY/CiJ0vvUOGJGZ1qm7XavAlDf5XT0egU4mvr37THubFO8vojbqmrmy0oZnYh3njKFA8axgyZ8PyKkjGHOfd0i6qyEWsOr9H90/2X8r3LwEeLaDFyHpu3wljIGBjweg53g2qwmDwCFa9UR80FJZ+xDB+rD6B3cXT0DTEkCoLZXLqXm0Y3HvBdSuBL1LR/FNb2BSxNq+tNLGiz1kdQZV5erausbbZypBoGxzz63xAnyz2kkFz73A8xQYVTzGbFodTPz7HM13GVZ5s43I03Y+HYyHBgBaSLziO2hi2kzVJccOwzBp7wS4fs1MqsFY5+IeWJ9k+hm89NiYT7+6zlEgoUMlIniny1qLqWTzx7btUeuC/y/h5TVBNgaV+z0jmHycHfeSyq5I+vmX4J8qe3wmaN8TcdqYKU5nIznOTk3CM5Fzu0Bs9vkCkOxmormmcjMFW1RbdOLc/hpZWZggsBA88sNEAI8eq+r5QEeqzeCx8YKoZDjdrsqvgLMc3El3gS9oMGxkn0Y/TEcqs9Tc4BXtTkqIA68hD0DYzlAxYjVbbkAI9Hh9lHNvV3Dr/oCkGXQ/HflM143kj1L3tSBZpqeqQE2XhngB5nqpS3OZTmZbMTQ8qD2luU18yaTGMLF5tJS/fdKPRx0gQ1kL8=
|
||||
- secure: VEskj/0TVX2o7iUVXuVPysj/VSWmPhDl57SrT7/nBNN/P/8N5jFAvx8PMzG7qT0S5FzKxuV20psE4WylUGRKdeRtK7/QNBV7T3YqYYM6BUB1VeRpxe5hLxTeuBK3izglFO8DkdDqjUtzQSjzkoYT75ilROjhBrBUPhVek7UlbBHbaklPWYFXHnJmYS1FpZTdzqIj+Y0Gd1PSL2MzK4X74aAHl0qaDgsTwYwtKs7IAz+kFaTZBRpi9VjQHAFhDlkDR3jo9wQjH8/F6x0lCgV/FulSc37Okdb40sLFG98xcEA6gWh1NPMkz8CulUdVE7mj7SJNxLbNvoMNrWOVRjmEsn59p/9LiNC1F9ncFz9vjQjAmi7rMFFGHGxe5nn8cIAkpTvHQQkZoWHAA9SNJTDMMf09m2pRy/vvzx+a6NVxyC9iNrhLlnBg4gxAqRh0S6NU0uL+fuygKixn7rqlnb7KMT7bAbfcuV+dng6c8V7hYKDCh7sJbH8iJump1xkwoM7ecnU8fxJF/oKOr/fbk0Bfxu+Q9qYLrV1+DEdm93Vl2Thq+DBKmI66jRGSva6HeCLFo81PEiEjP1nLv75+kvVfOqVqJrZD1BrvoG2eWT/3hVLrN2kEIWWlpvQVC7FL11yWmYtAuOBh/vfhI76zKr+YTS6ccG9rqW4XeYjJytshe8M=
|
||||
@@ -15,9 +15,6 @@ If you are submitting a new block, make sure it has a name and description, and
|
||||
If you are interested in adding a large mechanic/feature or changing large amounts of code, first contact me (Anuken) via [Discord](https://discord.gg/mindustry) (preferred method) or via e-mail (*anukendev@gmail.com*).
|
||||
For most changes, this should not be necessary. I just want to know if you're doing something big so I can offer advice and/or make sure you're not wasting your time on it.
|
||||
|
||||
### Do not include packed sprites in your pull request.
|
||||
When making a pull request that changes or adds new sprites, do not add the modified atlas & `spritesX.png` files to your final pull request. These are a frequent source of conflicts.
|
||||
|
||||
|
||||
## Style Guidelines
|
||||
|
||||
@@ -29,8 +26,8 @@ This means:
|
||||
- `camelCase`, **even for constants or enums**. Why? Because `SCREAMING_CASE` is ugly, annoying to type and does not achieve anything useful. Constants are *less* dangerous than variables, not more. Any reasonable IDE should highlight them for you anyway.
|
||||
- No underscores for anything. (Yes, I know `Bindings` violates this principle, but that's for legacy reasons and really should be cleaned up some day)
|
||||
- Do not use braceless `if/else` statements. `if(x) statement else statement2` should **never** be done. In very specific situations, having braceless if-statements on one line is allowed: `if(cond) return;` would be valid.
|
||||
- Prefer single-line javadoc `/** @return for example */` instead of multiline javadoc whenver possible
|
||||
- Short method/variable names (multipleLongWords should be avoided if it's possible to so reasonably, especially for variables)
|
||||
- Prefer single-line javadoc `/** @return for example */` instead of multiline javadoc whenever possible
|
||||
- Short method/variable names (multipleLongWords should be avoided if it's possible to do so reasonably, especially for variables)
|
||||
- Use wildcard imports - `import some.package.*` - for everything. This makes incorrect class usage more obvious (*e.g. arc.util.Timer vs java.util.Timer*) and leads to cleaner-looking code.
|
||||
|
||||
Import [this style file](.github/Mindustry-CodeStyle-IJ.xml) into IntelliJ to get correct formatting when developing Mindustry.
|
||||
|
||||
52
ISSUES.md
Normal file
52
ISSUES.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Why was my issue closed?
|
||||
|
||||
This document goes over some common causes for issue closures.
|
||||
|
||||
## You did not fill in the template
|
||||
|
||||
I can't debug the problem unless you provide the information the template asks for.
|
||||
If you cannot put in the effort to fill out a template, then don't expect me to put in the effort to fix it.
|
||||
|
||||
## Your issue was already reported
|
||||
|
||||
If the problem in your issue has already been encountered before, it will be closed - especially if your report doesn't provide any new information.
|
||||
Make sure you search the *closed* issues before making an issue.
|
||||
|
||||
I do not link the specific issue(s) that report the same problem, because searching takes time - if you're interested in finding them, you should be able to do so without my help.
|
||||
|
||||
To be clear: I do **not** expect users to look at *all* previous issues, or do a comprehensive stack trace analysis to see if their crash was already reported.
|
||||
|
||||
## Your issue was already fixed
|
||||
|
||||
The problem you reported has been addressed. Note that this does **not** mean that the latest stable version of Mindustry has the fix!
|
||||
It simply means that I have committed (or am about to commit) a patch that fixes it *on the current development branch*.
|
||||
|
||||
## Your issue is missing a crash report or log
|
||||
|
||||
If the game crashes without a specific cause, and you don't send me a log, I can't fix it. There is no way for me to know what went wrong.
|
||||
|
||||
During a normal crash, the game should tell you where the log is saved. If not, you should still be able to look in the game's crash folder on most operating systems, or export the logs in *Settings -> Game Data -> Export Crash Logs*.
|
||||
|
||||
## Your issue is missing saves or screenshots
|
||||
|
||||
Even if you think your problem happens everywhere and saves/screenshots are redundant, this is frequently not the case.
|
||||
If I cannot reproduce the problem on my own saves and you have not linked any of your own, then the problem is likely to be save-specific. If you do not send me any, the problem cannot be investigated further.
|
||||
|
||||
## Your issue is related to an external program
|
||||
|
||||
If Mindustry causes something else to crash or misbehave, I am very unlikely to fix it. Unless the problem is serious, widespread and/or clearly a bug *in Mindustry*, it is not my responsibility.
|
||||
|
||||
Similarly, if you use another (invasive) program to change how Mindustry works, and something goes wrong, that is not my problem. Don't do it.
|
||||
|
||||
## Your issue is caused by mods
|
||||
|
||||
Crashes and bugs related to installed mods should be reported in the relevant mod repository, not here.
|
||||
*Note that problems with the Mindustry modding API are a separate problem, and do not apply.*
|
||||
|
||||
## I cannot reproduce your issue
|
||||
|
||||
If I follow your instructions and am repeatedly unable to reproduce the problem you've reported, then it is very unlikely to be fixed.
|
||||
Either the problem is device-specific, or there is not enough information given for me to be able to reproduce it.
|
||||
|
||||
I may attempt to change some code if I think it will make the issue less likely to occur, but without knowing for sure, the issue cannot be considered truly "fixed".
|
||||
As I cannot make any further progress on the problem, there is no reason to keep it open. If it is a common bug/crash, other people will come along with information that may shed some light on the issue.
|
||||
41
README.md
41
README.md
@@ -1,7 +1,7 @@
|
||||

|
||||
|
||||
[](https://travis-ci.org/Anuken/Mindustry)
|
||||
[](https://discord.gg/mindustry)
|
||||
[](https://github.com/Anuken/Mindustry/actions)
|
||||
[](https://discord.gg/mindustry)
|
||||
|
||||
A sandbox tower defense game written in Java.
|
||||
|
||||
@@ -9,34 +9,34 @@ _[Trello Board](https://trello.com/b/aE2tcUwF/mindustry-40-plans)_
|
||||
_[Wiki](https://mindustrygame.github.io/wiki)_
|
||||
_[Javadoc](https://mindustrygame.github.io/docs/)_
|
||||
|
||||
### Contributing
|
||||
## Contributing
|
||||
|
||||
See [CONTRIBUTING](CONTRIBUTING.md).
|
||||
|
||||
### Building
|
||||
## Building
|
||||
|
||||
Bleeding-edge builds are generated automatically for every commit. You can see them [here](https://github.com/Anuken/MindustryBuilds/releases).
|
||||
|
||||
If you'd rather compile on your own, follow these instructions.
|
||||
First, make sure you have [JDK 14](https://adoptopenjdk.net/) installed. Open a terminal in the root directory, `cd` to the Mindustry folder and run the following commands:
|
||||
|
||||
#### Windows
|
||||
### Windows
|
||||
|
||||
_Running:_ `gradlew desktop:run`
|
||||
_Building:_ `gradlew desktop:dist`
|
||||
_Sprite Packing:_ `gradlew tools:pack`
|
||||
|
||||
#### Linux/Mac OS
|
||||
### Linux/Mac OS
|
||||
|
||||
_Running:_ `./gradlew desktop:run`
|
||||
_Building:_ `./gradlew desktop:dist`
|
||||
_Sprite Packing:_ `./gradlew tools:pack`
|
||||
|
||||
#### Server
|
||||
### Server
|
||||
|
||||
Server builds are bundled with each released build (in Releases). If you'd rather compile on your own, replace 'desktop' with 'server', e.g. `gradlew server:dist`.
|
||||
|
||||
#### Android
|
||||
### Android
|
||||
|
||||
1. Install the Android SDK [here.](https://developer.android.com/studio#downloads) Make sure you're downloading the "Command line tools only", as Android Studio is not required.
|
||||
2. Set the `ANDROID_HOME` environment variable to point to your unzipped Android SDK directory.
|
||||
@@ -44,7 +44,9 @@ Server builds are bundled with each released build (in Releases). If you'd rathe
|
||||
|
||||
To debug the application on a connected phone, run `gradlew android:installDebug android:run`.
|
||||
|
||||
##### Troubleshooting
|
||||
### Troubleshooting
|
||||
|
||||
#### Permission Denied
|
||||
|
||||
If the terminal returns `Permission denied` or `Command not found` on Mac/Linux, run `chmod +x ./gradlew` before running `./gradlew`. *This is a one-time procedure.*
|
||||
|
||||
@@ -53,24 +55,11 @@ If the terminal returns `Permission denied` or `Command not found` on Mac/Linux,
|
||||
Gradle may take up to several minutes to download files. Be patient. <br>
|
||||
After building, the output .JAR file should be in `/desktop/build/libs/Mindustry.jar` for desktop builds, and in `/server/build/libs/server-release.jar` for server builds.
|
||||
|
||||
### Feature Requests
|
||||
## Feature Requests
|
||||
|
||||
Post feature requests and feedback [here](https://github.com/Anuken/Mindustry-Suggestions/issues/new/choose).
|
||||
|
||||
### Downloads
|
||||
## Downloads
|
||||
|
||||
[<img src="https://static.itch.io/images/badge.svg"
|
||||
alt="Get it on Itch.io"
|
||||
height="60">](https://anuke.itch.io/mindustry)
|
||||
|
||||
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png"
|
||||
alt="Get it on Google Play"
|
||||
height="80">](https://play.google.com/store/apps/details?id=io.anuke.mindustry)
|
||||
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||
alt="Get it on F-Droid"
|
||||
height="80">](https://f-droid.org/packages/io.anuke.mindustry/)
|
||||
|
||||
[<img src="https://flathub.org/assets/badges/flathub-badge-en.svg"
|
||||
alt="Download On Flathub"
|
||||
height="60">](https://flathub.org/apps/details/com.github.Anuken.Mindustry)
|
||||
| [](https://anuke.itch.io/mindustry) | [](https://play.google.com/store/apps/details?id=io.anuke.mindustry) | [](https://f-droid.org/packages/io.anuke.mindustry) | [](https://flathub.org/apps/details/com.github.Anuken.Mindustry)
|
||||
|--- |--- |--- |--- |
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
### Adding a server to the list
|
||||
|
||||
Mindustry now has a public list of servers that everyone can see and connect to.
|
||||
This is done by letting clients `GET` a [JSON list of servers](https://github.com/Anuken/Mindustry/blob/master/servers.json) in this repository.
|
||||
This is done by letting clients `GET` a [JSON list of servers](https://github.com/Anuken/Mindustry/blob/master/servers_v6.json) in this repository.
|
||||
|
||||
You may want to add your server to this list. The steps for getting this done are as follows:
|
||||
|
||||
1. **Ensure your server is properly moderated.** For the most part, this applies to survival servers, but PvP servers can be affected as well.
|
||||
You'll need to either hire some moderators, or make use of (currently non-existent) anti-grief and anti-curse plugins.
|
||||
You'll need to either hire some moderators, or make use of (currently non-existent) anti-grief and anti-curse plugins.
|
||||
*Consider enabling a rate limit:* `config messageRateLimit 2` will make it so that players can only send messages every 2 seconds, for example.
|
||||
2. **Set an appropriate MOTD, name and description.** This is set with `config <name/desc/motd> <value>`. "Appropriate" means that:
|
||||
2. Make sure that your server is able to handle inappropriate content - this includes NSFW display/sorter art and abusive messages. **Servers that allow such content will be removed immediately.** Consider banning display blocks if it is a problem for your server: `rules add bannedBlocks ["logic-display", "large-logic-display"]`.
|
||||
3. **Set an appropriate MOTD, name and description.** This is set with `config <name/desc/motd> <value>`. "Appropriate" means that:
|
||||
- Your name or description must reflect the type of server you're hosting.
|
||||
Since new players may be exposed to the server list early on, put in a phrase like "Co-op survival" or "PvP" so players know what they're getting into. Yes, this is also displayed in the server mode info text, but having extra info in the name doesn't hurt.
|
||||
- Make sure players know where to refer to for server support. It should be fairly clear that the server owner is not me, but you.
|
||||
- Try to be professional in your text; use common sense.
|
||||
3. **Get some good maps.** *(optional, but highly recommended)*. Add some maps to your server and set the map rotation to custom-only. You can get maps from the Steam workshop by subscribing and exporting them; using the `#maps` channel on Discord is also an option.
|
||||
4. **Check your server configuration.** *(optional)* I would recommend adding a message rate limit of 1 second (`config messageRateLimit 1`), and disabling connect/disconnect messages to reduce spam (`config showConnectMessages false`).
|
||||
5. Finally, **submit a pull request** to add your server's IP to the list.
|
||||
This should be fairly straightforward: Press the edit button on the [server file](https://github.com/Anuken/Mindustry/blob/master/servers.json), then add a JSON object with a single key, indicating your server address.
|
||||
4. **Get some good maps.** *(optional, but highly recommended)*. Add some maps to your server and set the map rotation to custom-only. You can get maps from the Steam workshop by subscribing and exporting them; using the `#maps` channel on Discord is also an option.
|
||||
5. **Check your server configuration.** *(optional)* I would recommend adding a message rate limit of 1 second (`config messageRateLimit 1`), and disabling connect/disconnect messages to reduce spam (`config showConnectMessages false`).
|
||||
6. Finally, **submit a pull request** to add your server's IP to the list.
|
||||
This should be fairly straightforward: Press the edit button on the [server file](https://github.com/Anuken/Mindustry/blob/master/servers_v6.json), then add a JSON object with a single key, indicating your server address.
|
||||
For example, if your server address is `google.com`, you would add a comma after the last entry and insert:
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
android:usesCleartextTraffic="true"
|
||||
android:appCategory="game"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/ArcTheme" android:fullBackupContent="@xml/backup_rules">
|
||||
android:theme="@style/ArcTheme"
|
||||
android:fullBackupContent="@xml/backup_rules">
|
||||
<meta-data android:name="android.max_aspect" android:value="2.1"/>
|
||||
<activity
|
||||
android:name="mindustry.android.AndroidLauncher"
|
||||
|
||||
@@ -4,7 +4,6 @@ buildscript{
|
||||
mavenCentral()
|
||||
google()
|
||||
maven{ url "https://oss.sonatype.org/content/repositories/snapshots/" }
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies{
|
||||
@@ -20,22 +19,8 @@ configurations{ natives }
|
||||
|
||||
repositories{
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
maven{ url "https://maven.google.com" }
|
||||
}
|
||||
|
||||
dependencies{
|
||||
implementation project(":core")
|
||||
|
||||
implementation arcModule("backends:backend-android")
|
||||
implementation 'com.jakewharton.android.repackaged:dalvik-dx:9.0.0_r3'
|
||||
|
||||
natives "com.github.Anuken.Arc:natives-android:${getArcHash()}"
|
||||
natives "com.github.Anuken.Arc:natives-freetype-android:${getArcHash()}"
|
||||
natives "com.github.Anuken.Arc:natives-box2d-android:${getArcHash()}"
|
||||
|
||||
//android dependencies magically disappear during compilation, thanks gradle!
|
||||
if(new File(projectDir.parent, '../Arc').exists()) compileOnly fileTree(dir: '../../Arc/backends/backend-android/libs', include: ['*.jar'])
|
||||
jcenter() //remove later once google/JetBrains fixes the dependency
|
||||
}
|
||||
|
||||
task deploy(type: Copy){
|
||||
@@ -47,8 +32,8 @@ task deploy(type: Copy){
|
||||
}
|
||||
|
||||
android{
|
||||
buildToolsVersion '29.0.3'
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion '30.0.2'
|
||||
compileSdkVersion 30
|
||||
sourceSets{
|
||||
main{
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
@@ -59,9 +44,13 @@ android{
|
||||
assets.srcDirs = ['assets', 'src/main/assets', '../core/assets/']
|
||||
jniLibs.srcDirs = ['libs']
|
||||
}
|
||||
gp{
|
||||
java.srcDirs = ['srcgp']
|
||||
}
|
||||
|
||||
androidTest.setRoot('tests')
|
||||
}
|
||||
|
||||
packagingOptions{
|
||||
exclude 'META-INF/robovm/ios/robovm.xml'
|
||||
}
|
||||
@@ -73,10 +62,10 @@ android{
|
||||
|
||||
applicationId "io.anuke.mindustry"
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 29
|
||||
targetSdkVersion 30
|
||||
|
||||
versionName versionNameResult
|
||||
versionCode vcode
|
||||
versionCode = (System.getenv("TRAVIS_BUILD_ID") != null ? System.getenv("TRAVIS_BUILD_ID").toInteger() : vcode)
|
||||
|
||||
if(project.hasProperty("release")){
|
||||
props['androidBuildCode'] = (vcode + 1).toString()
|
||||
@@ -98,23 +87,63 @@ android{
|
||||
storePassword RELEASE_STORE_PASSWORD
|
||||
keyAlias RELEASE_KEY_ALIAS
|
||||
keyPassword RELEASE_KEY_PASSWORD
|
||||
}else if(System.getenv("CI") == "true"){
|
||||
storeFile = file("../../bekeystore.jks")
|
||||
storePassword = System.getenv("keystore_password")
|
||||
keyAlias = System.getenv("keystore_alias")
|
||||
keyPassword = System.getenv("keystore_alias_password")
|
||||
}else{
|
||||
println("No keystore property found. Releases will be unsigned.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(project.hasProperty("RELEASE_STORE_FILE")) {
|
||||
buildTypes {
|
||||
release {
|
||||
buildTypes{
|
||||
all{
|
||||
minifyEnabled = true
|
||||
shrinkResources = true
|
||||
proguardFiles("proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
|
||||
if(project.hasProperty("RELEASE_STORE_FILE") || System.getenv("CI") == "true"){
|
||||
buildTypes{
|
||||
release{
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Specifies one flavor dimension.
|
||||
flavorDimensions "version"
|
||||
productFlavors{
|
||||
standard{
|
||||
|
||||
}
|
||||
gp{
|
||||
applicationIdSuffix ".gp"
|
||||
versionNameSuffix "-gp"
|
||||
}
|
||||
}
|
||||
}
|
||||
// called every time gradle gets executed, takes the native dependencies of
|
||||
// the natives configuration, and extracts them to the proper libs/ folders
|
||||
// so they get packed with the APK.
|
||||
|
||||
dependencies{
|
||||
implementation project(":core")
|
||||
|
||||
implementation arcModule("backends:backend-android")
|
||||
implementation 'com.jakewharton.android.repackaged:dalvik-dx:9.0.0_r3'
|
||||
|
||||
natives "com.github.Anuken.Arc:natives-android:${getArcHash()}"
|
||||
natives "com.github.Anuken.Arc:natives-freetype-android:${getArcHash()}"
|
||||
|
||||
gpImplementation "com.google.android.gms:play-services-games:21.0.0"
|
||||
gpImplementation "com.google.android.gms:play-services-auth:19.0.0"
|
||||
|
||||
//android dependencies magically disappear during compilation, thanks gradle!
|
||||
def sdkFile = new File((String)findSdkDir(), "/platforms/android-29/android.jar")
|
||||
if(sdkFile.exists()) compileOnly files(sdkFile.absolutePath)
|
||||
}
|
||||
|
||||
task copyAndroidNatives(){
|
||||
configurations.natives.files.each{ jar ->
|
||||
copy{
|
||||
@@ -126,23 +155,13 @@ task copyAndroidNatives(){
|
||||
}
|
||||
|
||||
task run(type: Exec){
|
||||
def path
|
||||
def localProperties = project.file("../local.properties")
|
||||
if(localProperties.exists()){
|
||||
Properties properties = new Properties()
|
||||
localProperties.withInputStream{ instr ->
|
||||
properties.load(instr)
|
||||
}
|
||||
def sdkDir = properties.getProperty('sdk.dir')
|
||||
if(sdkDir){
|
||||
path = sdkDir
|
||||
}else{
|
||||
path = "$System.env.ANDROID_HOME"
|
||||
commandLine "${findSdkDir()}/platform-tools/adb", 'shell', 'am', 'start', '-n', 'io.anuke.mindustry/mindustry.android.AndroidLauncher'
|
||||
}
|
||||
|
||||
if(!project.ext.hasSprites()){
|
||||
tasks.whenTaskAdded{ task ->
|
||||
if(task.name == 'assembleDebug' || task.name == 'assembleRelease'){
|
||||
task.dependsOn ":tools:pack"
|
||||
}
|
||||
}else{
|
||||
path = "$System.env.ANDROID_HOME"
|
||||
}
|
||||
|
||||
def adb = path + "/platform-tools/adb"
|
||||
commandLine "$adb", 'shell', 'am', 'start', '-n', 'io.anuke.mindustry/mindustry.android.AndroidLauncher'
|
||||
}
|
||||
|
||||
8
android/proguard-rules.pro
vendored
Normal file
8
android/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
-dontobfuscate
|
||||
|
||||
#these are essential packages that should not be "optimized" in any way
|
||||
#the main purpose of d8 here is to shrink the absurdly-large google play games libraries
|
||||
-keep class mindustry.** { *; }
|
||||
-keep class arc.** { *; }
|
||||
-keep class net.jpountz.** { *; }
|
||||
-keep class rhino.** { *; }
|
||||
@@ -1,6 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name">Mindustry</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<resources>
|
||||
|
||||
<style name="ArcTheme" parent="android:Theme">
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:colorBackgroundCacheHint">@null</item>
|
||||
@@ -8,5 +7,4 @@
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -15,9 +15,11 @@ import arc.func.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import dalvik.system.*;
|
||||
import io.anuke.mindustry.*;
|
||||
import mindustry.*;
|
||||
import mindustry.game.Saves.*;
|
||||
import mindustry.io.*;
|
||||
import mindustry.mod.*;
|
||||
import mindustry.net.*;
|
||||
import mindustry.ui.dialogs.*;
|
||||
|
||||
@@ -27,13 +29,15 @@ import java.util.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
|
||||
public class AndroidLauncher extends AndroidApplication{
|
||||
public static final int PERMISSION_REQUEST_CODE = 1;
|
||||
boolean doubleScaleTablets = true;
|
||||
FileChooser chooser;
|
||||
Runnable permCallback;
|
||||
|
||||
Object gpService;
|
||||
Class<?> serviceClass;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState){
|
||||
UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
@@ -45,13 +49,13 @@ public class AndroidLauncher extends AndroidApplication{
|
||||
if(handler != null){
|
||||
handler.uncaughtException(thread, error);
|
||||
}else{
|
||||
error.printStackTrace();
|
||||
Log.err(error);
|
||||
System.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
if(doubleScaleTablets && isTablet(this.getContext())){
|
||||
if(doubleScaleTablets && isTablet(this)){
|
||||
Scl.setAddition(0.5f);
|
||||
}
|
||||
|
||||
@@ -64,7 +68,9 @@ public class AndroidLauncher extends AndroidApplication{
|
||||
|
||||
@Override
|
||||
public rhino.Context getScriptContext(){
|
||||
return AndroidRhinoContext.enter(getContext().getCacheDir());
|
||||
rhino.Context result = AndroidRhinoContext.enter(((Context)AndroidLauncher.this).getCacheDir());
|
||||
result.setClassShutter(Scripts::allowClass);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -72,17 +78,16 @@ public class AndroidLauncher extends AndroidApplication{
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> loadJar(Fi jar, String mainClass) throws Exception{
|
||||
DexClassLoader loader = new DexClassLoader(jar.file().getPath(), getFilesDir().getPath(), null, getClassLoader());
|
||||
return Class.forName(mainClass, true, loader);
|
||||
public ClassLoader loadJar(Fi jar, ClassLoader parent) throws Exception{
|
||||
return new DexClassLoader(jar.file().getPath(), getFilesDir().getPath(), null, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showFileChooser(boolean open, String extension, Cons<Fi> cons){
|
||||
showFileChooser(open, cons, extension);
|
||||
public void showFileChooser(boolean open, String title, String extension, Cons<Fi> cons){
|
||||
showFileChooser(open, title, cons, extension);
|
||||
}
|
||||
|
||||
void showFileChooser(boolean open, Cons<Fi> cons, String... extensions){
|
||||
void showFileChooser(boolean open, String title, Cons<Fi> cons, String... extensions){
|
||||
String extension = extensions[0];
|
||||
|
||||
if(VERSION.SDK_INT >= VERSION_CODES.Q){
|
||||
@@ -119,7 +124,7 @@ public class AndroidLauncher extends AndroidApplication{
|
||||
});
|
||||
}else if(VERSION.SDK_INT >= VERSION_CODES.M && !(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&
|
||||
checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)){
|
||||
chooser = new FileChooser(open ? "@open" : "@save", file -> Structs.contains(extensions, file.extension().toLowerCase()), open, file -> {
|
||||
chooser = new FileChooser(title, file -> Structs.contains(extensions, file.extension().toLowerCase()), open, file -> {
|
||||
if(!open){
|
||||
cons.get(file.parent().child(file.nameWithoutExtension() + "." + extension));
|
||||
}else{
|
||||
@@ -137,16 +142,16 @@ public class AndroidLauncher extends AndroidApplication{
|
||||
requestPermissions(perms.toArray(new String[0]), PERMISSION_REQUEST_CODE);
|
||||
}else{
|
||||
if(open){
|
||||
new FileChooser("@open", file -> Structs.contains(extensions, file.extension().toLowerCase()), true, cons).show();
|
||||
new FileChooser(title, file -> Structs.contains(extensions, file.extension().toLowerCase()), true, cons).show();
|
||||
}else{
|
||||
super.showFileChooser(open, extension, cons);
|
||||
super.showFileChooser(open, "@open", extension, cons);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showMultiFileChooser(Cons<Fi> cons, String... extensions){
|
||||
showFileChooser(true, cons, extensions);
|
||||
showFileChooser(true, "@open", cons, extensions);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -162,33 +167,47 @@ public class AndroidLauncher extends AndroidApplication{
|
||||
}, new AndroidApplicationConfiguration(){{
|
||||
useImmersiveMode = true;
|
||||
hideStatusBar = true;
|
||||
stencil = 8;
|
||||
}});
|
||||
checkFiles(getIntent());
|
||||
|
||||
try{
|
||||
//new external folder
|
||||
Fi data = Core.files.absolute(((Context)this).getExternalFilesDir(null).getAbsolutePath());
|
||||
Core.settings.setDataDirectory(data);
|
||||
|
||||
//new external folder
|
||||
Fi data = Core.files.absolute(getContext().getExternalFilesDir(null).getAbsolutePath());
|
||||
Core.settings.setDataDirectory(data);
|
||||
|
||||
//move to internal storage if there's no file indicating that it moved
|
||||
if(!Core.files.local("files_moved").exists()){
|
||||
Log.info("Moving files to external storage...");
|
||||
|
||||
//delete unused cache folder to free up space
|
||||
try{
|
||||
//current local storage folder
|
||||
Fi src = Core.files.absolute(Core.files.getLocalStoragePath());
|
||||
for(Fi fi : src.list()){
|
||||
fi.copyTo(data);
|
||||
Fi cache = Core.settings.getDataDirectory().child("cache");
|
||||
if(cache.exists()){
|
||||
cache.deleteDirectory();
|
||||
}
|
||||
//create marker
|
||||
Core.files.local("files_moved").writeString("files moved to " + data);
|
||||
Core.files.local("files_moved_103").writeString("files moved again");
|
||||
Log.info("Files moved.");
|
||||
}catch(Throwable t){
|
||||
Log.err("Failed to move files!");
|
||||
t.printStackTrace();
|
||||
Log.err("Failed to delete cached folder", t);
|
||||
}
|
||||
|
||||
|
||||
//move to internal storage if there's no file indicating that it moved
|
||||
if(!Core.files.local("files_moved").exists()){
|
||||
Log.info("Moving files to external storage...");
|
||||
|
||||
try{
|
||||
//current local storage folder
|
||||
Fi src = Core.files.absolute(Core.files.getLocalStoragePath());
|
||||
for(Fi fi : src.list()){
|
||||
fi.copyTo(data);
|
||||
}
|
||||
//create marker
|
||||
Core.files.local("files_moved").writeString("files moved to " + data);
|
||||
Core.files.local("files_moved_103").writeString("files moved again");
|
||||
Log.info("Files moved.");
|
||||
}catch(Throwable t){
|
||||
Log.err("Failed to move files!");
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
}catch(Exception e){
|
||||
//print log but don't crash
|
||||
Log.err(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,6 +227,24 @@ public class AndroidLauncher extends AndroidApplication{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume(){
|
||||
super.onResume();
|
||||
|
||||
//TODO enable once GPGS is set up on the GP console
|
||||
if(false && BuildConfig.FLAVOR.equals("gp")){
|
||||
try{
|
||||
if(gpService == null){
|
||||
serviceClass = Class.forName("mindustry.android.GPGameService");
|
||||
gpService = serviceClass.getConstructor().newInstance();
|
||||
}
|
||||
serviceClass.getMethod("onResume", Context.class).invoke(gpService, this);
|
||||
}catch(Exception e){
|
||||
Log.err("Failed to update Google Play Services", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkFiles(Intent intent){
|
||||
try{
|
||||
Uri uri = intent.getData();
|
||||
|
||||
@@ -175,7 +175,7 @@ public class AndroidRhinoContext{
|
||||
}catch(IOException e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
android.content.Context context = ((AndroidApplication) Core.app).getContext();
|
||||
android.content.Context context = (android.content.Context)((AndroidApplication)Core.app);
|
||||
return new DexClassLoader(dexFile.getPath(), VERSION.SDK_INT >= 21 ? context.getCodeCacheDir().getPath() : context.getCacheDir().getAbsolutePath(), null, getParent()).loadClass(name);
|
||||
}
|
||||
|
||||
|
||||
40
android/srcgp/mindustry/android/GPGameService.java
Normal file
40
android/srcgp/mindustry/android/GPGameService.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package mindustry.android;
|
||||
|
||||
import android.content.*;
|
||||
import arc.util.*;
|
||||
import com.google.android.gms.auth.api.signin.*;
|
||||
import com.google.android.gms.games.*;
|
||||
import mindustry.service.*;
|
||||
|
||||
public class GPGameService extends GameService{
|
||||
private GoogleSignInAccount account;
|
||||
|
||||
public void onResume(Context context){
|
||||
Log.info("[GooglePlayService] Resuming.");
|
||||
|
||||
GoogleSignInAccount current = GoogleSignIn.getLastSignedInAccount(context);
|
||||
|
||||
GoogleSignInOptions options =
|
||||
new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN)
|
||||
.requestScopes(Games.SCOPE_GAMES_SNAPSHOTS)
|
||||
.build();
|
||||
|
||||
if(GoogleSignIn.hasPermissions(current, options.getScopeArray())){
|
||||
this.account = current;
|
||||
Log.info("Already signed in to Google Play Games.");
|
||||
}else{
|
||||
GoogleSignIn.getClient(context, options).silentSignIn().addOnCompleteListener(complete -> {
|
||||
if(!complete.isSuccessful()){
|
||||
if(complete.getException() != null){
|
||||
Log.err("Failed to sign in to Google Play Games.", complete.getException());
|
||||
}else{
|
||||
Log.warn("Failed to sign in to Google Play Games.");
|
||||
}
|
||||
}else{
|
||||
this.account = complete.getResult();
|
||||
Log.info("Signed in to Google Play Games.");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
sourceSets.main.java.srcDirs = ["src/main/java/"]
|
||||
sourceSets.main.resources.srcDirs = ["src/main/resources/"]
|
||||
sourceSets.main.resources.srcDirs = ["src/main/resources/"]
|
||||
|
||||
@@ -98,6 +98,8 @@ public class Annotations{
|
||||
boolean serialize() default true;
|
||||
/** Whether to generate IO code. This is for advanced usage only. */
|
||||
boolean genio() default true;
|
||||
/** Whether I made a massive mistake by merging two different class branches */
|
||||
boolean legacy() default false;
|
||||
}
|
||||
|
||||
/** Indicates an internal interface for entity components. */
|
||||
@@ -116,7 +118,7 @@ public class Annotations{
|
||||
/**
|
||||
* The region name to load. Variables can be used:
|
||||
* "@" -> block name
|
||||
* "$size" -> block size
|
||||
* "@size" -> block size
|
||||
* "#" "#1" "#2" -> index number, for arrays
|
||||
* */
|
||||
String value();
|
||||
@@ -175,12 +177,12 @@ public class Annotations{
|
||||
//region remote
|
||||
|
||||
public enum PacketPriority{
|
||||
/** Does not get handled unless client is connected. */
|
||||
low,
|
||||
/** Gets put in a queue and processed if not connected. */
|
||||
normal,
|
||||
/** Gets handled immediately, regardless of connection status. */
|
||||
high,
|
||||
/** Does not get handled unless client is connected. */
|
||||
low
|
||||
}
|
||||
|
||||
/** A set of two booleans, one specifying server and one specifying client. */
|
||||
|
||||
@@ -2,15 +2,10 @@ package mindustry.annotations;
|
||||
|
||||
import arc.files.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.Log;
|
||||
import arc.util.Log.*;
|
||||
import arc.util.*;
|
||||
import arc.util.Log.*;
|
||||
import com.squareup.javapoet.*;
|
||||
import com.sun.source.util.*;
|
||||
import com.sun.tools.javac.model.*;
|
||||
import com.sun.tools.javac.processing.*;
|
||||
import com.sun.tools.javac.tree.*;
|
||||
import com.sun.tools.javac.util.*;
|
||||
import mindustry.annotations.util.*;
|
||||
|
||||
import javax.annotation.processing.*;
|
||||
@@ -22,7 +17,6 @@ import javax.tools.Diagnostic.*;
|
||||
import javax.tools.*;
|
||||
import java.io.*;
|
||||
import java.lang.annotation.*;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
@SupportedSourceVersion(SourceVersion.RELEASE_8)
|
||||
@@ -31,19 +25,16 @@ public abstract class BaseProcessor extends AbstractProcessor{
|
||||
public static final String packageName = "mindustry.gen";
|
||||
|
||||
public static Types typeu;
|
||||
public static JavacElements elementu;
|
||||
public static Elements elementu;
|
||||
public static Filer filer;
|
||||
public static Messager messager;
|
||||
public static Trees trees;
|
||||
public static TreeMaker maker;
|
||||
|
||||
protected int round;
|
||||
protected int rounds = 1;
|
||||
protected RoundEnvironment env;
|
||||
protected Fi rootDirectory;
|
||||
|
||||
protected Context context;
|
||||
|
||||
public static String getMethodName(Element element){
|
||||
return ((TypeElement)element.getEnclosingElement()).getQualifiedName().toString() + "." + element.getSimpleName();
|
||||
}
|
||||
@@ -115,12 +106,12 @@ public abstract class BaseProcessor extends AbstractProcessor{
|
||||
return ClassName.get(c).box();
|
||||
}
|
||||
|
||||
public static TypeVariableName getTVN(TypeParameterElement element) {
|
||||
public static TypeVariableName getTVN(TypeParameterElement element){
|
||||
String name = element.getSimpleName().toString();
|
||||
List<? extends TypeMirror> boundsMirrors = element.getBounds();
|
||||
|
||||
List<TypeName> boundsTypeNames = new ArrayList<>();
|
||||
for (TypeMirror typeMirror : boundsMirrors) {
|
||||
for(TypeMirror typeMirror : boundsMirrors){
|
||||
boundsTypeNames.add(TypeName.get(typeMirror));
|
||||
}
|
||||
|
||||
@@ -137,11 +128,11 @@ public abstract class BaseProcessor extends AbstractProcessor{
|
||||
if(imports != null){
|
||||
String rawSource = file.toString();
|
||||
Seq<String> result = new Seq<>();
|
||||
for (String s : rawSource.split("\n", -1)) {
|
||||
for(String s : rawSource.split("\n", -1)){
|
||||
result.add(s);
|
||||
if (s.startsWith("package ")) {
|
||||
if (s.startsWith("package ")){
|
||||
result.add("");
|
||||
for (String i : imports) {
|
||||
for (String i : imports){
|
||||
result.add(i);
|
||||
}
|
||||
}
|
||||
@@ -186,7 +177,7 @@ public abstract class BaseProcessor extends AbstractProcessor{
|
||||
Log.err("[CODEGEN ERROR] " + message + ": " + elem);
|
||||
}
|
||||
|
||||
public void err(String message, Selement elem){
|
||||
public static void err(String message, Selement elem){
|
||||
err(message, elem.e);
|
||||
}
|
||||
|
||||
@@ -194,20 +185,16 @@ public abstract class BaseProcessor extends AbstractProcessor{
|
||||
public synchronized void init(ProcessingEnvironment env){
|
||||
super.init(env);
|
||||
|
||||
JavacProcessingEnvironment javacProcessingEnv = (JavacProcessingEnvironment)env;
|
||||
|
||||
trees = Trees.instance(env);
|
||||
typeu = env.getTypeUtils();
|
||||
elementu = javacProcessingEnv.getElementUtils();
|
||||
elementu = env.getElementUtils();
|
||||
filer = env.getFiler();
|
||||
messager = env.getMessager();
|
||||
context = ((JavacProcessingEnvironment)env).getContext();
|
||||
maker = TreeMaker.instance(javacProcessingEnv.getContext());
|
||||
|
||||
Log.setLogLevel(LogLevel.info);
|
||||
Log.level = LogLevel.info;
|
||||
|
||||
if(System.getProperty("debug") != null){
|
||||
Log.setLogLevel(LogLevel.debug);
|
||||
Log.level = LogLevel.debug;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +206,7 @@ public abstract class BaseProcessor extends AbstractProcessor{
|
||||
String path = Fi.get(filer.getResource(StandardLocation.CLASS_OUTPUT, "no", "no")
|
||||
.toUri().toURL().toString().substring(OS.isWindows ? 6 : "file:".length()))
|
||||
.parent().parent().parent().parent().parent().parent().parent().toString().replace("%20", " ");
|
||||
rootDirectory = Fi.get(path);
|
||||
rootDirectory = Fi.get(path).parent();
|
||||
}catch(IOException e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -229,7 +216,7 @@ public abstract class BaseProcessor extends AbstractProcessor{
|
||||
try{
|
||||
process(roundEnv);
|
||||
}catch(Throwable e){
|
||||
e.printStackTrace();
|
||||
Log.err(e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -3,7 +3,6 @@ package mindustry.annotations.entity;
|
||||
import arc.files.*;
|
||||
import arc.func.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import arc.util.io.*;
|
||||
import arc.util.pooling.Pool.*;
|
||||
@@ -133,6 +132,7 @@ public class EntityProcess extends BaseProcessor{
|
||||
.build())).addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT).build());
|
||||
}
|
||||
|
||||
//generate interface getters and setters for all "standard" fields
|
||||
for(Svar field : component.fields().select(e -> !e.is(Modifier.STATIC) && !e.is(Modifier.PRIVATE) && !e.has(Import.class))){
|
||||
String cname = field.name();
|
||||
|
||||
@@ -241,7 +241,6 @@ public class EntityProcess extends BaseProcessor{
|
||||
//look at each definition
|
||||
for(Selement<?> type : allDefs){
|
||||
EntityDef ann = type.annotation(EntityDef.class);
|
||||
boolean isFinal = ann.isFinal();
|
||||
|
||||
//all component classes (not interfaces)
|
||||
Seq<Stype> components = allComponents(type);
|
||||
@@ -275,6 +274,13 @@ public class EntityProcess extends BaseProcessor{
|
||||
name += "Entity";
|
||||
}
|
||||
|
||||
boolean legacy = ann.legacy();
|
||||
|
||||
if(legacy){
|
||||
baseClass = tname(packageName + "." + name);
|
||||
name += "Legacy" + Strings.capitalize(type.name());
|
||||
}
|
||||
|
||||
//skip double classes
|
||||
if(usedNames.containsKey(name)){
|
||||
extraNames.get(usedNames.get(name), ObjectSet::new).add(type.name());
|
||||
@@ -336,7 +342,8 @@ public class EntityProcess extends BaseProcessor{
|
||||
boolean isVisible = !f.is(Modifier.STATIC) && !f.is(Modifier.PRIVATE) && !f.has(ReadOnly.class);
|
||||
|
||||
//add the field only if it isn't visible or it wasn't implemented by the base class
|
||||
if(!isShadowed || !isVisible){
|
||||
//legacy classes have no extra fields
|
||||
if((!isShadowed || !isVisible) && !legacy){
|
||||
builder.addField(spec);
|
||||
}
|
||||
|
||||
@@ -346,7 +353,7 @@ public class EntityProcess extends BaseProcessor{
|
||||
allFields.add(f);
|
||||
|
||||
//add extra sync fields
|
||||
if(f.has(SyncField.class) && isSync){
|
||||
if(f.has(SyncField.class) && isSync && !legacy){
|
||||
if(!f.tname().toString().equals("float")) err("All SyncFields must be of type float", f);
|
||||
|
||||
syncedFields.add(f);
|
||||
@@ -379,7 +386,7 @@ public class EntityProcess extends BaseProcessor{
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addStatement("return $S + $L", name + "#", "id").build());
|
||||
|
||||
EntityIO io = new EntityIO(type.name(), builder, allFieldSpecs, serializer, rootDirectory.child("annotations/src/main/resources/revisions").child(name));
|
||||
EntityIO io = new EntityIO(type.name(), builder, allFieldSpecs, serializer, rootDirectory.child("annotations/src/main/resources/revisions").child(type.name()));
|
||||
//entities with no sync comp and no serialization gen no code
|
||||
boolean hasIO = ann.genio() && (components.contains(s -> s.name().contains("Sync")) || ann.serialize());
|
||||
|
||||
@@ -440,11 +447,14 @@ public class EntityProcess extends BaseProcessor{
|
||||
}
|
||||
}
|
||||
|
||||
boolean specialIO = false;
|
||||
|
||||
if(hasIO){
|
||||
//SPECIAL CASE: I/O code
|
||||
//note that serialization is generated even for non-serializing entities for manual usage
|
||||
if((first.name().equals("read") || first.name().equals("write"))){
|
||||
io.write(mbuilder, first.name().equals("write"));
|
||||
specialIO = true;
|
||||
}
|
||||
|
||||
//SPECIAL CASE: sync I/O code
|
||||
@@ -523,7 +533,9 @@ public class EntityProcess extends BaseProcessor{
|
||||
mbuilder.addStatement("mindustry.gen.Groups.queueFree(($T)this)", Poolable.class);
|
||||
}
|
||||
|
||||
builder.addMethod(mbuilder.build());
|
||||
if(!legacy || specialIO){
|
||||
builder.addMethod(mbuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
//add pool reset method and implement Poolable
|
||||
@@ -558,7 +570,7 @@ public class EntityProcess extends BaseProcessor{
|
||||
.returns(tname(packageName + "." + name))
|
||||
.addStatement(ann.pooled() ? "return Pools.obtain($L.class, " +name +"::new)" : "return new $L()", name).build());
|
||||
|
||||
definitions.add(new EntityDefinition(packageName + "." + name, builder, type, typeIsBase ? null : baseClass, components, groups, allFieldSpecs));
|
||||
definitions.add(new EntityDefinition(packageName + "." + name, builder, type, typeIsBase ? null : baseClass, components, groups, allFieldSpecs, legacy));
|
||||
}
|
||||
|
||||
//generate groups
|
||||
@@ -663,11 +675,28 @@ public class EntityProcess extends BaseProcessor{
|
||||
//build mapping class for sync IDs
|
||||
TypeSpec.Builder idBuilder = TypeSpec.classBuilder("EntityMapping").addModifiers(Modifier.PUBLIC)
|
||||
.addField(FieldSpec.builder(TypeName.get(Prov[].class), "idMap", Modifier.PUBLIC, Modifier.STATIC).initializer("new Prov[256]").build())
|
||||
|
||||
.addField(FieldSpec.builder(ParameterizedTypeName.get(ClassName.get(ObjectMap.class),
|
||||
tname(String.class), tname(Prov.class)),
|
||||
"nameMap", Modifier.PUBLIC, Modifier.STATIC).initializer("new ObjectMap<>()").build())
|
||||
|
||||
.addField(FieldSpec.builder(ParameterizedTypeName.get(ClassName.get(IntMap.class), tname(String.class)),
|
||||
"customIdMap", Modifier.PUBLIC, Modifier.STATIC).initializer("new IntMap<>()").build())
|
||||
|
||||
.addMethod(MethodSpec.methodBuilder("register").addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.returns(TypeName.get(int.class))
|
||||
.addParameter(String.class, "name").addParameter(Prov.class, "constructor")
|
||||
.addStatement("int next = arc.util.Structs.indexOf(idMap, v -> v == null)")
|
||||
.addStatement("idMap[next] = constructor")
|
||||
.addStatement("nameMap.put(name, constructor)")
|
||||
.addStatement("customIdMap.put(next, name)")
|
||||
.addStatement("return next")
|
||||
.addJavadoc("Use this method for obtaining a classId for custom modded unit types. Only call this once for each type. Modded types should return this id in their overridden classId method.")
|
||||
.build())
|
||||
|
||||
.addMethod(MethodSpec.methodBuilder("map").addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.returns(TypeName.get(Prov.class)).addParameter(int.class, "id").addStatement("return idMap[id]").build())
|
||||
|
||||
.addMethod(MethodSpec.methodBuilder("map").addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.returns(TypeName.get(Prov.class)).addParameter(String.class, "name").addStatement("return nameMap.get(name)").build());
|
||||
|
||||
@@ -696,11 +725,6 @@ public class EntityProcess extends BaseProcessor{
|
||||
}else{
|
||||
//round 3: generate actual classes and implement interfaces
|
||||
|
||||
//write base classes
|
||||
for(TypeSpec.Builder b : baseClasses){
|
||||
write(b, imports.asArray());
|
||||
}
|
||||
|
||||
//implement each definition
|
||||
for(EntityDefinition def : definitions){
|
||||
|
||||
@@ -723,6 +747,14 @@ public class EntityProcess extends BaseProcessor{
|
||||
|
||||
def.builder.addSuperinterface(inter.tname());
|
||||
|
||||
if(def.legacy) continue;
|
||||
|
||||
@Nullable TypeSpec.Builder superclass = null;
|
||||
|
||||
if(def.extend != null){
|
||||
superclass = baseClasses.find(b -> (packageName + "." + Reflect.get(b, "name")).equals(def.extend.toString()));
|
||||
}
|
||||
|
||||
//generate getter/setter for each method
|
||||
for(Smethod method : inter.methods()){
|
||||
String var = method.name();
|
||||
@@ -730,14 +762,36 @@ public class EntityProcess extends BaseProcessor{
|
||||
//make sure it's a real variable AND that the component doesn't already implement it somewhere with custom logic
|
||||
if(field == null || methodNames.contains(method.simpleString())) continue;
|
||||
|
||||
MethodSpec result = null;
|
||||
|
||||
//getter
|
||||
if(!method.isVoid()){
|
||||
def.builder.addMethod(MethodSpec.overriding(method.e).addStatement("return " + var).build());
|
||||
result = MethodSpec.overriding(method.e).addStatement("return " + var).build();
|
||||
}
|
||||
|
||||
//setter
|
||||
if(method.isVoid() && !Seq.with(field.annotations).contains(f -> f.type.toString().equals("@mindustry.annotations.Annotations.ReadOnly"))){
|
||||
def.builder.addMethod(MethodSpec.overriding(method.e).addStatement("this." + var + " = " + var).build());
|
||||
result = MethodSpec.overriding(method.e).addStatement("this." + var + " = " + var).build();
|
||||
}
|
||||
|
||||
//add getter/setter to parent class, if possible. when this happens, skip adding getters setters *here* because they are defined in the superclass.
|
||||
if(result != null && superclass != null){
|
||||
FieldSpec superField = Seq.with(superclass.fieldSpecs).find(f -> f.name.equals(var));
|
||||
|
||||
//found the right field, try to check for the method already existing now
|
||||
if(superField != null){
|
||||
MethodSpec fr = result;
|
||||
MethodSpec targetMethod = Seq.with(superclass.methodSpecs).find(m -> m.name.equals(var) && m.returnType.equals(fr.returnType));
|
||||
//if the method isn't added yet, add it. in any case, skip.
|
||||
if(targetMethod == null){
|
||||
superclass.addMethod(result);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if(result != null){
|
||||
def.builder.addMethod(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -745,8 +799,16 @@ public class EntityProcess extends BaseProcessor{
|
||||
write(def.builder, imports.asArray());
|
||||
}
|
||||
|
||||
//write base classes last
|
||||
for(TypeSpec.Builder b : baseClasses){
|
||||
write(b, imports.asArray());
|
||||
}
|
||||
|
||||
//TODO nulls were an awful idea
|
||||
//store nulls
|
||||
TypeSpec.Builder nullsBuilder = TypeSpec.classBuilder("Nulls").addModifiers(Modifier.PUBLIC).addModifiers(Modifier.FINAL);
|
||||
//TODO should be dynamic
|
||||
ObjectSet<String> nullList = ObjectSet.with("unit");
|
||||
|
||||
//create mock types of all components
|
||||
for(Stype interf : allInterfaces){
|
||||
@@ -765,6 +827,12 @@ public class EntityProcess extends BaseProcessor{
|
||||
|
||||
//create null builder
|
||||
String baseName = interf.name().substring(0, interf.name().length() - 1);
|
||||
|
||||
//prevent Nulls bloat
|
||||
if(!nullList.contains(Strings.camelize(baseName))){
|
||||
continue;
|
||||
}
|
||||
|
||||
String className = "Null" + baseName;
|
||||
TypeSpec.Builder nullBuilder = TypeSpec.classBuilder(className)
|
||||
.addModifiers(Modifier.FINAL);
|
||||
@@ -898,7 +966,7 @@ public class EntityProcess extends BaseProcessor{
|
||||
}
|
||||
|
||||
String createName(Selement<?> elem){
|
||||
Seq<Stype> comps = types(elem.annotation(EntityDef.class), EntityDef::value).map(this::interfaceToComp);;
|
||||
Seq<Stype> comps = types(elem.annotation(EntityDef.class), EntityDef::value).map(this::interfaceToComp);
|
||||
comps.sortComparing(Selement::name);
|
||||
return comps.toString("", s -> s.name().replace("Comp", ""));
|
||||
}
|
||||
@@ -942,9 +1010,10 @@ public class EntityProcess extends BaseProcessor{
|
||||
final Selement naming;
|
||||
final String name;
|
||||
final @Nullable TypeName extend;
|
||||
final boolean legacy;
|
||||
int classID;
|
||||
|
||||
public EntityDefinition(String name, Builder builder, Selement naming, TypeName extend, Seq<Stype> components, Seq<GroupDefinition> groups, Seq<FieldSpec> fieldSpec){
|
||||
public EntityDefinition(String name, Builder builder, Selement naming, TypeName extend, Seq<Stype> components, Seq<GroupDefinition> groups, Seq<FieldSpec> fieldSpec, boolean legacy){
|
||||
this.builder = builder;
|
||||
this.name = name;
|
||||
this.naming = naming;
|
||||
@@ -952,6 +1021,7 @@ public class EntityProcess extends BaseProcessor{
|
||||
this.components = components;
|
||||
this.extend = extend;
|
||||
this.fieldSpecs = fieldSpec;
|
||||
this.legacy = legacy;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -32,27 +32,40 @@ public class AssetsProcess extends BaseProcessor{
|
||||
MethodSpec.Builder load = MethodSpec.methodBuilder("load").addModifiers(Modifier.PUBLIC, Modifier.STATIC);
|
||||
MethodSpec.Builder loadStyles = MethodSpec.methodBuilder("loadStyles").addModifiers(Modifier.PUBLIC, Modifier.STATIC);
|
||||
MethodSpec.Builder icload = MethodSpec.methodBuilder("load").addModifiers(Modifier.PUBLIC, Modifier.STATIC);
|
||||
CodeBlock.Builder ichinit = CodeBlock.builder();
|
||||
String resources = rootDirectory + "/core/assets-raw/sprites/ui";
|
||||
Jval icons = Jval.read(Fi.get(rootDirectory + "/core/assets-raw/fontgen/config.json").readString());
|
||||
|
||||
ObjectMap<String, String> texIcons = new OrderedMap<>();
|
||||
PropertiesUtils.load(texIcons, Fi.get(rootDirectory + "/core/assets/icons/icons.properties").reader());
|
||||
|
||||
StringBuilder iconcAll = new StringBuilder();
|
||||
|
||||
texIcons.each((key, val) -> {
|
||||
String[] split = val.split("\\|");
|
||||
String name = Strings.kebabToCamel(split[1]).replace("Medium", "").replace("Icon", "");
|
||||
if(SourceVersion.isKeyword(name) || name.equals("char")) name = name + "i";
|
||||
String name = Strings.kebabToCamel(split[1]).replace("Medium", "").replace("Icon", "").replace("Ui", "");
|
||||
if(SourceVersion.isKeyword(name) || name.equals("char")) name += "i";
|
||||
|
||||
ichtype.addField(FieldSpec.builder(char.class, name, Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer("(char)" + key).build());
|
||||
ichtype.addField(FieldSpec.builder(char.class, name, Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).addJavadoc(String.format("\\u%04x", Integer.parseInt(key))).initializer("'" + ((char)Integer.parseInt(key)) + "'").build());
|
||||
});
|
||||
|
||||
ictype.addField(FieldSpec.builder(ParameterizedTypeName.get(ObjectMap.class, String.class, TextureRegionDrawable.class),
|
||||
"icons", Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer("new ObjectMap<>()").build());
|
||||
|
||||
ichtype.addField(FieldSpec.builder(ParameterizedTypeName.get(ObjectIntMap.class, String.class),
|
||||
"codes", Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer("new ObjectIntMap<>()").build());
|
||||
|
||||
ObjectSet<String> used = new ObjectSet<>();
|
||||
|
||||
for(Jval val : icons.get("glyphs").asArray()){
|
||||
String name = capitalize(val.getString("css", ""));
|
||||
|
||||
if(!val.getBool("selected", true) || !used.add(name)) continue;
|
||||
|
||||
int code = val.getInt("code", 0);
|
||||
ichtype.addField(FieldSpec.builder(char.class, name, Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer("(char)" + code).build());
|
||||
iconcAll.append((char)code);
|
||||
ichtype.addField(FieldSpec.builder(char.class, name, Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).addJavadoc(String.format("\\u%04x", code)).initializer("'" + ((char)code) + "'").build());
|
||||
ichinit.addStatement("codes.put($S, $L)", name, code);
|
||||
|
||||
ictype.addField(TextureRegionDrawable.class, name + "Small", Modifier.PUBLIC, Modifier.STATIC);
|
||||
icload.addStatement(name + "Small = mindustry.ui.Fonts.getGlyph(mindustry.ui.Fonts.def, (char)" + code + ")");
|
||||
@@ -64,6 +77,9 @@ public class AssetsProcess extends BaseProcessor{
|
||||
icload.addStatement("icons.put($S, " + name + "Small)", name + "Small");
|
||||
}
|
||||
|
||||
ichtype.addField(FieldSpec.builder(String.class, "all", Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer("$S", iconcAll.toString()).build());
|
||||
ichtype.addStaticBlock(ichinit.build());
|
||||
|
||||
Fi.get(resources).walk(p -> {
|
||||
if(!p.extEquals("png")) return;
|
||||
|
||||
@@ -78,7 +94,7 @@ public class AssetsProcess extends BaseProcessor{
|
||||
if(SourceVersion.isKeyword(varname)) varname += "s";
|
||||
|
||||
type.addField(ClassName.bestGuess(dtype), varname, Modifier.STATIC, Modifier.PUBLIC);
|
||||
load.addStatement(varname + " = ("+dtype+")arc.Core.atlas.drawable($S)", sfilen);
|
||||
load.addStatement(varname + " = (" + dtype + ")arc.Core.atlas.drawable($S)", sfilen);
|
||||
});
|
||||
|
||||
for(Element elem : elements){
|
||||
@@ -101,12 +117,10 @@ public class AssetsProcess extends BaseProcessor{
|
||||
|
||||
void processSounds(String classname, String path, String rtype) throws Exception{
|
||||
TypeSpec.Builder type = TypeSpec.classBuilder(classname).addModifiers(Modifier.PUBLIC);
|
||||
MethodSpec.Builder dispose = MethodSpec.methodBuilder("dispose").addModifiers(Modifier.PUBLIC, Modifier.STATIC);
|
||||
MethodSpec.Builder loadBegin = MethodSpec.methodBuilder("load").addModifiers(Modifier.PUBLIC, Modifier.STATIC);
|
||||
|
||||
HashSet<String> names = new HashSet<>();
|
||||
Fi.get(path).walk(p -> {
|
||||
String fname = p.name();
|
||||
String name = p.nameWithoutExtension();
|
||||
|
||||
if(names.contains(name)){
|
||||
@@ -115,27 +129,21 @@ public class AssetsProcess extends BaseProcessor{
|
||||
names.add(name);
|
||||
}
|
||||
|
||||
if(SourceVersion.isKeyword(name)){
|
||||
name = name + "s";
|
||||
}
|
||||
if(SourceVersion.isKeyword(name)) name += "s";
|
||||
|
||||
String filepath = path.substring(path.lastIndexOf("/") + 1) + "/" + fname;
|
||||
String filepath = path.substring(path.lastIndexOf("/") + 1) + p.path().substring(p.path().lastIndexOf(path) + path.length());
|
||||
|
||||
String filename = "arc.Core.app.getType() != arc.Application.ApplicationType.iOS ? \"" + filepath + "\" : \"" + filepath.replace(".ogg", ".mp3")+"\"";
|
||||
String filename = "\"" + filepath + "\"";
|
||||
loadBegin.addStatement("arc.Core.assets.load(" + filename + ", " + rtype + ".class).loaded = a -> " + name + " = (" + rtype + ")a", filepath, filepath.replace(".ogg", ".mp3"));
|
||||
|
||||
loadBegin.addStatement("arc.Core.assets.load("+filename +", "+rtype+".class).loaded = a -> " + name + " = ("+rtype+")a", filepath, filepath.replace(".ogg", ".mp3"));
|
||||
|
||||
dispose.addStatement("arc.Core.assets.unload(" + filename + ")");
|
||||
dispose.addStatement(name + " = null");
|
||||
type.addField(FieldSpec.builder(ClassName.bestGuess(rtype), name, Modifier.STATIC, Modifier.PUBLIC).initializer("new arc.mock.Mock" + rtype.substring(rtype.lastIndexOf(".") + 1)+ "()").build());
|
||||
type.addField(FieldSpec.builder(ClassName.bestGuess(rtype), name, Modifier.STATIC, Modifier.PUBLIC).initializer("new arc.audio." + rtype.substring(rtype.lastIndexOf(".") + 1) + "()").build());
|
||||
});
|
||||
|
||||
if(classname.equals("Sounds")){
|
||||
type.addField(FieldSpec.builder(ClassName.bestGuess(rtype), "none", Modifier.STATIC, Modifier.PUBLIC).initializer("new arc.mock.Mock" + rtype.substring(rtype.lastIndexOf(".") + 1)+ "()").build());
|
||||
type.addField(FieldSpec.builder(ClassName.bestGuess(rtype), "none", Modifier.STATIC, Modifier.PUBLIC).initializer("new arc.audio." + rtype.substring(rtype.lastIndexOf(".") + 1) + "()").build());
|
||||
}
|
||||
|
||||
type.addMethod(loadBegin.build());
|
||||
type.addMethod(dispose.build());
|
||||
JavaFile.builder(packageName, type.build()).build().writeTo(BaseProcessor.filer);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ public class LoadRegionProcessor extends BaseProcessor{
|
||||
@Override
|
||||
public void process(RoundEnvironment env) throws Exception{
|
||||
TypeSpec.Builder regionClass = TypeSpec.classBuilder("ContentRegions")
|
||||
.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class).addMember("value", "\"deprecation\"").build())
|
||||
.addModifiers(Modifier.PUBLIC);
|
||||
MethodSpec.Builder method = MethodSpec.methodBuilder("loadRegions")
|
||||
.addParameter(tname("mindustry.ctype.MappableContent"), "content")
|
||||
@@ -34,7 +35,7 @@ public class LoadRegionProcessor extends BaseProcessor{
|
||||
}
|
||||
|
||||
for(Entry<Stype, Seq<Svar>> entry : fieldMap){
|
||||
method.beginControlFlow("if(content instanceof $T)", entry.key.tname());
|
||||
method.beginControlFlow("if(content instanceof $L)", entry.key.fullName());
|
||||
|
||||
for(Svar field : entry.value){
|
||||
Load an = field.annotation(Load.class);
|
||||
@@ -45,7 +46,7 @@ public class LoadRegionProcessor extends BaseProcessor{
|
||||
|
||||
//not an array
|
||||
if(dims == 0){
|
||||
method.addStatement("(($T)content).$L = $T.atlas.find($L$L)", entry.key.tname(), field.name(), Core.class, parse(an.value()), fallbackString);
|
||||
method.addStatement("(($L)content).$L = $T.atlas.find($L$L)", entry.key.fullName(), field.name(), Core.class, parse(an.value()), fallbackString);
|
||||
}else{
|
||||
//is an array, create length string
|
||||
int[] lengths = an.lengths();
|
||||
|
||||
@@ -26,7 +26,8 @@ public class LogicStatementProcessor extends BaseProcessor{
|
||||
MethodSpec.Builder reader = MethodSpec.methodBuilder("read")
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.returns(tname("mindustry.logic.LStatement"))
|
||||
.addParameter(String[].class, "tokens");
|
||||
.addParameter(String[].class, "tokens")
|
||||
.addParameter(int.class, "length");
|
||||
|
||||
Seq<Stype> types = types(RegisterStatement.class);
|
||||
|
||||
@@ -43,9 +44,9 @@ public class LogicStatementProcessor extends BaseProcessor{
|
||||
String name = c.annotation(RegisterStatement.class).value();
|
||||
|
||||
if(beganWrite){
|
||||
writer.nextControlFlow("else if(obj instanceof $T)", c.mirror());
|
||||
writer.nextControlFlow("else if(obj.getClass() == $T.class)", c.mirror());
|
||||
}else{
|
||||
writer.beginControlFlow("if(obj instanceof $T)", c.mirror());
|
||||
writer.beginControlFlow("if(obj.getClass() == $T.class)", c.mirror());
|
||||
beganWrite = true;
|
||||
}
|
||||
|
||||
@@ -53,6 +54,7 @@ public class LogicStatementProcessor extends BaseProcessor{
|
||||
writer.addStatement("out.append($S)", name);
|
||||
|
||||
Seq<Svar> fields = c.fields();
|
||||
fields.addAll(c.superclass().fields());
|
||||
|
||||
String readSt = "if(tokens[0].equals($S))";
|
||||
if(beganRead){
|
||||
@@ -75,7 +77,7 @@ public class LogicStatementProcessor extends BaseProcessor{
|
||||
"");
|
||||
|
||||
//reading primitives, strings and enums is supported; nothing else is
|
||||
reader.addStatement("if(tokens.length > $L) result.$L = $L(tokens[$L])",
|
||||
reader.addStatement("if(length > $L) result.$L = $L(tokens[$L])",
|
||||
index + 1,
|
||||
field.name(),
|
||||
field.mirror().toString().equals("java.lang.String") ?
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
package mindustry.annotations.remote;
|
||||
|
||||
import arc.struct.*;
|
||||
import arc.util.io.*;
|
||||
import com.squareup.javapoet.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.annotations.*;
|
||||
import mindustry.annotations.util.*;
|
||||
import mindustry.annotations.util.TypeIOResolver.*;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
import java.io.*;
|
||||
|
||||
import static mindustry.annotations.BaseProcessor.*;
|
||||
|
||||
/** Generates code for writing remote invoke packets on the client and server. */
|
||||
public class CallGenerator{
|
||||
|
||||
/** Generates all classes in this list. */
|
||||
public static void generate(ClassSerializer serializer, Seq<MethodEntry> methods) throws IOException{
|
||||
//create builder
|
||||
TypeSpec.Builder callBuilder = TypeSpec.classBuilder(RemoteProcess.callLocation).addModifiers(Modifier.PUBLIC);
|
||||
|
||||
MethodSpec.Builder register = MethodSpec.methodBuilder("registerPackets")
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC);
|
||||
|
||||
//go through each method entry in this class
|
||||
for(MethodEntry ent : methods){
|
||||
//builder for the packet type
|
||||
TypeSpec.Builder packet = TypeSpec.classBuilder(ent.packetClassName)
|
||||
.addModifiers(Modifier.PUBLIC);
|
||||
|
||||
packet.superclass(tname("mindustry.net.Packet"));
|
||||
|
||||
//return the correct priority
|
||||
if(ent.priority != PacketPriority.normal){
|
||||
packet.addMethod(MethodSpec.methodBuilder("getPriority")
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(Override.class).returns(int.class).addStatement("return $L", ent.priority.ordinal())
|
||||
.build());
|
||||
}
|
||||
|
||||
//implement read & write methods
|
||||
packet.addMethod(makeWriter(ent, serializer));
|
||||
packet.addMethod(makeReader(ent, serializer));
|
||||
|
||||
//generate handlers
|
||||
if(ent.where.isClient){
|
||||
packet.addMethod(writeHandleMethod(ent, false));
|
||||
}
|
||||
|
||||
if(ent.where.isServer){
|
||||
packet.addMethod(writeHandleMethod(ent, true));
|
||||
}
|
||||
|
||||
//register packet
|
||||
register.addStatement("mindustry.net.Net.registerPacket($L.$L::new)", packageName, ent.packetClassName);
|
||||
|
||||
//add fields to the type
|
||||
for(Svar param : ent.element.params()){
|
||||
packet.addField(param.tname(), param.name(), Modifier.PUBLIC);
|
||||
}
|
||||
|
||||
//write the 'send event to all players' variant: always happens for clients, but only happens if 'all' is enabled on the server method
|
||||
if(ent.where.isClient || ent.target.isAll){
|
||||
writeCallMethod(callBuilder, ent, true, false);
|
||||
}
|
||||
|
||||
//write the 'send event to one player' variant, which is only applicable on the server
|
||||
if(ent.where.isServer && ent.target.isOne){
|
||||
writeCallMethod(callBuilder, ent, false, false);
|
||||
}
|
||||
|
||||
//write the forwarded method version
|
||||
if(ent.where.isServer && ent.forward){
|
||||
writeCallMethod(callBuilder, ent, true, true);
|
||||
}
|
||||
|
||||
//write the completed packet class
|
||||
JavaFile.builder(packageName, packet.build()).build().writeTo(BaseProcessor.filer);
|
||||
}
|
||||
|
||||
callBuilder.addMethod(register.build());
|
||||
|
||||
//build and write resulting class
|
||||
TypeSpec spec = callBuilder.build();
|
||||
JavaFile.builder(packageName, spec).build().writeTo(BaseProcessor.filer);
|
||||
}
|
||||
|
||||
private static MethodSpec makeWriter(MethodEntry ent, ClassSerializer serializer){
|
||||
MethodSpec.Builder builder = MethodSpec.methodBuilder("write")
|
||||
.addParameter(Writes.class, "WRITE")
|
||||
.addModifiers(Modifier.PUBLIC).addAnnotation(Override.class);
|
||||
Seq<Svar> params = ent.element.params();
|
||||
|
||||
for(int i = 0; i < params.size; i++){
|
||||
//first argument is skipped as it is always the player caller
|
||||
if(!ent.where.isServer && i == 0){
|
||||
continue;
|
||||
}
|
||||
|
||||
Svar var = params.get(i);
|
||||
|
||||
//name of parameter
|
||||
String varName = var.name();
|
||||
//name of parameter type
|
||||
String typeName = var.mirror().toString();
|
||||
//special case: method can be called from anywhere to anywhere
|
||||
//thus, only write the player when the SERVER is writing data, since the client is the only one who reads the player anyway
|
||||
boolean writePlayerSkipCheck = ent.where == Loc.both && i == 0;
|
||||
|
||||
if(writePlayerSkipCheck){ //write begin check
|
||||
builder.beginControlFlow("if(mindustry.Vars.net.server())");
|
||||
}
|
||||
|
||||
if(BaseProcessor.isPrimitive(typeName)){ //check if it's a primitive, and if so write it
|
||||
builder.addStatement("WRITE.$L($L)", typeName.equals("boolean") ? "bool" : typeName.charAt(0) + "", varName);
|
||||
}else{
|
||||
//else, try and find a serializer
|
||||
String ser = serializer.writers.get(typeName.replace("mindustry.gen.", ""), SerializerResolver.locate(ent.element.e, var.mirror(), true));
|
||||
|
||||
if(ser == null){ //make sure a serializer exists!
|
||||
BaseProcessor.err("No method to write class type: '" + typeName + "'", var);
|
||||
}
|
||||
|
||||
//add statement for writing it
|
||||
builder.addStatement(ser + "(WRITE, " + varName + ")");
|
||||
}
|
||||
|
||||
if(writePlayerSkipCheck){ //write end check
|
||||
builder.endControlFlow();
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static MethodSpec makeReader(MethodEntry ent, ClassSerializer serializer){
|
||||
MethodSpec.Builder builder = MethodSpec.methodBuilder("read")
|
||||
.addParameter(Reads.class, "READ")
|
||||
.addModifiers(Modifier.PUBLIC).addAnnotation(Override.class);
|
||||
Seq<Svar> params = ent.element.params();
|
||||
|
||||
//go through each parameter
|
||||
for(int i = 0; i < params.size; i++){
|
||||
Svar var = params.get(i);
|
||||
|
||||
//first argument is skipped as it is always the player caller
|
||||
if(!ent.where.isServer && i == 0){
|
||||
continue;
|
||||
}
|
||||
|
||||
//special case: method can be called from anywhere to anywhere
|
||||
//thus, only read the player when the CLIENT is receiving data, since the client is the only one who cares about the player anyway
|
||||
boolean writePlayerSkipCheck = ent.where == Loc.both && i == 0;
|
||||
|
||||
if(writePlayerSkipCheck){ //write begin check
|
||||
builder.beginControlFlow("if(mindustry.Vars.net.client())");
|
||||
}
|
||||
|
||||
//full type name of parameter
|
||||
String typeName = var.mirror().toString();
|
||||
//name of parameter
|
||||
String varName = var.name();
|
||||
//capitalized version of type name for reading primitives
|
||||
String pname = typeName.equals("boolean") ? "bool" : typeName.charAt(0) + "";
|
||||
|
||||
//write primitives automatically
|
||||
if(BaseProcessor.isPrimitive(typeName)){
|
||||
builder.addStatement("$L = READ.$L()", varName, pname);
|
||||
}else{
|
||||
//else, try and find a serializer
|
||||
String ser = serializer.readers.get(typeName.replace("mindustry.gen.", ""), SerializerResolver.locate(ent.element.e, var.mirror(), false));
|
||||
|
||||
if(ser == null){ //make sure a serializer exists!
|
||||
BaseProcessor.err("No read method to read class type '" + typeName + "' in method " + ent.targetMethod + "; " + serializer.readers, var);
|
||||
}
|
||||
|
||||
//add statement for reading it
|
||||
builder.addStatement("$L = $L(READ)", varName, ser);
|
||||
}
|
||||
|
||||
if(writePlayerSkipCheck){ //write end check
|
||||
builder.endControlFlow();
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/** Creates a specific variant for a method entry. */
|
||||
private static void writeCallMethod(TypeSpec.Builder classBuilder, MethodEntry ent, boolean toAll, boolean forwarded){
|
||||
Smethod elem = ent.element;
|
||||
Seq<Svar> params = elem.params();
|
||||
|
||||
//create builder
|
||||
MethodSpec.Builder method = MethodSpec.methodBuilder(elem.name() + (forwarded ? "__forward" : "")) //add except suffix when forwarding
|
||||
.addModifiers(Modifier.STATIC)
|
||||
.returns(void.class);
|
||||
|
||||
//forwarded methods aren't intended for use, and are not public
|
||||
if(!forwarded){
|
||||
method.addModifiers(Modifier.PUBLIC);
|
||||
}
|
||||
|
||||
//validate client methods to make sure
|
||||
if(ent.where.isClient){
|
||||
if(params.isEmpty()){
|
||||
BaseProcessor.err("Client invoke methods must have a first parameter of type Player", elem);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!params.get(0).mirror().toString().contains("Player")){
|
||||
BaseProcessor.err("Client invoke methods should have a first parameter of type Player", elem);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//if toAll is false, it's a 'send to one player' variant, so add the player as a parameter
|
||||
if(!toAll){
|
||||
method.addParameter(ClassName.bestGuess("mindustry.net.NetConnection"), "playerConnection");
|
||||
}
|
||||
|
||||
//add sender to ignore
|
||||
if(forwarded){
|
||||
method.addParameter(ClassName.bestGuess("mindustry.net.NetConnection"), "exceptConnection");
|
||||
}
|
||||
|
||||
//call local method if applicable, shouldn't happen when forwarding method as that already happens by default
|
||||
if(!forwarded && ent.local != Loc.none){
|
||||
//add in local checks
|
||||
if(ent.local != Loc.both){
|
||||
method.beginControlFlow("if(" + getCheckString(ent.local) + " || !mindustry.Vars.net.active())");
|
||||
}
|
||||
|
||||
//concatenate parameters
|
||||
int index = 0;
|
||||
StringBuilder results = new StringBuilder();
|
||||
for(Svar var : params){
|
||||
//special case: calling local-only methods uses the local player
|
||||
if(index == 0 && ent.where == Loc.client){
|
||||
results.append("mindustry.Vars.player");
|
||||
}else{
|
||||
results.append(var.name());
|
||||
}
|
||||
if(index != params.size - 1) results.append(", ");
|
||||
index++;
|
||||
}
|
||||
|
||||
//add the statement to call it
|
||||
method.addStatement("$N." + elem.name() + "(" + results + ")",
|
||||
((TypeElement)elem.up()).getQualifiedName().toString());
|
||||
|
||||
if(ent.local != Loc.both){
|
||||
method.endControlFlow();
|
||||
}
|
||||
}
|
||||
|
||||
//start control flow to check if it's actually client/server so no netcode is called
|
||||
method.beginControlFlow("if(" + getCheckString(ent.where) + ")");
|
||||
|
||||
//add statement to create packet from pool
|
||||
method.addStatement("$1T packet = new $1T()", tname("mindustry.gen." + ent.packetClassName));
|
||||
|
||||
method.addTypeVariables(Seq.with(elem.e.getTypeParameters()).map(BaseProcessor::getTVN));
|
||||
|
||||
for(int i = 0; i < params.size; i++){
|
||||
//first argument is skipped as it is always the player caller
|
||||
if((!ent.where.isServer) && i == 0){
|
||||
continue;
|
||||
}
|
||||
|
||||
Svar var = params.get(i);
|
||||
|
||||
method.addParameter(var.tname(), var.name());
|
||||
|
||||
//name of parameter
|
||||
String varName = var.name();
|
||||
//special case: method can be called from anywhere to anywhere
|
||||
//thus, only write the player when the SERVER is writing data, since the client is the only one who reads it
|
||||
boolean writePlayerSkipCheck = ent.where == Loc.both && i == 0;
|
||||
|
||||
if(writePlayerSkipCheck){ //write begin check
|
||||
method.beginControlFlow("if(mindustry.Vars.net.server())");
|
||||
}
|
||||
|
||||
method.addStatement("packet.$L = $L", varName, varName);
|
||||
|
||||
if(writePlayerSkipCheck){ //write end check
|
||||
method.endControlFlow();
|
||||
}
|
||||
}
|
||||
|
||||
String sendString;
|
||||
|
||||
if(forwarded){ //forward packet
|
||||
if(!ent.local.isClient){ //if the client doesn't get it called locally, forward it back after validation
|
||||
sendString = "mindustry.Vars.net.send(";
|
||||
}else{
|
||||
sendString = "mindustry.Vars.net.sendExcept(exceptConnection, ";
|
||||
}
|
||||
}else if(toAll){ //send to all players / to server
|
||||
sendString = "mindustry.Vars.net.send(";
|
||||
}else{ //send to specific client from server
|
||||
sendString = "playerConnection.send(";
|
||||
}
|
||||
|
||||
//send the actual packet
|
||||
method.addStatement(sendString + "packet, " + (!ent.unreliable) + ")");
|
||||
|
||||
|
||||
//end check for server/client
|
||||
method.endControlFlow();
|
||||
|
||||
//add method to class, finally
|
||||
classBuilder.addMethod(method.build());
|
||||
}
|
||||
|
||||
private static String getCheckString(Loc loc){
|
||||
return
|
||||
loc.isClient && loc.isServer ? "mindustry.Vars.net.server() || mindustry.Vars.net.client()" :
|
||||
loc.isClient ? "mindustry.Vars.net.client()" :
|
||||
loc.isServer ? "mindustry.Vars.net.server()" : "false";
|
||||
}
|
||||
|
||||
/** Generates handleServer / handleClient methods. */
|
||||
public static MethodSpec writeHandleMethod(MethodEntry ent, boolean isClient){
|
||||
|
||||
//create main method builder
|
||||
MethodSpec.Builder builder = MethodSpec.methodBuilder(isClient ? "handleClient" : "handleServer")
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(Override.class)
|
||||
.returns(void.class);
|
||||
|
||||
Smethod elem = ent.element;
|
||||
Seq<Svar> params = elem.params();
|
||||
|
||||
if(!isClient){
|
||||
//add player parameter
|
||||
builder.addParameter(ClassName.get("mindustry.net", "NetConnection"), "con");
|
||||
|
||||
//skip if player is invalid
|
||||
builder.beginControlFlow("if(con.player == null || con.kicked)");
|
||||
builder.addStatement("return");
|
||||
builder.endControlFlow();
|
||||
|
||||
//make sure to use the actual player who sent the packet
|
||||
builder.addStatement("mindustry.gen.Player player = con.player");
|
||||
}
|
||||
|
||||
//execute the relevant method before the forward
|
||||
//if it throws a ValidateException, the method won't be forwarded
|
||||
builder.addStatement("$N." + elem.name() + "(" + params.toString(", ", s -> s.name()) + ")", ((TypeElement)elem.up()).getQualifiedName().toString());
|
||||
|
||||
//call forwarded method, don't forward on the client reader
|
||||
if(ent.forward && ent.where.isServer && !isClient){
|
||||
//call forwarded method
|
||||
builder.addStatement("$L.$L.$L__forward(con, $L)", packageName, ent.className, elem.name(), params.toString(", ", s -> s.name()));
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
package mindustry.annotations.remote;
|
||||
|
||||
import mindustry.annotations.Annotations.*;
|
||||
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import mindustry.annotations.util.*;
|
||||
|
||||
/** Class that repesents a remote method to be constructed and put into a class. */
|
||||
public class MethodEntry{
|
||||
@@ -10,6 +9,8 @@ public class MethodEntry{
|
||||
public final String className;
|
||||
/** Fully qualified target method to call. */
|
||||
public final String targetMethod;
|
||||
/** Simple name of the generated packet class. */
|
||||
public final String packetClassName;
|
||||
/** Whether this method can be called on a client/server. */
|
||||
public final Loc where;
|
||||
/**
|
||||
@@ -26,12 +27,13 @@ public class MethodEntry{
|
||||
/** Unique method ID. */
|
||||
public final int id;
|
||||
/** The element method associated with this entry. */
|
||||
public final ExecutableElement element;
|
||||
public final Smethod element;
|
||||
/** The assigned packet priority. Only used in clients. */
|
||||
public final PacketPriority priority;
|
||||
|
||||
public MethodEntry(String className, String targetMethod, Loc where, Variant target,
|
||||
Loc local, boolean unreliable, boolean forward, int id, ExecutableElement element, PacketPriority priority){
|
||||
public MethodEntry(String className, String targetMethod, String packetClassName, Loc where, Variant target,
|
||||
Loc local, boolean unreliable, boolean forward, int id, Smethod element, PacketPriority priority){
|
||||
this.packetClassName = packetClassName;
|
||||
this.className = className;
|
||||
this.forward = forward;
|
||||
this.targetMethod = targetMethod;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package mindustry.annotations.remote;
|
||||
|
||||
import arc.struct.*;
|
||||
import com.squareup.javapoet.*;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.annotations.*;
|
||||
import mindustry.annotations.util.*;
|
||||
@@ -9,7 +9,6 @@ import mindustry.annotations.util.TypeIOResolver.*;
|
||||
|
||||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.element.*;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
/** The annotation processor for generating remote method call code. */
|
||||
@@ -18,106 +17,58 @@ import java.util.*;
|
||||
"mindustry.annotations.Annotations.TypeIOHandler"
|
||||
})
|
||||
public class RemoteProcess extends BaseProcessor{
|
||||
/** Maximum size of each event packet. */
|
||||
public static final int maxPacketSize = 8192;
|
||||
/** Warning on top of each autogenerated file. */
|
||||
public static final String autogenWarning = "Autogenerated file. Do not modify!\n";
|
||||
|
||||
/** Name of class that handles reading and invoking packets on the server. */
|
||||
private static final String readServerName = "RemoteReadServer";
|
||||
/** Name of class that handles reading and invoking packets on the client. */
|
||||
private static final String readClientName = "RemoteReadClient";
|
||||
/** Simple class name of generated class name. */
|
||||
private static final String callLocation = "Call";
|
||||
|
||||
//class serializers
|
||||
private ClassSerializer serializer;
|
||||
//all elements with the Remote annotation
|
||||
private Seq<Smethod> elements;
|
||||
//map of all classes to generate by name
|
||||
private HashMap<String, ClassEntry> classMap;
|
||||
//list of all method entries
|
||||
private Seq<MethodEntry> methods;
|
||||
//list of all method entries
|
||||
private Seq<ClassEntry> classes;
|
||||
|
||||
{
|
||||
rounds = 2;
|
||||
}
|
||||
public static final String callLocation = "Call";
|
||||
|
||||
@Override
|
||||
public void process(RoundEnvironment roundEnv) throws Exception{
|
||||
//round 1: find all annotations, generate *writers*
|
||||
if(round == 1){
|
||||
//get serializers
|
||||
serializer = TypeIOResolver.resolve(this);
|
||||
//last method ID used
|
||||
int lastMethodID = 0;
|
||||
//find all elements with the Remote annotation
|
||||
elements = methods(Remote.class);
|
||||
//map of all classes to generate by name
|
||||
classMap = new HashMap<>();
|
||||
//list of all method entries
|
||||
methods = new Seq<>();
|
||||
//list of all method entries
|
||||
classes = new Seq<>();
|
||||
//get serializers
|
||||
//class serializers
|
||||
ClassSerializer serializer = TypeIOResolver.resolve(this);
|
||||
//last method ID used
|
||||
int lastMethodID = 0;
|
||||
//find all elements with the Remote annotation
|
||||
//all elements with the Remote annotation
|
||||
Seq<Smethod> elements = methods(Remote.class);
|
||||
//list of all method entries
|
||||
Seq<MethodEntry> methods = new Seq<>();
|
||||
|
||||
Seq<Smethod> orderedElements = elements.copy();
|
||||
orderedElements.sortComparing(Object::toString);
|
||||
Seq<Smethod> orderedElements = elements.copy();
|
||||
orderedElements.sortComparing(Selement::toString);
|
||||
|
||||
//create methods
|
||||
for(Smethod element : orderedElements){
|
||||
Remote annotation = element.annotation(Remote.class);
|
||||
//create methods
|
||||
for(Smethod element : orderedElements){
|
||||
Remote annotation = element.annotation(Remote.class);
|
||||
|
||||
//check for static
|
||||
if(!element.is(Modifier.STATIC) || !element.is(Modifier.PUBLIC)){
|
||||
err("All @Remote methods must be public and static: ", element);
|
||||
}
|
||||
|
||||
//can't generate none methods
|
||||
if(annotation.targets() == Loc.none){
|
||||
err("A @Remote method's targets() cannot be equal to 'none':", element);
|
||||
}
|
||||
|
||||
//get and create class entry if needed
|
||||
if(!classMap.containsKey(callLocation)){
|
||||
ClassEntry clas = new ClassEntry(callLocation);
|
||||
classMap.put(callLocation, clas);
|
||||
classes.add(clas);
|
||||
}
|
||||
|
||||
ClassEntry entry = classMap.get(callLocation);
|
||||
|
||||
//create and add entry
|
||||
MethodEntry method = new MethodEntry(entry.name, BaseProcessor.getMethodName(element.e), annotation.targets(), annotation.variants(),
|
||||
annotation.called(), annotation.unreliable(), annotation.forward(), lastMethodID++, element.e, annotation.priority());
|
||||
|
||||
entry.methods.add(method);
|
||||
methods.add(method);
|
||||
//check for static
|
||||
if(!element.is(Modifier.STATIC) || !element.is(Modifier.PUBLIC)){
|
||||
err("All @Remote methods must be public and static", element);
|
||||
}
|
||||
|
||||
//create read/write generators
|
||||
RemoteWriteGenerator writegen = new RemoteWriteGenerator(serializer);
|
||||
//can't generate none methods
|
||||
if(annotation.targets() == Loc.none){
|
||||
err("A @Remote method's targets() cannot be equal to 'none'", element);
|
||||
}
|
||||
|
||||
//generate the methods to invoke (write)
|
||||
writegen.generateFor(classes, packageName);
|
||||
}else if(round == 2){ //round 2: generate all *readers*
|
||||
RemoteReadGenerator readgen = new RemoteReadGenerator(serializer);
|
||||
String packetName = Strings.capitalize(element.name()) + "CallPacket";
|
||||
int[] index = {1};
|
||||
|
||||
//generate server readers
|
||||
readgen.generateFor(methods.select(method -> method.where.isClient), readServerName, packageName, true);
|
||||
//generate client readers
|
||||
readgen.generateFor(methods.select(method -> method.where.isServer), readClientName, packageName, false);
|
||||
while(methods.contains(m -> m.packetClassName.equals(packetName + (index[0] == 1 ? "" : index[0])))){
|
||||
index[0] ++;
|
||||
}
|
||||
|
||||
//create class for storing unique method hash
|
||||
TypeSpec.Builder hashBuilder = TypeSpec.classBuilder("MethodHash").addModifiers(Modifier.PUBLIC);
|
||||
hashBuilder.addJavadoc(autogenWarning);
|
||||
hashBuilder.addField(FieldSpec.builder(int.class, "HASH", Modifier.STATIC, Modifier.PUBLIC, Modifier.FINAL)
|
||||
.initializer("$1L", Arrays.hashCode(methods.map(m -> m.element).toArray())).build());
|
||||
//create and add entry
|
||||
MethodEntry method = new MethodEntry(
|
||||
callLocation, BaseProcessor.getMethodName(element.e), packetName + (index[0] == 1 ? "" : index[0]),
|
||||
annotation.targets(), annotation.variants(),
|
||||
annotation.called(), annotation.unreliable(), annotation.forward(), lastMethodID++,
|
||||
element, annotation.priority()
|
||||
);
|
||||
|
||||
//build and write resulting hash class
|
||||
TypeSpec spec = hashBuilder.build();
|
||||
JavaFile.builder(packageName, spec).build().writeTo(BaseProcessor.filer);
|
||||
methods.add(method);
|
||||
}
|
||||
|
||||
//generate the methods to invoke, as well as the packet classes
|
||||
CallGenerator.generate(serializer, methods);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
package mindustry.annotations.remote;
|
||||
|
||||
import arc.struct.*;
|
||||
import arc.util.io.*;
|
||||
import com.squareup.javapoet.*;
|
||||
import mindustry.annotations.*;
|
||||
import mindustry.annotations.util.TypeIOResolver.*;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
|
||||
/** Generates code for reading remote invoke packets on the client and server. */
|
||||
public class RemoteReadGenerator{
|
||||
private final ClassSerializer serializers;
|
||||
|
||||
/** Creates a read generator that uses the supplied serializer setup. */
|
||||
public RemoteReadGenerator(ClassSerializer serializers){
|
||||
this.serializers = serializers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a class for reading remote invoke packets.
|
||||
* @param entries List of methods to use.
|
||||
* @param className Simple target class name.
|
||||
* @param packageName Full target package name.
|
||||
* @param needsPlayer Whether this read method requires a reference to the player sender.
|
||||
*/
|
||||
public void generateFor(Seq<MethodEntry> entries, String className, String packageName, boolean needsPlayer) throws Exception{
|
||||
|
||||
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className).addModifiers(Modifier.PUBLIC);
|
||||
classBuilder.addJavadoc(RemoteProcess.autogenWarning);
|
||||
|
||||
//create main method builder
|
||||
MethodSpec.Builder readMethod = MethodSpec.methodBuilder("readPacket")
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(Reads.class, "read") //buffer to read form
|
||||
.addParameter(int.class, "id") //ID of method type to read
|
||||
.returns(void.class);
|
||||
|
||||
if(needsPlayer){
|
||||
//add player parameter
|
||||
readMethod.addParameter(ClassName.get(packageName, "Player"), "player");
|
||||
}
|
||||
|
||||
CodeBlock.Builder readBlock = CodeBlock.builder(); //start building block of code inside read method
|
||||
boolean started = false; //whether an if() statement has been written yet
|
||||
|
||||
for(MethodEntry entry : entries){
|
||||
//write if check for this entry ID
|
||||
if(!started){
|
||||
started = true;
|
||||
readBlock.beginControlFlow("if(id == " + entry.id + ")");
|
||||
}else{
|
||||
readBlock.nextControlFlow("else if(id == " + entry.id + ")");
|
||||
}
|
||||
|
||||
readBlock.beginControlFlow("try");
|
||||
|
||||
//concatenated list of variable names for method invocation
|
||||
StringBuilder varResult = new StringBuilder();
|
||||
|
||||
//go through each parameter
|
||||
for(int i = 0; i < entry.element.getParameters().size(); i++){
|
||||
VariableElement var = entry.element.getParameters().get(i);
|
||||
|
||||
if(!needsPlayer || i != 0){ //if client, skip first parameter since it's always of type player and doesn't need to be read
|
||||
//full type name of parameter
|
||||
String typeName = var.asType().toString();
|
||||
//name of parameter
|
||||
String varName = var.getSimpleName().toString();
|
||||
//captialized version of type name for reading primitives
|
||||
String pname = typeName.equals("boolean") ? "bool" : typeName.charAt(0) + "";
|
||||
|
||||
//write primitives automatically
|
||||
if(BaseProcessor.isPrimitive(typeName)){
|
||||
readBlock.addStatement("$L $L = read.$L()", typeName, varName, pname);
|
||||
}else{
|
||||
//else, try and find a serializer
|
||||
String ser = serializers.readers.get(typeName.replace("mindustry.gen.", ""), SerializerResolver.locate(entry.element, var.asType(), false));
|
||||
|
||||
if(ser == null){ //make sure a serializer exists!
|
||||
BaseProcessor.err("No read method to read class type '" + typeName + "' in method " + entry.targetMethod + "; " + serializers.readers, var);
|
||||
return;
|
||||
}
|
||||
|
||||
//add statement for reading it
|
||||
readBlock.addStatement(typeName + " " + varName + " = " + ser + "(read)");
|
||||
}
|
||||
|
||||
//append variable name to string builder
|
||||
varResult.append(var.getSimpleName());
|
||||
if(i != entry.element.getParameters().size() - 1) varResult.append(", ");
|
||||
}else{
|
||||
varResult.append("player");
|
||||
if(i != entry.element.getParameters().size() - 1) varResult.append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
//execute the relevant method before the forward
|
||||
//if it throws a ValidateException, the method won't be forwarded
|
||||
readBlock.addStatement("$N." + entry.element.getSimpleName() + "(" + varResult.toString() + ")", ((TypeElement)entry.element.getEnclosingElement()).getQualifiedName().toString());
|
||||
|
||||
//call forwarded method, don't forward on the client reader
|
||||
if(entry.forward && entry.where.isServer && needsPlayer){
|
||||
//call forwarded method
|
||||
readBlock.addStatement(packageName + "." + entry.className + "." + entry.element.getSimpleName() +
|
||||
"__forward(player.con" + (varResult.length() == 0 ? "" : ", ") + varResult.toString() + ")");
|
||||
}
|
||||
|
||||
readBlock.nextControlFlow("catch (java.lang.Exception e)");
|
||||
readBlock.addStatement("throw new java.lang.RuntimeException(\"Failed to to read remote method '" + entry.element.getSimpleName() + "'!\", e)");
|
||||
readBlock.endControlFlow();
|
||||
}
|
||||
|
||||
//end control flow if necessary
|
||||
if(started){
|
||||
readBlock.nextControlFlow("else");
|
||||
readBlock.addStatement("throw new $1N(\"Invalid read method ID: \" + id + \"\")", RuntimeException.class.getName()); //handle invalid method IDs
|
||||
readBlock.endControlFlow();
|
||||
}
|
||||
|
||||
//add block and method to class
|
||||
readMethod.addCode(readBlock.build());
|
||||
classBuilder.addMethod(readMethod.build());
|
||||
|
||||
//build and write resulting class
|
||||
TypeSpec spec = classBuilder.build();
|
||||
JavaFile.builder(packageName, spec).build().writeTo(BaseProcessor.filer);
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package mindustry.annotations.util;
|
||||
|
||||
import arc.struct.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import com.squareup.javapoet.*;
|
||||
import com.sun.tools.javac.code.Attribute.*;
|
||||
import mindustry.annotations.*;
|
||||
@@ -19,7 +19,8 @@ public class Selement<T extends Element>{
|
||||
this.e = e;
|
||||
}
|
||||
|
||||
public @Nullable String doc(){
|
||||
@Nullable
|
||||
public String doc(){
|
||||
return BaseProcessor.elementu.getDocComment(e);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package mindustry.annotations.util;
|
||||
|
||||
import com.sun.source.tree.*;
|
||||
import com.sun.tools.javac.tree.JCTree.*;
|
||||
import mindustry.annotations.*;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
@@ -16,10 +15,6 @@ public class Svar extends Selement<VariableElement>{
|
||||
return up().asType().toString() + "#" + super.toString().replace("mindustry.gen.", "");
|
||||
}
|
||||
|
||||
public JCVariableDecl jtree(){
|
||||
return (JCVariableDecl)BaseProcessor.elementu.getTree(e);
|
||||
}
|
||||
|
||||
public Stype enclosingType(){
|
||||
return new Stype((TypeElement)up());
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
#Maps entity names to IDs. Autogenerated.
|
||||
|
||||
alpha=0
|
||||
arkyid=29
|
||||
atrax=1
|
||||
beta=30
|
||||
block=2
|
||||
corvus=24
|
||||
flare=3
|
||||
gamma=31
|
||||
mace=4
|
||||
mega=5
|
||||
mindustry.entities.comp.BuildingComp=6
|
||||
@@ -14,9 +17,12 @@ mindustry.entities.comp.EffectStateComp=9
|
||||
mindustry.entities.comp.FireComp=10
|
||||
mindustry.entities.comp.LaunchCoreComp=11
|
||||
mindustry.entities.comp.PlayerComp=12
|
||||
mindustry.entities.comp.PosTeam=27
|
||||
mindustry.entities.comp.PosTeamDef=28
|
||||
mindustry.entities.comp.PuddleComp=13
|
||||
mindustry.type.Weather.WeatherStateComp=14
|
||||
mindustry.world.blocks.campaign.LaunchPad.LaunchPayloadComp=15
|
||||
mindustry.world.blocks.campaign.PayloadLaunchPad.LargeLaunchPayloadComp=34
|
||||
mindustry.world.blocks.defense.ForceProjector.ForceDrawComp=22
|
||||
mono=16
|
||||
nova=17
|
||||
@@ -24,6 +30,8 @@ oct=26
|
||||
poly=18
|
||||
pulsar=19
|
||||
quad=23
|
||||
quasar=32
|
||||
risso=20
|
||||
spiroct=21
|
||||
toxopid=33
|
||||
vela=25
|
||||
@@ -1 +0,0 @@
|
||||
{fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:health,type:float},{name:isShooting,type:boolean},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:payloads,type:arc.struct.Seq<mindustry.world.blocks.payloads.Payload>},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}
|
||||
@@ -1 +0,0 @@
|
||||
{fields:[{name:ammo,type:float,size:4},{name:armor,type:float,size:4},{name:controller,type:mindustry.entities.units.UnitController,size:-1},{name:deactivated,type:boolean,size:1},{name:elevation,type:float,size:4},{name:health,type:float,size:4},{name:isShooting,type:boolean,size:1},{name:mounts,type:"mindustry.entities.units.WeaponMount[]",size:-1},{name:rotation,type:float,size:4},{name:shield,type:float,size:4},{name:spawnedByCore,type:boolean,size:1},{name:stack,type:mindustry.type.ItemStack,size:-1},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>,size:-1},{name:team,type:mindustry.game.Team,size:-1},{name:type,type:mindustry.type.UnitType,size:-1},{name:x,type:float,size:4},{name:y,type:float,size:4}]}
|
||||
@@ -1 +0,0 @@
|
||||
{version:1,fields:[{name:ammo,type:float,size:4},{name:armor,type:float,size:4},{name:controller,type:mindustry.entities.units.UnitController,size:-1},{name:elevation,type:float,size:4},{name:health,type:float,size:4},{name:isShooting,type:boolean,size:1},{name:mounts,type:"mindustry.entities.units.WeaponMount[]",size:-1},{name:rotation,type:float,size:4},{name:shield,type:float,size:4},{name:spawnedByCore,type:boolean,size:1},{name:stack,type:mindustry.type.ItemStack,size:-1},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>,size:-1},{name:team,type:mindustry.game.Team,size:-1},{name:type,type:mindustry.type.UnitType,size:-1},{name:x,type:float,size:4},{name:y,type:float,size:4}]}
|
||||
@@ -1 +0,0 @@
|
||||
{fields:[{name:ammo,type:float,size:4},{name:armor,type:float,size:4},{name:baseRotation,type:float,size:4},{name:controller,type:mindustry.entities.units.UnitController,size:-1},{name:deactivated,type:boolean,size:1},{name:elevation,type:float,size:4},{name:health,type:float,size:4},{name:isShooting,type:boolean,size:1},{name:mounts,type:"mindustry.entities.units.WeaponMount[]",size:-1},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>,size:-1},{name:rotation,type:float,size:4},{name:shield,type:float,size:4},{name:spawnedByCore,type:boolean,size:1},{name:stack,type:mindustry.type.ItemStack,size:-1},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>,size:-1},{name:team,type:mindustry.game.Team,size:-1},{name:type,type:mindustry.type.UnitType,size:-1},{name:x,type:float,size:4},{name:y,type:float,size:4}]}
|
||||
@@ -1 +0,0 @@
|
||||
{fields:[{name:ammo,type:float,size:4},{name:armor,type:float,size:4},{name:controller,type:mindustry.entities.units.UnitController,size:-1},{name:deactivated,type:boolean,size:1},{name:elevation,type:float,size:4},{name:health,type:float,size:4},{name:isShooting,type:boolean,size:1},{name:mineTile,type:mindustry.world.Tile,size:-1},{name:mounts,type:"mindustry.entities.units.WeaponMount[]",size:-1},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>,size:-1},{name:rotation,type:float,size:4},{name:shield,type:float,size:4},{name:spawnedByCore,type:boolean,size:1},{name:stack,type:mindustry.type.ItemStack,size:-1},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>,size:-1},{name:team,type:mindustry.game.Team,size:-1},{name:type,type:mindustry.type.UnitType,size:-1},{name:x,type:float,size:4},{name:y,type:float,size:4}]}
|
||||
@@ -1 +0,0 @@
|
||||
{fields:[{name:ammo,type:float,size:4},{name:armor,type:float,size:4},{name:baseRotation,type:float,size:4},{name:controller,type:mindustry.entities.units.UnitController,size:-1},{name:deactivated,type:boolean,size:1},{name:elevation,type:float,size:4},{name:health,type:float,size:4},{name:isShooting,type:boolean,size:1},{name:mounts,type:"mindustry.entities.units.WeaponMount[]",size:-1},{name:rotation,type:float,size:4},{name:shield,type:float,size:4},{name:spawnedByCore,type:boolean,size:1},{name:stack,type:mindustry.type.ItemStack,size:-1},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>,size:-1},{name:team,type:mindustry.game.Team,size:-1},{name:type,type:mindustry.type.UnitType,size:-1},{name:x,type:float,size:4},{name:y,type:float,size:4}]}
|
||||
@@ -1 +0,0 @@
|
||||
{version:1,fields:[{name:ammo,type:float,size:4},{name:armor,type:float,size:4},{name:baseRotation,type:float,size:4},{name:controller,type:mindustry.entities.units.UnitController,size:-1},{name:elevation,type:float,size:4},{name:health,type:float,size:4},{name:isShooting,type:boolean,size:1},{name:mounts,type:"mindustry.entities.units.WeaponMount[]",size:-1},{name:rotation,type:float,size:4},{name:shield,type:float,size:4},{name:spawnedByCore,type:boolean,size:1},{name:stack,type:mindustry.type.ItemStack,size:-1},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>,size:-1},{name:team,type:mindustry.game.Team,size:-1},{name:type,type:mindustry.type.UnitType,size:-1},{name:x,type:float,size:4},{name:y,type:float,size:4}]}
|
||||
@@ -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}]}
|
||||
@@ -0,0 +1 @@
|
||||
{fields:[{name:team,type:mindustry.game.Team},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/alpha/0.json
Normal file
1
annotations/src/main/resources/revisions/alpha/0.json
Normal file
@@ -0,0 +1 @@
|
||||
{fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/alpha/1.json
Normal file
1
annotations/src/main/resources/revisions/alpha/1.json
Normal file
@@ -0,0 +1 @@
|
||||
{version:1,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/arkyid/0.json
Normal file
1
annotations/src/main/resources/revisions/arkyid/0.json
Normal file
@@ -0,0 +1 @@
|
||||
{fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/arkyid/1.json
Normal file
1
annotations/src/main/resources/revisions/arkyid/1.json
Normal file
@@ -0,0 +1 @@
|
||||
{version:1,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/beta/0.json
Normal file
1
annotations/src/main/resources/revisions/beta/0.json
Normal file
@@ -0,0 +1 @@
|
||||
{fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/beta/1.json
Normal file
1
annotations/src/main/resources/revisions/beta/1.json
Normal file
@@ -0,0 +1 @@
|
||||
{version:1,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/block/2.json
Normal file
1
annotations/src/main/resources/revisions/block/2.json
Normal file
@@ -0,0 +1 @@
|
||||
{version:2,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/block/3.json
Normal file
1
annotations/src/main/resources/revisions/block/3.json
Normal file
@@ -0,0 +1 @@
|
||||
{version:3,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/block/4.json
Normal file
1
annotations/src/main/resources/revisions/block/4.json
Normal file
@@ -0,0 +1 @@
|
||||
{version:4,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/block/5.json
Normal file
1
annotations/src/main/resources/revisions/block/5.json
Normal file
@@ -0,0 +1 @@
|
||||
{version:5,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/corvus/2.json
Normal file
1
annotations/src/main/resources/revisions/corvus/2.json
Normal file
@@ -0,0 +1 @@
|
||||
{version:2,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/corvus/3.json
Normal file
1
annotations/src/main/resources/revisions/corvus/3.json
Normal file
@@ -0,0 +1 @@
|
||||
{version:3,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/corvus/4.json
Normal file
1
annotations/src/main/resources/revisions/corvus/4.json
Normal file
@@ -0,0 +1 @@
|
||||
{version:4,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/corvus/5.json
Normal file
1
annotations/src/main/resources/revisions/corvus/5.json
Normal file
@@ -0,0 +1 @@
|
||||
{version:5,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/flare/2.json
Normal file
1
annotations/src/main/resources/revisions/flare/2.json
Normal file
@@ -0,0 +1 @@
|
||||
{version:2,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/flare/3.json
Normal file
1
annotations/src/main/resources/revisions/flare/3.json
Normal file
@@ -0,0 +1 @@
|
||||
{version:3,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/flare/4.json
Normal file
1
annotations/src/main/resources/revisions/flare/4.json
Normal file
@@ -0,0 +1 @@
|
||||
{version:4,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/flare/5.json
Normal file
1
annotations/src/main/resources/revisions/flare/5.json
Normal file
@@ -0,0 +1 @@
|
||||
{version:5,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/gamma/0.json
Normal file
1
annotations/src/main/resources/revisions/gamma/0.json
Normal file
@@ -0,0 +1 @@
|
||||
{fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/gamma/1.json
Normal file
1
annotations/src/main/resources/revisions/gamma/1.json
Normal file
@@ -0,0 +1 @@
|
||||
{version:1,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/mace/2.json
Normal file
1
annotations/src/main/resources/revisions/mace/2.json
Normal file
@@ -0,0 +1 @@
|
||||
{version:2,fields:[{name:ammo,type:float},{name:armor,type:float},{name:baseRotation,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/mace/3.json
Normal file
1
annotations/src/main/resources/revisions/mace/3.json
Normal file
@@ -0,0 +1 @@
|
||||
{version:3,fields:[{name:ammo,type:float},{name:armor,type:float},{name:baseRotation,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/mace/4.json
Normal file
1
annotations/src/main/resources/revisions/mace/4.json
Normal file
@@ -0,0 +1 @@
|
||||
{version:4,fields:[{name:ammo,type:float},{name:armor,type:float},{name:baseRotation,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/mace/5.json
Normal file
1
annotations/src/main/resources/revisions/mace/5.json
Normal file
@@ -0,0 +1 @@
|
||||
{version:5,fields:[{name:ammo,type:float},{name:armor,type:float},{name:baseRotation,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/mega/2.json
Normal file
1
annotations/src/main/resources/revisions/mega/2.json
Normal file
@@ -0,0 +1 @@
|
||||
{version:2,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:payloads,type:arc.struct.Seq<mindustry.world.blocks.payloads.Payload>},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:x,type:float},{name:y,type:float}]}
|
||||
1
annotations/src/main/resources/revisions/mega/3.json
Normal file
1
annotations/src/main/resources/revisions/mega/3.json
Normal file
@@ -0,0 +1 @@
|
||||
{version:3,fields:[{name:ammo,type:float},{name:armor,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:payloads,type:arc.struct.Seq<mindustry.world.blocks.payloads.Payload>},{name:plans,type:arc.struct.Queue<mindustry.entities.units.BuildPlan>},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq<mindustry.entities.units.StatusEntry>},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:x,type:float},{name:y,type:float}]}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user