├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── config.yml └── workflows │ └── ci.yml ├── .gitignore ├── README.md ├── assets ├── bundles │ ├── bundle.properties │ ├── bundle_Ko.properties │ ├── bundle_de.properties │ ├── bundle_ru.properties │ └── bundle_uk_UA.properties ├── icon.png └── sprites │ ├── frog.png │ ├── icons │ ├── clone.png │ ├── core.png │ ├── dump.png │ ├── fullSword.png │ ├── heal.png │ ├── invincibility.png │ ├── light-off.png │ ├── light-on.png │ ├── musics.png │ ├── sandbox.png │ ├── seppuku.png │ ├── sounds.png │ ├── stop.png │ ├── survival.png │ └── weather.png │ ├── settings-banner.png │ ├── settings-icon.png │ └── ui │ ├── button-center-disabled.9.png │ ├── button-center-down.9.png │ ├── button-center-over.9.png │ ├── button-center.9.png │ ├── button-left-down.9.png │ ├── button-left-over.9.png │ ├── button-left.9.png │ ├── button-right-down.9.png │ ├── button-right-over.9.png │ ├── button-right.9.png │ └── pane-bottom.9.png ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── licence ├── mod.hjson ├── settings.gradle └── src └── testing ├── TestUtils.java ├── buttons ├── Console.java ├── Death.java ├── Effect.java ├── Environment.java ├── Health.java ├── LightSwitch.java ├── Sandbox.java ├── Spawn.java └── TeamChanger.java ├── content └── TUFx.java ├── dialogs ├── BlockDialog.java ├── InterpDialog.java ├── InterpGraph.java ├── StatusDialog.java ├── TUBaseDialog.java ├── TeamDialog.java ├── UnitDialog.java ├── WaveChangeDialog.java ├── sound │ ├── FilterTable.java │ ├── LoadedSounds.java │ ├── MusicsTable.java │ ├── STable.java │ ├── SoundDialog.java │ ├── SoundsTable.java │ └── TUFilters.java └── world │ ├── PlanetTable.java │ ├── WeatherTable.java │ └── WorldDialog.java ├── editor ├── PaintOp.java ├── PaintOperation.java ├── PaintOperationStack.java ├── PaintedTileData.java ├── PainterTool.java ├── TerrainPaintbrush.java └── TerrainPainter.java ├── ui ├── TUDialogs.java ├── TUStyles.java └── TerrainPainterFragment.java └── util ├── Setup.java ├── TUIcons.java ├── TUSettings.java ├── TUVars.java └── Utils.java /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Create a report to help fix an issue. 3 | labels: ["bug"] 4 | 5 | body: 6 | - type: dropdown 7 | id: platform 8 | attributes: 9 | label: Platforms 10 | description: What platform were you using when the bug occurred? 11 | multiple: false 12 | options: 13 | - Android 14 | - Mac 15 | - Windows 16 | - Linux 17 | validations: 18 | required: true 19 | - type: input 20 | id: version 21 | attributes: 22 | label: Mod Version 23 | description: What version of the mod are you using? 24 | placeholder: LATEST IS NOT A VERSION. 25 | validations: 26 | required: true 27 | - type: textarea 28 | id: issue 29 | attributes: 30 | label: Issue 31 | description: Explain your issue in detail. 32 | validations: 33 | required: true 34 | - type: textarea 35 | id: reproduction 36 | attributes: 37 | label: Steps to reproduce 38 | description: How you happened across the issue, and what exactly you did to make the bug happen. 39 | validations: 40 | required: true 41 | - type: textarea 42 | id: logs 43 | attributes: 44 | label: (Crash) logs 45 | description: Either crash reports from the crash folder, or the file you get when you go into Settings -> Game Data -> Export Crash logs. 46 | placeholder: REQUIRED if you are reporting a crash. 47 | validations: 48 | required: false 49 | - type: checkboxes 50 | id: agreements 51 | attributes: 52 | label: Submission 53 | description: Check the boxes to confirm that you have read the lines below. 54 | options: 55 | - label: I have updated to the latest release (https://github.com/MEEPofFaith/testing-utilities-java/releases) to make sure my issue has not been fixed. 56 | required: true 57 | - label: I have searched the closed and open issues to make sure that this problem has not already been reported. 58 | required: true 59 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | buildJar: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: Set up PATH 11 | run: | 12 | echo "${ANDROID_HOME}/build-tools/35.0.0" >> $GITHUB_PATH 13 | - name: Set up JDK 17 14 | uses: actions/setup-java@v1 15 | with: 16 | java-version: 17 17 | - name: Build mod jar 18 | run: | 19 | chmod +x ./gradlew 20 | ./gradlew deploy 21 | - name: Upload built jar file 22 | uses: actions/upload-artifact@v4 23 | with: 24 | name: ${{ github.event.repository.name }} 25 | path: build/libs/${{ github.event.repository.name }}.jar -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs/ 2 | /core/assets/mindustry-saves/ 3 | /core/assets/mindustry-maps/ 4 | /core/assets/bundles/output/ 5 | /core/assets/.gifimages/ 6 | /deploy/ 7 | /desktop/packr-out/ 8 | /desktop/packr-export/ 9 | /desktop/mindustry-saves/ 10 | /desktop/mindustry-maps/ 11 | /desktop/gifexport/ 12 | /core/lib/ 13 | /ios/assets/ 14 | /core/assets-raw/sprites/generated/ 15 | /core/assets-raw/sprites_out/ 16 | /annotations/build/ 17 | /annotations/out/ 18 | /net/build/ 19 | /tools/build/ 20 | /tests/build/ 21 | /server/build/ 22 | /test_files/ 23 | /annotations/build/ 24 | /desktop-sdl/build/ 25 | desktop-sdl/build/ 26 | /android/assets/mindustry-maps/ 27 | /android/assets/mindustry-saves/ 28 | /core/assets/gifexport/ 29 | /core/assets/version.properties 30 | /core/assets/locales 31 | /ios/src/io/anuke/mindustry/gen/ 32 | /core/src/io/anuke/mindustry/gen/ 33 | ios/robovm.properties 34 | packr-out/ 35 | config/ 36 | *.gif 37 | 38 | version.properties 39 | 40 | .attach_* 41 | ## Java 42 | 43 | *.class 44 | *.war 45 | *.ear 46 | hs_err_pid* 47 | crash-report-* 48 | replay_pid* 49 | 50 | ## Robovm 51 | /ios/robovm-build/ 52 | 53 | ## GWT 54 | /html/war/ 55 | /html/gwt-unitCache/ 56 | .apt_generated/ 57 | .gwt/ 58 | gwt-unitCache/ 59 | www-test/ 60 | .gwt-tmp/ 61 | 62 | ## Android Studio and Intellij and Android in general 63 | /android/libs/armeabi/ 64 | /android/libs/armeabi-v7a/ 65 | /android/libs/arm64-v8a/ 66 | /android/libs/x86/ 67 | /android/libs/x86_64/ 68 | /android/gen/ 69 | .idea/ 70 | *.ipr 71 | *.iws 72 | *.iml 73 | /android/out/ 74 | com_crashlytics_export_strings.xml 75 | 76 | ## Eclipse 77 | 78 | .classpath 79 | .project 80 | .metadata/ 81 | /android/bin/ 82 | /core/bin/ 83 | /desktop/bin/ 84 | /html/bin/ 85 | /ios/bin/ 86 | /ios-moe/bin/ 87 | *.tmp 88 | *.bak 89 | *.swp 90 | *~.nib 91 | .settings/ 92 | .loadpath 93 | .externalToolBuilders/ 94 | *.launch 95 | 96 | ## NetBeans 97 | 98 | /nbproject/private/ 99 | /android/nbproject/private/ 100 | /core/nbproject/private/ 101 | /desktop/nbproject/private/ 102 | /html/nbproject/private/ 103 | /ios/nbproject/private/ 104 | /ios-moe/nbproject/private/ 105 | 106 | /build/ 107 | /android/build/ 108 | /core/build/ 109 | /desktop/build/ 110 | /html/build/ 111 | /ios/build/ 112 | /ios-moe/build/ 113 | 114 | /nbbuild/ 115 | /android/nbbuild/ 116 | /core/nbbuild/ 117 | /desktop/nbbuild/ 118 | /html/nbbuild/ 119 | /ios/nbbuild/ 120 | /ios-moe/nbbuild/ 121 | 122 | /dist/ 123 | /android/dist/ 124 | /core/dist/ 125 | /desktop/dist/ 126 | /html/dist/ 127 | /ios/dist/ 128 | /ios-moe/dist/ 129 | 130 | /nbdist/ 131 | /android/nbdist/ 132 | /core/nbdist/ 133 | /desktop/nbdist/ 134 | /html/nbdist/ 135 | /ios/nbdist/ 136 | /ios-moe/nbdist/ 137 | 138 | nbactions.xml 139 | nb-configuration.xml 140 | 141 | ## Gradle 142 | 143 | /local.properties 144 | .gradle/ 145 | gradle-app.setting 146 | /build/ 147 | /android/build/ 148 | /core/build/ 149 | /desktop/build/ 150 | /html/build/ 151 | /ios/build/ 152 | /ios-moe/build/ 153 | 154 | ## OS Specific 155 | .DS_Store 156 | Thumbs.db 157 | android/libs/ 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](assets/sprites/settings-banner.png) 2 | 3 | [![Commit Compiling](https://github.com/MEEPofFaith/testing-utilities-java/workflows/Commit%20Testing/badge.svg)](https://github.com/MEEPofFaith/testing-utilities-java/actions/workflows/commitTest.yml) 4 | [![Discord](https://img.shields.io/discord/704355237246402721.svg?logo=discord&logoColor=white&logoWidth=20&labelColor=ffd37f&label=Mindustry)](https://discord.com/invite/mindustry) 5 | [![Discord](https://img.shields.io/discord/704355237246402721.svg?logo=discord&logoColor=white&logoWidth=20&labelColor=181818&label=de_)](https://discord.gg/VF8Vsc6mmN) 6 | [![Discord](https://img.shields.io/discord/704355237246402721.svg?logo=discord&logoColor=white&logoWidth=20&labelColor=8a1a1a&label=Avant)](https://discord.gg/V6ygvgGVqE) 7 | [![Stars](https://img.shields.io/github/stars/MEEPofFaith/testing-utilities-java?label=Star%20the%20mod%20here%21&style=social)]() 8 | 9 | Several useful utilities for testing stuff in sandbox: 10 | 11 | - Team Changer - Change teams easilty. 12 | - Self Destruct - Kill yourself. Press and hold to repeat. (Enabled in campaign) 13 | - Clone - Clones the unit you're controlling. Press and hold to repeat. 14 | - Heal/Invincibility - Heal yourself, or make yourself invincible. 15 | - Sandbox/Survival Button - Toggles infinite resources. 16 | - Fill/Dump Core - Fill or empty your core of all items. Press and hold to swap between filling and emptying. 17 | - Status Menu - Apply/clear status effects to yourself. 18 | - Environment Menu - Change the environment and start/stop weather. 19 | - Light Switch - Toggles ambient lighting. Press and hold to change color. 20 | - Unit Menu - Spawn units or transform into a different unit. 21 | - Wave selector - Change the current wave and send multiple at once. 22 | - Block Menu - Place any block anywhere. 23 | - Terrain Painter - Draw terrain just like in the editor. 24 | - [sk7725/whynotteleport](https://github.com/sk7725/WhyNotTeleport): Alt + Click to teleport to your cursor. (Sorry if your on mobile.) 25 | - Enables console and increases zoom range. 26 | 27 | Other non-in-game features: 28 | 29 | - Interp Visualizer - Visualzes the various interpolation curves in Interp. 30 | - Sound Room - Play sounds and music. 31 | 32 | Original repo here: [MEEPofFaith/testing-utilities-js](https://github.com/MEEPofFaith/testing-utilities-js). 33 | 34 | ## Building for Desktop Testing 35 | 36 | 1. Install JDK **16**. 37 | 2. Run `gradlew jar` [1]. 38 | 3. Your mod jar will be in the `build/libs` directory. **Only use this version for testing on desktop. It will not work with Android.** 39 | To build an Android-compatible version, you need the Android SDK. You can either let Github Actions handle this, or set it up yourself. See steps below. 40 | 41 | ## Building through Github Actions 42 | 43 | This repository is set up with Github Actions CI to automatically build the mod for you every commit. This requires a Github repository, for obvious reasons. 44 | To get a jar file that works for every platform, do the following: 45 | 1. Make a Github repository with your mod name, and upload the contents of this repo to it. Perform any modifications necessary, then commit and push. 46 | 2. Check the "Actions" tab on your repository page. Select the most recent commit in the list. If it completed successfully, there should be a download link under the "Artifacts" section. 47 | 3. Click the download link (should be the name of your repo). This will download a **zipped jar** - **not** the jar file itself [2]! Unzip this file and import the jar contained within in Mindustry. This version should work both on Android and Desktop. 48 | 49 | ## Building Locally 50 | 51 | Building locally takes more time to set up, but shouldn't be a problem if you've done Android development before. 52 | 1. Download the Android SDK, unzip it and set the `ANDROID_HOME` environment variable to its location. 53 | 2. Make sure you have API level 30 installed, as well as any recent version of build tools (e.g. 30.0.1) 54 | 3. Add a build-tools folder to your PATH. For testing, if you have `30.0.1` installed, that would be `$ANDROID_HOME/build-tools/30.0.1`. 55 | 4. Run `gradlew deploy`. If you did everything correctlly, this will create a jar file in the `build/libs` directory that can be run on both Android and desktop. 56 | 57 | ## Adding Dependencies 58 | 59 | Please note that all dependencies on Mindustry, Arc or its submodules **must be declared as compileOnly in Gradle**. Never use `implementation` for core Mindustry or Arc dependencies. 60 | 61 | - `implementation` **places the entire dependency in the jar**, which is, in most mod dependencies, very undesirable. You do not want the entirety of the Mindustry API included with your mod. 62 | - `compileOnly` means that the dependency is only around at compile time, and not included in the jar. 63 | 64 | Only use `implementation` if you want to package another Java library *with your mod*, and that library is not present in Mindustry already. 65 | 66 | --- 67 | 68 | *[1]* *On Linux/Mac it's `./gradlew`, but if you're using Linux I assume you know how to run executables properly anyway.* 69 | *[2]: Yes, I know this is stupid. It's a Github UI limitation - while the jar itself is uploaded unzipped, there is currently no way to download it as a single file.* 70 | -------------------------------------------------------------------------------- /assets/bundles/bundle_Ko.properties: -------------------------------------------------------------------------------- 1 | # suppress inspection "UnusedProperty" for whole file 2 | 3 | tu-ui-button.death = 할복 4 | tu-ui-button.clone = 복제 5 | tu-ui-button.core = 코어 6 | tu-ui-button.fill = 채움 7 | tu-ui-button.dump = 버림 8 | tu-ui-button.heal = 회복 9 | tu-ui-button.invincible = 무적 10 | 11 | tu-status-menu.name = 상태이상 효과 메뉴 12 | tu-status-menu.permaeff = [accent] (영구적인 효과) 13 | tu-status-menu.duration = 지속시간 (초): 14 | tu-status-menu.apply = 효과 적용 15 | tu-status-menu.perma = 영구 적용 16 | tu-status-menu.clear = 효과 제거 17 | 18 | tu-weather-menu.name = 날씨 메뉴 19 | tu-weather-menu.intensity = 세기 (%): 20 | tu-weather-menu.create = 날씨 시작 21 | tu-weather-menu.remove = 날씨 멈춤 22 | tu-weather-menu.clear = 모든 날씨 멈춤 23 | 24 | tu-unit-menu.name = 기체 메뉴 25 | tu-unit-menu.amount = 수량: 26 | tu-unit-menu.radius = 소환 반지름: 27 | tu-unit-menu.team = 팀 설정 28 | tu-unit-menu.set-team = 팀 설정 (선택됨: {0}) 29 | tu-unit-menu.teams-id = 팀 ID 30 | tu-unit-menu.teams-main = 주요 팀 31 | tu-unit-menu.teams-other = 다른 팀 32 | tu-unit-menu.pos = 위치: ({0}, {1}) 33 | tu-unit-menu.set-pos = [accent]위치 지정완료! ({0}, {1}) 34 | tu-unit-menu.cancel = [red]위치 지정 취소됨: 유효한 공간을 클릭하지 않았습니다. 35 | tu-unit-menu.spawn = 기체 소환 36 | tu-unit-menu.spawn-plural = 기체 소환 37 | tu-unit-menu.transform = 변환 38 | tu-unit-menu.despawns = 소멸 39 | tu-unit-menu.waves = 단계 설정 40 | tu-unit-menu.waves-menu = 단계 설정 (맵: {0}) 41 | tu-unit-menu.wave-range = 설정할 단계 목록 범위: 42 | tu-unit-menu.wave-start = 시작할 단계: 43 | tu-unit-menu.wave-waves = 단계의 양: 44 | tu-unit-menu.wave-current = 현재 단계: {0} 45 | tu-unit-menu.wave-total = 총: {0} 기 46 | tu-unit-menu.wave-send = 단계 지정 47 | 48 | tu-block-menu.name = 블록 메뉴 49 | tu-block-menu.place = 블록 배치 50 | tu-block-menu.delete = 블록 제거 51 | 52 | tu-menu.selection = [accent]현재 선택: 53 | 54 | tu-tooltip.button-team = 현재 소속된 팀을 변경합니다.\n길게 눌러 메뉴 열기. 55 | tu-tooltip.button-clone = 자신의 복제품을 창조합니다.\n길게 눌러 도배하기. 56 | tu-tooltip.button-seppuku = 할복합니다, [red]당장[]!\n길게 눌러 도배하기. 57 | tu-tooltip.button-sandbox = 샌드박스 활성화/비활성화. 58 | tu-tooltip.button-status = 상태이상 효과 메뉴 열기. 59 | tu-tooltip.button-weather = 날씨 메뉴 열기. 60 | tu-tooltip.button-units = 기체 메뉴 열기. 61 | tu-tooltip.button-block = 블록 메뉴 열기. 62 | tu-tooltip.button-fill = 코어를 완전히 채우거나 비웁니다.\n길게 눌러 전환. 63 | tu-tooltip.button-heal = 최대 체력을 초기화하고 완전히 회복합니다. 64 | tu-tooltip.button-invincibility = 체력을 무한대로 설정합니다. 65 | #Wait if tooltips don't work on mobile isn't this completely pointless. 66 | tu-tooltip.button-console = 콘솔 열기. 67 | tu-tooltip.status-apply = 선택한 상태를 적용합니다. 68 | tu-tooltip.status-duration = 적용된 효과의 지속시간. (영구적이지 않을 경우) 69 | tu-tooltip.status-perma = 적용된 상태가 영원히 지속되어야 하나요? (아주 길게!) 70 | tu-tooltip.status-clear = 적용된 효과를 제거합니다. 71 | tu-tooltip.weather-intensity = 날씨의 세기. 72 | tu-tooltip.weather-duration = 날씨의 지속시간. 73 | tu-tooltip.weather-create = 선택한 날씨를 시작합니다. 74 | tu-tooltip.weather-remove = 선택한 날씨를 중지합니다. 75 | tu-tooltip.unit-amount = 소환할 기체 수. 76 | tu-tooltip.unit-radius = 설정한 반지름 내에서 임의의 위치로 소환합니다. 77 | tu-tooltip.unit-pos = 소환할 기체의 위치를 설정합니다. 78 | tu-tooltip.unit-set-team = 소환할 기체의 팀을 설정합니다. 79 | tu-tooltip.unit-transform = 선택한 기체로 변환합니다. 80 | tu-tooltip.unit-despawns = 코어 기체인 알파와 비슷하게,\n변환 후 이전 기체는 소멸합니다. 81 | tu-tooltip.unit-spawn = 선택한 기체를 선택한 위치에 소환합니다. 82 | tu-tooltip.unit-set-wave = 현재 단계를 설정합니다. 83 | tu-tooltip.unit-send-wave = 현재 단계를 보내고 다음 단계로 진행합니다. 84 | tu-tooltip.block-pos = 배치할 블록의 위치를 설정합니다. 85 | tu-tooltip.block-set-team = 배치할 블록의 팀을 설정합니다. 86 | tu-tooltip.block-rotate = 배치할 블록의 회전을 설정합니다. 87 | tu-tooltip.block-place = 선택한 블록을 선택한 위치에 배치합니다.\n(맵의 바깥엔 배치 불가) 88 | tu-tooltip.block-delete = 선택한 위치의 블록을 제거합니다. 89 | 90 | setting.tu-title = Testing Utilities 91 | setting.tu-default-team.name = 기본 선택 팀 (선택됨: {0}) 92 | setting.tu-default-team.description = 유닛 소환과 블록 배치 시 적용될 기본 팀을 설정합니다. 93 | setting.tu-instakill.name = 할복 즉시 적용 94 | setting.tu-despawns.name = 기본적으로 변환 후 이전 기체는 소멸. 95 | setting.tu-permanent.name = 기본적으로 상태이상 효과 영구 적용. 96 | setting.tu-show-hidden.name = 숨겨진 기체/블록을 기체 메뉴와 블록 메뉴에 표시. 97 | setting.tu-show-hidden.description = 숨겨진 컨텐츠에는 숨겨질만한 이유가 있습니다.\n그들을 소환하면 게임이 충돌할 수 있습니다. 98 | setting.tu-fill-all.name = 숨겨진 아이템과 함께 코어 채우기. 99 | setting.tu-fill-all.description = 코어 채우기 기능은 일반적으로 지도의 규칙에 따라 사용할 수 있는 아이템만 채웁니다. 100 | setting.tu-mobile-test.name = 모바일 ui 테스트 101 | setting.tu-mobile-test.description = 변경점을 적용하기 위해서 재시작이 필요합니다. 102 | setting.tu-long-press.name = 길게 누르기 지연 시간 (초) 103 | setting.tu-long-press.description = 변경점을 적용하기 위해서 재시작이 필요합니다. 104 | 105 | mod.test-utils.description = 샌드박스에서 유용한 실험 도구. 106 | mod.test-utils-tool.0 = [#FCC21B]팀 변경:[] 팀을 간단하게 변경합니다. 107 | mod.test-utils-tool.1 = [#FCC21B]할복:[] 스스로 죽입니다. 길게 눌러 계속 죽입니다. [accent](캠페인에서 활성화)[] 108 | mod.test-utils-tool.2 = [#FCC21B]복제:[] 현재 조종하는 기체를 복제합니다. 길게 눌러 계속 복제합니다. 109 | mod.test-utils-tool.3 = [#FCC21B]회복/무적:[] 스스로 치유하거나, 무적으로 만듭니다. 110 | mod.test-utils-tool.4 = [#FCC21B]샌드박스/생존 버튼:[] 무한한 자원으로 전환합니다. 111 | mod.test-utils-tool.5 = [#FCC21B]코어 채움/비움:[] 코어에 모든 아이템을 채우거나 비웁니다. 길게 눌러 바꿉니다. 112 | mod.test-utils-tool.6 = [#FCC21B]상태 메뉴:[] 상태이상 효과를 적용/제거합니다. 113 | mod.test-utils-tool.7 = [#FCC21B]날씨 메뉴:[] 날씨를 시작/중지합니다. 114 | mod.test-utils.tool.8 = [#FCC21B]Light Switch:[] Toggles ambient lighting. Press and hold to change color. 115 | mod.test-utils-tool.9 = [#FCC21B]기체 메뉴:[] 기체를 소환하거나 다른 기체로 변환합니다. 단계 선택도 포함합니다. 116 | mod.test-utils-tool.10 = [#FCC21B]블록 메뉴:[] 아무 곳에나 블록을 배치하세요. 심지어 당신이 해야 하는 것까지도. 117 | mod.test-utils-tool.11 = [#cc6eaf]sk7725/whynotteleport:[] Ctrl + Alt + Click 을 통해 당신의 커서로 순간이동합니다. 모바일은 미안해요. 118 | mod.test-utils-tool.12 = [#FCC21B]콘솔을 활성화하고 확대/축소 범위가 증가합니다.[] 119 | -------------------------------------------------------------------------------- /assets/bundles/bundle_de.properties: -------------------------------------------------------------------------------- 1 | tu-status-menu.name = Effekt Menü 2 | tu-status-menu.permaeff = [accent] (Permanenter Effekt) 3 | tu-status-menu.duration = Dauer (Sekunden): 4 | tu-status-menu.apply = Effekt hinzufügen 5 | tu-status-menu.perma = Permanent 6 | tu-status-menu.clear = Effekte entfernen 7 | tu-world-menu.name = Welten Menü 8 | tu-planet-menu.name = Umgebungs Menü 9 | tu-planet-menu.set = Umgebung festlegen 10 | tu-planet-menu.rules = Regeln festlegen 11 | tu-planet-menu.rules-confirm = Bist du sicher, dass du die Welt-Regeln als Planeten-Regeln festlegen möchtest?\n[red]Das kann nicht rückgängig gemacht werden. 12 | tu-weather-menu.name = Wetter Menü 13 | tu-weather-menu.intensity = Stärke (%): 14 | tu-weather-menu.create = Wetter starten 15 | tu-weather-menu.remove = Wetter stoppen 16 | tu-weather-menu.clear = Alle Wetter stoppen 17 | tu-unit-menu.name = Einheiten Menü 18 | tu-unit-menu.amount = Anzahl: 19 | tu-unit-menu.radius = Spawn Radius: 20 | tu-unit-menu.team = Team setzen 21 | tu-unit-menu.set-team = Team setzen ({0}) 22 | tu-unit-menu.teams-id = Team ID 23 | tu-unit-menu.teams-main = Haupt-Teams 24 | tu-unit-menu.teams-other = Andere Teams 25 | tu-unit-menu.pos = Position: ({0}, {1}) 26 | tu-unit-menu.set-pos = [accent]Position gesetzt! ({0}, {1}) 27 | tu-unit-menu.cancel = [red]Position wurde nicht gesetzt: Nicht auf Welt geklickt. 28 | tu-unit-menu.spawn = Einheit erschaffen 29 | tu-unit-menu.spawn-plural = Einheiten erschaffen 30 | tu-unit-menu.limit = [red]Anzahl ist im Mehrspieler begrenzt!\nGroße Mengen an Einheiten zu erschaffen ist unkreativ, lag-verursachend, und keiner mag es! 31 | tu-unit-menu.transform = Transformieren 32 | tu-unit-menu.despawns = Despawnt 33 | tu-unit-menu.waves = Welle setzen 34 | tu-unit-menu.waves-menu = Welle setzen (Karte: {0}) 35 | tu-unit-menu.wave-range = Listen Reichweite: 36 | tu-unit-menu.wave-start = Start-Welle: 37 | tu-unit-menu.wave-waves = Wellen Anzahl: 38 | tu-unit-menu.wave-current = Jetzige Welle: {0} 39 | tu-unit-menu.wave-total = Insgesamt: {0} Einheiten 40 | tu-unit-menu.wave-send = Welle senden 41 | tu-block-menu.name = Block Menü 42 | tu-block-menu.place = Block setzen 43 | tu-block-menu.delete = Block löschen 44 | tu-block-menu.open-painter = Umgebungsmaler öffnen 45 | tu-painter.cliffs = Klippen spülen 46 | tu-painter.paused = Schließe den Maler bevor du die Welt schließt! 47 | tu-interp-menu.name = Interpretations Graph 48 | tu-sound-menu.name = Tonstudio 49 | tu-sound-menu.vanilla = Vanilla 50 | tu-sound-menu.modded = Mods 51 | tu-sound-menu.sound = Ton 52 | tu-sound-menu.sound-loop = Tonschleife 53 | tu-sound-menu.min-vol = Min. Lautstärke: 54 | tu-sound-menu.max-vol = Max. Lautstärke: 55 | tu-sound-menu.min-pitch = Min. Pitch: 56 | tu-sound-menu.max-pitch = Max. Pitch: 57 | tu-sound-menu.vol = Lautstärke: 58 | tu-sound-menu.pitch = Pitch: 59 | tu-menu.selection = [accent]Ausgewählt: [] 60 | tu-tooltip.button-team = Ändere das Team in dem du bist.\nDrücken und halten um das Menü zu öffnen. 61 | tu-tooltip.button-clone = Klon von dir selbst erschaffen.\nDrücken und halten zum spammen. 62 | tu-tooltip.button-seppuku = Bring dich um, [red]JETZT[]!\nDrücken und halten zum spammen. 63 | tu-tooltip.button-sandbox = Sandbox an-/ausschalten. 64 | tu-tooltip.button-status = Effekt Menü öffnen. 65 | tu-tooltip.button-world = Welten Menü öffnen. 66 | tu-tooltip.button-units = Einheiten Menü öffnen. 67 | tu-tooltip.button-block = Block Menü öffnen. 68 | tu-tooltip.button-core = Plaziere einen "Kern: Scherbe" an deinem Standort. 69 | tu-tooltip.button-fill = Kompletten Kern füllen oder leeren.\nDrücken und halten zum Wechseln. 70 | tu-tooltip.button-heal = Voll heilen. 71 | tu-tooltip.button-invincibility = Unverwundbar werden. 72 | tu-tooltip.button-console-show = Konsole öffnen. 73 | tu-tooltip.button-console-clear = Konsole leeren. 74 | tu-tooltip.button-console-input = Konsolen Befehl eingeben. 75 | tu-tooltip.status-apply = Ausgewählten Status dir selbst geben. 76 | tu-tooltip.status-duration = Dauer des ausgewählten Effekts, wenn er nicht permanent ist. 77 | tu-tooltip.status-perma = Soll der Status für immer anhalten? (Eine lange Zeit!) 78 | tu-tooltip.status-clear = Entferne jeden deiner Stauts Effekte. 79 | tu-tooltip.weather-intensity = Intensität des Wetters. 80 | tu-tooltip.weather-duration = Dauer des Wetter. 81 | tu-tooltip.weather-create = Starte das ausgewählte Wetter. 82 | tu-tooltip.weather-remove = Beende das ausgewählte Wetter. 83 | tu-tooltip.planet-set = Setze die Umgebung zu dem ausgewählten Planetens Umgebung. 84 | tu-tooltip.planet-rules = Setze die Regeln zu den ausgewählten Planetens Standart Regeln.\n[red]WARNUNG: Dies kann nicht rückgängig gemacht werden. 85 | tu-tooltip.unit-amount = Anzahl der erschaffenen Einheiten. 86 | tu-tooltip.unit-radius = Radius in dem die Einheiten zufällig erschaffen werden. 87 | tu-tooltip.unit-pos = Die Position der erschaffenen Einheiten festlegen. 88 | tu-tooltip.unit-set-team = Das Team der erschaffenen Einheiten festlegen. 89 | tu-tooltip.unit-transform = In die ausgewählte Einheit transformieren. 90 | tu-tooltip.unit-despawns = Soll die Einheit in die du dich transformierst despawnen,\nähnlich zu Kerneinheiten wie Alpha. 91 | tu-tooltip.unit-spawn = Erschafft die ausgewählte Einheit am ausgewählten Ort. 92 | tu-tooltip.unit-set-wave = Die jetzige Welle festlegen. 93 | tu-tooltip.unit-send-wave = Schicke die jetzige Welle und schreite zur nächsten voran. 94 | tu-tooltip.block-pos = Die Position des plazierten Blocks festlegen. 95 | tu-tooltip.block-set-team = Das Team der plazierten Blöcke festlegen. 96 | tu-tooltip.block-rotate = Die Ausrichtung der plazierten Blöcke festlegen. 97 | tu-tooltip.block-place = Plaziere den ausgewählten Block am ausgewählten Ort.\n(Kann nicht ausserhalb der Karte plazieren) 98 | tu-tooltip.block-delete = Entferne den Block am ausgewählten Ort. 99 | tu-tooltip.block-terrain-painter-open = Öffnet das Umbegungsmaler HUD. 100 | tu-tooltip.painter-cliffs = Spült ungesetzte Klippen 101 | tu-tooltip.painter-close = Umgebungsmaler HUD schließen.\nLadet neu wenn benötigt. 102 | setting.tu-title = Testing Utilities 103 | setting.tu-default-team.name = Als Standart ausgewähltes Team ({0}) 104 | setting.tu-default-team.description = Standart Team für Einheitenerschaffung oder Blockplazierung festlegen. 105 | setting.tu-instakill.name = Harakiri ist sofortig 106 | setting.tu-despawns.name = Transformierte Einheiten despawnen ist standart 107 | setting.tu-permanent.name = Status Effekte sind permanent ist standart 108 | setting.tu-show-hidden.name = Ausgeblendete Einheiten/Blöcke im Einheiten Menü und Block Menü anzeigen 109 | setting.tu-show-hidden.description = Ausgeblendete Inhalte sind absichtlich ausgeblendet.\nDas Erschaffen kann das Spiel abstürzen lassen. 110 | setting.tu-fill-all.name = Kern füllen füllt auch ausgeblendete Items. 111 | setting.tu-fill-all.description = Kern füllen füllt gewöhnlich nur Items die laut Spielregeln verfügbar sind. 112 | setting.tu-wu-coords.name = Welteinheiten Koordinaten unter Block Koordinaten anzeigen. 113 | setting.tu-mobile-test.name = Test mobile ui 114 | setting.tu-mobile-test.description = Neustart erforderlich um Änderungenzu übernehmen. 115 | setting.tu-long-press.name = Drücken und halten Verzögerung 116 | setting.tu-lerp-time.name = Interpretations Lerp Zeit 117 | setting.tu-interp.name = Interpretations Graph öffnen 118 | setting.tu-sounds.name = Tonstudio öffnen 119 | mod.test-utils.description = Utilities fürs Rumprobieren im Sandbox. 120 | mod.test-utils-tool.0 = [#FCC21B]Team wechslen:[] Wechsle einfach Teams. 121 | mod.test-utils-tool.1 = [#FCC21B]Harakiri:[] Bring dich um. Drücken und halten zum Wiederholen. [accent](Aktiviert in Kampagne)[] 122 | mod.test-utils-tool.2 = [#FCC21B]Klonen:[] Klont dich selbst. Drücken und halten zum Wiederholen. 123 | mod.test-utils-tool.3 = [#FCC21B]Heilen/Unverwundbarkeit:[] Heile dich oder mache dich unverwundbar. 124 | mod.test-utils-tool.4 = [#FCC21B]Sandbox/Überleben:[] Schalte unendliche Ressourcen ein/aus (Sandbox). 125 | mod.test-utils-tool.5 = [#FCC21B]Kern füllen/leeren:[] Füllt order leert deinen Kern mit allen Ressourcen. Drücken und halten um zwischen füllen und leeren zu wechseln. [#e55454](Deaktiviert im Kampagne)[] 126 | mod.test-utils-tool.6 = [#FCC21B]Effekte Menü:[] Füge hinzu oder entferne Effekte. 127 | mod.test-utils-tool.7 = [#FCC21B]Wetter Menü:[] Startet/Beendet Wetter. 128 | mod.test-utils.tool.8 = [#FCC21B]Light Switch:[] Toggles ambient lighting. Press and hold to change color. 129 | mod.test-utils-tool.9 = [#FCC21B]Einheiten Menü:[] Erschaffe Einheiten oder transformiere dich. Beinhaltet einen Wellen-Selektor. 130 | mod.test-utils-tool.10 = [#FCC21B]Block Menü:[] Plaziere oder entferne Blöcke überall. Beinhaltet einen Umgebungsmaler. 131 | mod.test-utils-tool.11 = [#cc6eaf]sk7725/whynotteleport:[] Alt + Klick um dich zum Cursor zu teleportieren. (Entschuldige wenn du auf Mobile bist.) 132 | mod.test-utils-tool.12 = [#FCC21B]Erhöht Zoomreichweite.[] 133 | -------------------------------------------------------------------------------- /assets/bundles/bundle_ru.properties: -------------------------------------------------------------------------------- 1 | # suppress inspection "UnusedProperty" for whole file 2 | 3 | tu-ui-button.death = Сеппуку 4 | tu-ui-button.clone = Клон 5 | tu-ui-button.core = Ядро 6 | tu-ui-button.fill = Заполнить 7 | tu-ui-button.dump = Опустошить 8 | tu-ui-button.heal = Ремонт 9 | tu-ui-button.invincible = Неуязвимость 10 | 11 | tu-status-menu.name = Меню эффектов статуса 12 | tu-status-menu.permaeff = [accent] (Постоянный эффект) 13 | tu-status-menu.duration = Длительность (секунды): 14 | tu-status-menu.apply = Добавить эффект 15 | tu-status-menu.perma = Постоянный 16 | tu-status-menu.clear = Очистить эффекты 17 | 18 | tu-weather-menu.name = Меню погоды 19 | tu-weather-menu.intensity = Интенсивность (%): 20 | tu-weather-menu.create = Начать погоду 21 | tu-weather-menu.remove = Остановить погоду 22 | tu-weather-menu.clear = Остановить всю погоду 23 | 24 | tu-unit-menu.name = Меню боевых единиц 25 | tu-unit-menu.amount = Количество: 26 | tu-unit-menu.radius = Радиус появления: 27 | tu-unit-menu.team = Выбрать команду 28 | tu-unit-menu.set-team = Выбранная команда (Выбрано: {0}) 29 | tu-unit-menu.teams-id = ID команды 30 | tu-unit-menu.teams-main = Основные команды 31 | tu-unit-menu.teams-other = Прочие команды 32 | tu-unit-menu.pos = Позиция: ({0}, {1}) 33 | tu-unit-menu.set-pos = [accent]Позиция задана! ({0}, {1}) 34 | tu-unit-menu.cancel = [red]Выбор позиции отменён: Вы нажали не на карту. 35 | tu-unit-menu.spawn = Создать боевую единицу 36 | tu-unit-menu.spawn-plural = Создать боевые единицы 37 | tu-unit-menu.transform = Трансформация 38 | tu-unit-menu.despawns = Уничтожение 39 | tu-unit-menu.waves = Задать волну 40 | tu-unit-menu.waves-menu = Заданная волна (Карта: {0}) 41 | tu-unit-menu.wave-range = Задать размер списка волн: 42 | tu-unit-menu.wave-start = Начальная волна: 43 | tu-unit-menu.wave-waves = Количество волн: 44 | tu-unit-menu.wave-current = Текущая волна: {0} 45 | tu-unit-menu.wave-total = Итого: {0} боевых единиц 46 | tu-unit-menu.wave-send = Отправить волну 47 | 48 | tu-block-menu.name = Меню блоков 49 | tu-block-menu.place = Поставить блок 50 | tu-block-menu.delete = Удалить блок 51 | 52 | tu-interp-menu.name = График интерполяции 53 | 54 | tu-menu.selection = [accent]Текущий выбор: 55 | 56 | tu-tooltip.button-team = Сменить Вашу текущую команду.\nУдерживайте, чтобы открыть меню. 57 | tu-tooltip.button-clone = Создать своего клона.\nУдерживайте для спама. 58 | tu-tooltip.button-seppuku = Самоуничтожься, [red]СЕЙЧАС[]!\nУдерживайте для спама. 59 | tu-tooltip.button-sandbox = Переключить режим песочницы. 60 | tu-tooltip.button-status = Открыть меню эффектов статуса. 61 | tu-tooltip.button-weather = Открыть меню погоды. 62 | tu-tooltip.button-units = Открыть меню боевых единиц. 63 | tu-tooltip.button-block = Открыть меню блоков. 64 | tu-tooltip.button-fill = Полностью заполнить или опустошить ядро.\nУдерживайте для переключения режима. 65 | tu-tooltip.button-heal = Сбросить макс. прочность и полностью восстановиться. 66 | tu-tooltip.button-invincibility = Бесконечная прочность и максимальная прочность. 67 | #Wait if tooltips don't work on mobile isn't this completely pointless. 68 | tu-tooltip.button-console-show = Открыть консоль. 69 | tu-tooltip.button-console-clear = Очистить консоль. 70 | tu-tooltip.button-console-input = Ввести консольную команду. 71 | tu-tooltip.status-apply = Добавить выбранный статус своей собственной боевой единице. 72 | tu-tooltip.status-duration = Длительность выбранного эффекта, если тот не постоянный. 73 | tu-tooltip.status-perma = Должен ли выбранный статус длиться вечно? (Это надолго!) 74 | tu-tooltip.status-clear = Очистить все свои статусы. 75 | tu-tooltip.weather-intensity = Интенсивность погоды. 76 | tu-tooltip.weather-duration = Длительность погоды. 77 | tu-tooltip.weather-create = Начать выбранную погоду. 78 | tu-tooltip.weather-remove = Остановить выбранную погоду. 79 | tu-tooltip.unit-amount = Количество созданных боевых единиц. 80 | tu-tooltip.unit-radius = Случайный радиус места появления. 81 | tu-tooltip.unit-pos = Задать позицию созданных боевых единиц. 82 | tu-tooltip.unit-set-team = Задать команду созданных боевых единиц. 83 | tu-tooltip.unit-transform = Трансформироваться в выбранную боевую единицу. 84 | tu-tooltip.unit-despawns = Должна ли трансформированная боевая единица исчезнуть,\nнаподобие дронов, как Альфа. 85 | tu-tooltip.unit-spawn = Создаёт выбранную боевую единицу в выбранной позиции. 86 | tu-tooltip.unit-set-wave = Задать текущую волну. 87 | tu-tooltip.unit-send-wave = Задать текущую волну и продвинуться до следующей. 88 | tu-tooltip.block-pos = Задать позицию поставленных блоков. 89 | tu-tooltip.block-set-team = Задать команду поставленных блоков. 90 | tu-tooltip.block-rotate = Задать направление поставленных блоков. 91 | tu-tooltip.block-place = Поставить выбранный блок на выбранной позиции.\n(Нельзя поставить вне карты) 92 | tu-tooltip.block-delete = Удалить блок на выбранной позиции. 93 | 94 | setting.tu-title = Testing Utilities 95 | setting.tu-default-team.name = Команда по умолчанию (Выбрано: {0}) 96 | setting.tu-default-team.description = Задать команду по умолчанию для создания боевых единиц и установки блоков. 97 | setting.tu-instakill.name = Мгновенный сеппуку 98 | setting.tu-despawns.name = Трансформированная боевая единица исчезает по умолчанию 99 | setting.tu-permanent.name = Заданный эффект статуса постоянный по умолчанию 100 | setting.tu-show-hidden.name = Показать скрытые боевые единицы/блоки в соответствующих меню. 101 | setting.tu-show-hidden.description = Скрытый контент скрыт не просто так.\nИх создание может привести к вылету игры. 102 | setting.tu-fill-all.name = Функция заполнения ядра также заполняет скрытыми предметами. 103 | setting.tu-fill-all.description = Функция заполнения ядра обычно только заполняет предметами,\nдоступными в зависимости от правил карты. 104 | setting.tu-mobile-test.name = Мобильный интерфейс 105 | setting.tu-mobile-test.description = Перезагрузите игру, чтобы изменения вступили в силу. 106 | setting.tu-long-press.name = Время ожидания удержания кнопки 107 | setting.tu-long-press.description = Перезагрузите игру, чтобы изменения вступили в силу. 108 | setting.tu-lerp-time.name = Время отрисовки графика 109 | setting.tu-interp.name = Открыть график интерполяции. 110 | 111 | mod.test-utils.description = Утилиты для тестирования в режиме песочницы. 112 | mod.test-utils-tool.0 = [#FCC21B]Смена команды:[] Легко меняйте свою команду. 113 | mod.test-utils-tool.1 = [#FCC21B]Сеппуку:[] Самоуничтожьтесь. Удерживайте для многократного выполнения. [accent](Включено в кампании)[] 114 | mod.test-utils-tool.2 = [#FCC21B]Клонирование:[] Клонирует управляемую боевую единицу. Удерживайте для многократного клонирования. 115 | mod.test-utils-tool.3 = [#FCC21B]Ремонт/Неуязвимость:[] Починитесь, или сделайте себя неуязвимым. 116 | mod.test-utils-tool.4 = [#FCC21B]Кнопка "Песочница/Выживание":[] Переключает бесконечные ресурсы. 117 | mod.test-utils-tool.5 = [#FCC21B]Заполнить/Опустошить ядро:[] Полностью заполните или опустошите Ваше ядро. Удерживайте для переключения режима. 118 | mod.test-utils-tool.6 = [#FCC21B]Меню статусов:[] Добавляйте или убирайте с себя эффекты статусов. 119 | mod.test-utils-tool.7 = [#FCC21B]Меню погоды:[] Начинайте/останавливайте погоду. 120 | mod.test-utils.tool.8 = [#FCC21B]Light Switch:[] Toggles ambient lighting. Press and hold to change color. 121 | mod.test-utils-tool.9 = [#FCC21B]Меню боевых единиц:[] Создавайте боевые единицы или трансформируйтесь в другую боевую единицу. Включает в себя выборку волн. 122 | mod.test-utils-tool.10 = [#FCC21B]Меню блоков:[] Ставьте блоки где угодно, даже те, которые вы не должны ставить. 123 | mod.test-utils-tool.11 = [#cc6eaf]sk7725/whynotteleport:[] Ctrl + Alt + Клик для телепортации в свой курсор. Приносим извинения мобильным игрокам. 124 | mod.test-utils-tool.12 = [#FCC21B]Включает консоль и увеличивает дальность масштабирования.[] 125 | -------------------------------------------------------------------------------- /assets/bundles/bundle_uk_UA.properties: -------------------------------------------------------------------------------- 1 | # suppress inspection "UnusedProperty" for whole file 2 | 3 | tu-ui-button.death = Сеппуку 4 | tu-ui-button.clone = Клонувати 5 | tu-ui-button.core = ядро 6 | tu-ui-button.fill = Заповнити 7 | tu-ui-button.dump = Спорожнити 8 | tu-ui-button.heal = Зцілення 9 | tu-ui-button.invincible = Невидимість 10 | 11 | tu-status-menu.name = Меню ефектів стану 12 | tu-status-menu.permaeff = [accent] (Постійний ефект) 13 | tu-status-menu.duration = Тривалість в секундах: 14 | tu-status-menu.apply = Застосувати ефект 15 | tu-status-menu.perma = Постійно 16 | tu-status-menu.clear = Очистити ефекти стану 17 | 18 | tu-weather-menu.name = Меню погоди 19 | tu-weather-menu.intensity = Інтенсивність (%): 20 | tu-weather-menu.create = Почати погоду 21 | tu-weather-menu.remove = Зупинити погоду 22 | tu-weather-menu.clear = Зупинити всю погоду 23 | 24 | tu-unit-menu.name = Меню одиниць 25 | tu-unit-menu.amount = Кількість 26 | tu-unit-menu.radius = Радіус появи 27 | tu-unit-menu.team = Установити команду 28 | tu-unit-menu.set-team = Установити команду (вибрано: {0}) 29 | tu-unit-menu.teams-id = ID команди: 30 | tu-unit-menu.teams-main = Головні команди 31 | tu-unit-menu.teams-other = Інші команди 32 | tu-unit-menu.pos = Місцезнаходження: ({0}, {1}) 33 | tu-unit-menu.set-pos = [accent]Місцезнаходження встановлено! ({0}, {1}) 34 | tu-unit-menu.cancel = [red]Вибір місцезнаходження скасовано. Ви не натиснули кудись у світі. 35 | tu-unit-menu.spawn = Поява одиниці 36 | tu-unit-menu.spawn-plural = Поява одиниць 37 | tu-unit-menu.limit = [red]Кікільсть обмежена в багатоосібній грі!\nСтворення великої кількості одиниць є некреативним, реально сповільнює гру, і ніхто цього не любить. 38 | tu-unit-menu.transform = Перетворення 39 | tu-unit-menu.despawns = Видалити 40 | tu-unit-menu.waves = Установити хвилю 41 | tu-unit-menu.waves-menu = Установити хвилю (Мапа: {0}) 42 | tu-unit-menu.wave-range = Установити діапазон списку хвиль: 43 | tu-unit-menu.wave-start = Початкова хвиль: 44 | tu-unit-menu.wave-waves = Кількість хвиль: 45 | tu-unit-menu.wave-current = Поточна хвиля: {0} 46 | tu-unit-menu.wave-total = Усього: {0} одиниць 47 | tu-unit-menu.wave-send = Надіслати хвилю 48 | 49 | tu-block-menu.name = Меню блоків 50 | tu-block-menu.place = Меню розміщення 51 | tu-block-menu.delete = Меню видалення 52 | 53 | tu-interp-menu.name = Візуалізатор інтерполяції 54 | 55 | tu-menu.selection = [accent]Поточний вибір: 56 | 57 | tu-tooltip.button-team = Змінити команду, в якій ви знаходитеся. \nНатисніть та утримуйте для спаму. 58 | tu-tooltip.button-clone = Створити клон самого себе.\nНатисніть та утримуйте для спаму. 59 | tu-tooltip.button-seppuku = Убий себе, [red]НЕГАЙНО[]!\nНатисніть та утримуйте для спаму. 60 | tu-tooltip.button-sandbox = Перемикання пісочниці. 61 | tu-tooltip.button-status = Відкриває меню ефектів стану. 62 | tu-tooltip.button-weather = Відкриває меню погоди. 63 | tu-tooltip.button-units = Відкриває меню одиниць. 64 | tu-tooltip.button-block = Відкриває меню блоків. 65 | tu-tooltip.button-fill = Повністю заповнює або спорожнює ядро.\nНатисніть і утримуйте, щоби перемкнути. 66 | tu-tooltip.button-heal = Максимальне здоров’я стає стандартним та ви виліковуєтеся повністю. 67 | tu-tooltip.button-invincibility = Установити здоров'я та максимальне здоров'я на нескінченність. 68 | #Wait if tooltips don't work on mobile isn't this completely pointless. 69 | tu-tooltip.button-console-show = Показати консоль. 70 | tu-tooltip.button-console-clear = Очистити консоль. 71 | tu-tooltip.button-console-input = Ввести консольну команду. 72 | tu-tooltip.status-apply = Застосовує вибраний ефект стану на вас. 73 | tu-tooltip.status-duration = Тривалість застосованого ефекту, якщо він не є постійним. 74 | tu-tooltip.status-perma = Чи має ефект стану тривати вічно? (Надовго!) 75 | tu-tooltip.status-clear = Очищує застосований ефект стану на вас. 76 | tu-tooltip.weather-intensity = Інтенсивність погоди. 77 | tu-tooltip.weather-duration = Тривалість погоди. 78 | tu-tooltip.weather-create = Вмикає вибрану погоду. 79 | tu-tooltip.weather-remove = Вимикає вибрану погоду. 80 | tu-tooltip.unit-amount = Кількість породжених одиниць. 81 | tu-tooltip.unit-radius = Радіус випадковості місця породження. 82 | tu-tooltip.unit-pos = Установлює місцерозташування породжених одиниць. 83 | tu-tooltip.unit-set-team = Установлює команду породжених одиниць. 84 | tu-tooltip.unit-transform = Трансформує у вибрану одиницю. 85 | tu-tooltip.unit-despawns = Чи повинна одиниця видалятися подібно до одиниць з ядра? 86 | tu-tooltip.unit-spawn = Породжує вибрану одиницю на вибраній позиції 87 | tu-tooltip.unit-set-wave = Установлює поточну хвилю. 88 | tu-tooltip.unit-send-wave = Установлює поточну хвилю та переходить до наступної. 89 | tu-tooltip.block-pos = Установлює позицію розміщуваних блоків. 90 | tu-tooltip.block-set-team = Установлює команду розміщуваних блоків. 91 | tu-tooltip.block-rotate = Установлює поворот розміщуваних блоків. 92 | tu-tooltip.block-place = Розміщує вибраний блок у вибраній позицію.\n(Розміщення за межами мапи неможливе) 93 | tu-tooltip.block-delete = Видаляє блок на заданій позиції. 94 | 95 | setting.tu-title = Утиліти для тестування 96 | setting.tu-default-team.name = Стандартна команда (вибрано: {0}) 97 | setting.tu-default-team.description = Установити стандартну команду для породжувача одиниць та розміщувача блоків. 98 | setting.tu-instakill.name = Миттєве самогубство 99 | setting.tu-despawns.name = Трансформована одиниця зникає 100 | setting.tu-permanent.name = Після застосування ефект стану лишається назавжди 101 | setting.tu-show-hidden.name = Показує приховані одиниці та блоки в меню одиниць і меню блоків. 102 | setting.tu-show-hidden.description = Прихований вміст прихований неспроста.\nЙого поява може призвести до збою в грі. 103 | setting.tu-fill-all.name = Заповнення ядра також заповнює прихованими предметами. 104 | setting.tu-fill-all.description = Заповнення ядра зазвичай заповнює лише предметами,\n що доступні відповідно до правил мапи. 105 | setting.tu-mobile-test.name = Тестовий мобільний інтерфейс. 106 | setting.tu-mobile-test.description = Щоб застосувати зміни, потрібен перезапуск. 107 | setting.tu-long-press.name = Натисніть і утримуйте час затримки (сек.) 108 | setting.tu-long-press.description = Щоб застосувати зміни, потрібен перезапуск. 109 | setting.tu-lerp-time.name = Час лінійної інтерполяції 110 | setting.tu-interp.name = Відкрити візуалізатор інтерполяції 111 | 112 | mod.test-utils.description = Утиліти для тестування речей в пісочниці. 113 | mod.test-utils-tool.0 = [#FCC21B]Змінювальник команд:[] змінюйте команди легко. 114 | mod.test-utils-tool.1 = [#FCC21B]Сеппууку:[] вбийте себе. Натисніть та утримуйте для багаторазового вбивства. [accent](Увімкнено в кампанії)[] 115 | mod.test-utils-tool.2 = [#FCC21B]Клонування:[] клонуйте керовану вами одиницю. Натисніть та утримуйте для багаторазового клонування. 116 | mod.test-utils-tool.3 = [#FCC21B]Зцілення та невидимість:[] зцілює вас або робить вас невидимим. 117 | mod.test-utils-tool.4 = [#FCC21B]Пісочниця/Виживання:[] перемикає наявність нескінченних ресурсів. 118 | mod.test-utils-tool.5 = [#FCC21B]Заповнити чи спорожнити ядро[]: заповнює чи спорожнює ваше ядре всі предмети. Натисніть та утримуйте, щоби перемкнути. 119 | mod.test-utils-tool.6 = [#FCC21B]Меню ефектів стану:[] застосовує ефекти стану на себе. 120 | mod.test-utils-tool.7 = [#FCC21B]Меню погоди:[] вмикає чи вимикає погоду. [#e55454](Вимнено в кампанії)[] 121 | mod.test-utils.tool.8 = [#FCC21B]Light Switch:[] Toggles ambient lighting. Press and hold to change color. 122 | mod.test-utils-tool.9 = [#FCC21B]Меню одиниць:[] породжує одиниці або трансформує в певну одиницю. Включає вибір хвиль.[#e55454](Вимкнено в кампанії)[] 123 | mod.test-utils-tool.10 = [#FCC21B]Меню блоків:[] розміщуйте блоки де завгодно. 124 | mod.test-utils-tool.11 = [#cc6eaf]sk7725/whynotteleport:[] Ctrl + Alt + ЛКМ, щоби телепортуватися до свого курсору. Перепрошуємо, якщо ви на телефоні. 125 | mod.test-utils-tool.12 = [#FCC21B]Вмикає консоль і збільшує діапазон масштабування.[] 126 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/icon.png -------------------------------------------------------------------------------- /assets/sprites/frog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/frog.png -------------------------------------------------------------------------------- /assets/sprites/icons/clone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/icons/clone.png -------------------------------------------------------------------------------- /assets/sprites/icons/core.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/icons/core.png -------------------------------------------------------------------------------- /assets/sprites/icons/dump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/icons/dump.png -------------------------------------------------------------------------------- /assets/sprites/icons/fullSword.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/icons/fullSword.png -------------------------------------------------------------------------------- /assets/sprites/icons/heal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/icons/heal.png -------------------------------------------------------------------------------- /assets/sprites/icons/invincibility.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/icons/invincibility.png -------------------------------------------------------------------------------- /assets/sprites/icons/light-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/icons/light-off.png -------------------------------------------------------------------------------- /assets/sprites/icons/light-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/icons/light-on.png -------------------------------------------------------------------------------- /assets/sprites/icons/musics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/icons/musics.png -------------------------------------------------------------------------------- /assets/sprites/icons/sandbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/icons/sandbox.png -------------------------------------------------------------------------------- /assets/sprites/icons/seppuku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/icons/seppuku.png -------------------------------------------------------------------------------- /assets/sprites/icons/sounds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/icons/sounds.png -------------------------------------------------------------------------------- /assets/sprites/icons/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/icons/stop.png -------------------------------------------------------------------------------- /assets/sprites/icons/survival.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/icons/survival.png -------------------------------------------------------------------------------- /assets/sprites/icons/weather.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/icons/weather.png -------------------------------------------------------------------------------- /assets/sprites/settings-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/settings-banner.png -------------------------------------------------------------------------------- /assets/sprites/settings-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/settings-icon.png -------------------------------------------------------------------------------- /assets/sprites/ui/button-center-disabled.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/ui/button-center-disabled.9.png -------------------------------------------------------------------------------- /assets/sprites/ui/button-center-down.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/ui/button-center-down.9.png -------------------------------------------------------------------------------- /assets/sprites/ui/button-center-over.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/ui/button-center-over.9.png -------------------------------------------------------------------------------- /assets/sprites/ui/button-center.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/ui/button-center.9.png -------------------------------------------------------------------------------- /assets/sprites/ui/button-left-down.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/ui/button-left-down.9.png -------------------------------------------------------------------------------- /assets/sprites/ui/button-left-over.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/ui/button-left-over.9.png -------------------------------------------------------------------------------- /assets/sprites/ui/button-left.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/ui/button-left.9.png -------------------------------------------------------------------------------- /assets/sprites/ui/button-right-down.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/ui/button-right-down.9.png -------------------------------------------------------------------------------- /assets/sprites/ui/button-right-over.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/ui/button-right-over.9.png -------------------------------------------------------------------------------- /assets/sprites/ui/button-right.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/ui/button-right.9.png -------------------------------------------------------------------------------- /assets/sprites/ui/pane-bottom.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/assets/sprites/ui/pane-bottom.9.png -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "java" 2 | 3 | version '1.0' 4 | 5 | sourceSets.main.java.srcDirs = ["src"] 6 | 7 | repositories{ 8 | mavenCentral() 9 | maven{ url "https://raw.githubusercontent.com/Zelaux/MindustryRepo/master/repository" } 10 | maven { url = uri("https://maven.xpdustry.com/mindustry") } 11 | maven{ url 'https://www.jitpack.io' } 12 | } 13 | 14 | java{ 15 | targetCompatibility = 8 16 | sourceCompatibility = JavaVersion.VERSION_17 17 | } 18 | 19 | ext{ 20 | //windows sucks 21 | isWindows = System.getProperty("os.name").toLowerCase().contains("windows") 22 | sdkRoot = System.getenv("ANDROID_HOME") ?: System.getenv("ANDROID_SDK_ROOT") 23 | } 24 | 25 | //java 8 backwards compatibility flag 26 | allprojects{ 27 | tasks.withType(JavaCompile){ 28 | options.compilerArgs.addAll(['--release', '8']) 29 | } 30 | } 31 | 32 | dependencies{ 33 | compileOnly "com.github.Anuken.Arc:arc-core:$mindustryVersion" 34 | compileOnly "com.github.Anuken.Mindustry:core:$mindustryVersion" 35 | 36 | annotationProcessor "com.github.Anuken:jabel:$jabelVersion" 37 | 38 | implementation "com.github.MEEPofFaith:BottomLeftUILib:$bluiVersion" 39 | } 40 | 41 | //force arc version 42 | configurations.all{ 43 | resolutionStrategy.eachDependency { details -> 44 | if (details.requested.group == 'com.github.Anuken.Arc') { 45 | details.useVersion "$mindustryVersion" 46 | } 47 | } 48 | } 49 | 50 | task jarAndroid{ 51 | dependsOn "jar" 52 | 53 | doLast{ 54 | if(!sdkRoot || !new File(sdkRoot).exists()) throw new GradleException("No valid Android SDK found. Ensure that ANDROID_HOME is set to your Android SDK directory."); 55 | 56 | def platformRoot = new File("$sdkRoot/platforms/").listFiles().sort().reverse().find{ f -> new File(f, "android.jar").exists()} 57 | 58 | if(!platformRoot) throw new GradleException("No android.jar found. Ensure that you have an Android platform installed.") 59 | 60 | //collect dependencies needed for desugaring 61 | def dependencies = (configurations.compileClasspath.asList() + configurations.runtimeClasspath.asList() + [new File(platformRoot, "android.jar")]).collect{ "--classpath $it.path" }.join(" ") 62 | 63 | def d8 = isWindows ? "d8.bat" : "d8" 64 | 65 | //dex and desugar files - this requires d8 in your PATH 66 | "$d8 $dependencies --min-api 14 --output ${project.archivesBaseName}Android.jar ${project.archivesBaseName}Desktop.jar" 67 | .execute(null, new File("$buildDir/libs")).waitForProcessOutput(System.out, System.err) 68 | } 69 | } 70 | 71 | jar{ 72 | archiveFileName = "${base.archivesBaseName}Desktop.jar" 73 | 74 | from{ 75 | configurations.runtimeClasspath.collect{ it.isDirectory() ? it : zipTree(it) } 76 | } 77 | 78 | from(projectDir){ 79 | include "mod.hjson" 80 | } 81 | 82 | from("assets/"){ 83 | include "**" 84 | } 85 | } 86 | 87 | task deploy(type: Jar){ 88 | dependsOn jarAndroid 89 | dependsOn jar 90 | archiveFileName = "${base.archivesBaseName}.jar" 91 | 92 | from{ [zipTree("$buildDir/libs/${project.archivesBaseName}Desktop.jar"), zipTree("$buildDir/libs/${project.archivesBaseName}Android.jar")] } 93 | 94 | doLast{ 95 | delete{ 96 | delete "$buildDir/libs/${project.archivesBaseName}Desktop.jar" 97 | delete "$buildDir/libs/${project.archivesBaseName}Android.jar" 98 | } 99 | } 100 | } 101 | 102 | task move doLast{ 103 | println "Yeeting the mod jar to the shadow realm..." 104 | 105 | String os = System.getProperty("os.name").toLowerCase(Locale.ROOT) 106 | if(os.contains("windows")) { 107 | ant.move file: "${buildDir}\\libs\\${project.archivesBaseName}Desktop.jar", 108 | todir: "${System.env.USERPROFILE}\\AppData\\Roaming\\Mindustry\\mods" 109 | }else if(os.contains("linux")) { //from Router 110 | ant.move file: "${buildDir}/libs/${project.archivesBaseName}Desktop.jar", 111 | todir: "${System.env.USERPROFILE}/.local/share/Mindustry/mods" 112 | }else{ 113 | println "Yeah I don't know how to do this on a Mac can someone tell me?" 114 | } 115 | } 116 | 117 | task jarMove dependsOn "jar", "move" 118 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | mindustryVersion = v147 2 | jabelVersion = 93fde537c7 3 | bluiVersion = v12 4 | 5 | org.gradle.jvmargs=--illegal-access=permit \ 6 | --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ 7 | --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED \ 8 | --add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED \ 9 | --add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED \ 10 | --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \ 11 | --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \ 12 | --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \ 13 | --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \ 14 | --add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED \ 15 | --add-exports=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED \ 16 | --add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED \ 17 | --add-exports=java.base/sun.reflect.annotation=ALL-UNNAMED 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MEEPofFaith/testing-utilities-java/5f0088a2a4123b370666c6425af6ec84bc02a523/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /mod.hjson: -------------------------------------------------------------------------------- 1 | #the mod name as displayed in-game 2 | displayName: "Testing Utilities" 3 | 4 | #the internal name of your mod 5 | name: "test-utils" 6 | 7 | #your name 8 | author: "MEEP of Faith" 9 | 10 | #the fully qualified main class of the mod 11 | main: "testing.TestUtils" 12 | 13 | #mod repo on github 14 | repo: MEEPofFaith/testing-utilities-java 15 | 16 | #the mod description as seen in the mod dialog 17 | description: "Utilities for testing stuff in sandbox. (Load the game with this mod enabled for more info.)" 18 | 19 | #short description displayed under the title 20 | subtitle: "Easier to use than the console." 21 | 22 | version: 68.6 23 | 24 | #the minimum game build required to run this mod 25 | minGameVersion: 147 26 | 27 | #this is a java mod 28 | java: true 29 | 30 | #doesn't have content; client-sided 31 | hidden: true 32 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | def name = 'BottomLeftUILib' 2 | if(new File(rootDir.parent, name).exists()){ 3 | println("Compiling with local BLUI Lib") 4 | includeBuild("../${name}"){ 5 | dependencySubstitution{sub -> 6 | sub.all{ 7 | if(it.requested instanceof ModuleComponentSelector){ 8 | def req = it.requested as ModuleComponentSelector 9 | if(req.group == 'com.github.Anuken.Arc'){ 10 | useTarget "com.github.Anuken.Arc:$req.module:$mindustryVersion" 11 | }else if(req.group.startsWith('com.github.MEEPofFaith')){ 12 | def group = req.group.substring('com.github.MEEPofFaith'.length()) 13 | if(group.isEmpty() && req.module == name){ 14 | useTarget sub.project(':') 15 | }else if(!group.isEmpty() && group.substring(1) == name){ 16 | if(req.module.endsWith('.gradle.plugin')){ 17 | useTarget sub.project(':') 18 | }else{ 19 | useTarget sub.project(":$req.module") 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/testing/TestUtils.java: -------------------------------------------------------------------------------- 1 | package testing; 2 | 3 | import arc.*; 4 | import arc.func.*; 5 | import arc.graphics.g2d.*; 6 | import arc.input.*; 7 | import arc.math.*; 8 | import arc.struct.*; 9 | import arc.util.*; 10 | import mindustry.game.EventType.*; 11 | import mindustry.graphics.*; 12 | import mindustry.mod.*; 13 | import mindustry.mod.Mods.*; 14 | import testing.buttons.*; 15 | import testing.content.*; 16 | import testing.content.TUFx.*; 17 | import testing.ui.*; 18 | import testing.util.*; 19 | 20 | import static arc.Core.*; 21 | import static mindustry.Vars.*; 22 | import static testing.ui.TUDialogs.*; 23 | 24 | public class TestUtils extends Mod{ 25 | private static boolean teleport; 26 | 27 | public TestUtils(){ 28 | if(settings.getBool("tu-mobile-test", false)) mobile = testMobile = true; 29 | 30 | if(mobile) loadLogger(); 31 | 32 | //Add campaign maps to custom maps list 33 | Seq mapNames = new Seq<>(); 34 | mapNames.addAll( //Sectors aren't loaded yet, need to hardcode 35 | "groundZero", 36 | "craters", "biomassFacility", "taintedWoods", "frozenForest", "ruinousShores", "facility32m", "windsweptIslands", "stainedMountains", "tarFields", 37 | "frontier", "fungalPass", "infestedCanyons", "atolls", "mycelialBastion", "extractionOutpost", "saltFlats", "testingGrounds", "overgrowth", 38 | "impact0078", "desolateRift", "nuclearComplex", "planetaryTerminal", 39 | "coastline", "navalFortress", "weatheredChannels", "seaPort", 40 | 41 | "geothermalStronghold", "cruxscape", 42 | 43 | "onset", "aegis", "lake", "intersect", "basin", "atlas", "split", "marsh", "peaks", "ravine", 44 | "stronghold", "crevice", "siege", "crossroads", "karst", "origin" 45 | ); 46 | mapNames.addAll((String[])Reflect.get(maps.getClass(), "defaultMapNames")); 47 | Reflect.set(maps.getClass(), "defaultMapNames", mapNames.toArray(String.class)); 48 | } 49 | 50 | @Override 51 | public void init(){ 52 | TUIcons.init(); 53 | TUStyles.init(); 54 | TUSettings.init(); 55 | Setup.init(); 56 | 57 | LoadedMod tu = mods.locateMod("test-utils"); 58 | 59 | Func getModBundle = value -> bundle.get("mod." + value); 60 | 61 | tu.meta.displayName = "[#FCC21B]" + tu.meta.displayName; 62 | tu.meta.author = "[#FCC21B]" + tu.meta.author; 63 | 64 | StringBuilder tools = new StringBuilder(getModBundle.get(tu.meta.name + ".description")); 65 | tools.append("\n\n"); 66 | int i = 0; 67 | while(bundle.has("mod." + tu.meta.name + "-tool." + i)){ 68 | tools.append("\n ").append(getModBundle.get(tu.meta.name + "-tool." + i)); 69 | i++; 70 | } 71 | tu.meta.description = tools.toString(); 72 | 73 | setupZoom(); 74 | 75 | //Spawn position drawing and sk7725/whynotteleport. (Anything beyond here does not have mobile support.) 76 | if(mobile) return; 77 | Events.on(WorldLoadEvent.class, e -> { 78 | Spawn.spawnHover = Spawn.blockHover = false; 79 | }); 80 | Events.run(Trigger.draw, () -> { 81 | Draw.z(Layer.overlayUI + 0.04f); 82 | unitDialog.drawPos(); 83 | blockDialog.drawPos(); 84 | if(!teleport && canTeleport()){ 85 | Draw.z(Layer.effect); 86 | Lines.stroke(2f, Pal.accent); 87 | float x1 = player.x, y1 = player.y, 88 | x2 = input.mouseWorldX(), y2 = input.mouseWorldY(); 89 | 90 | Lines.line(x1, y1, x2, y2, false); 91 | Fill.circle(x1, y1, 1f); 92 | Fill.circle(x2, y2, 1f); 93 | 94 | for(int j = 0; j < 4; j++){ 95 | float rot = j * 90f + 45f + (-Time.time) % 360f; 96 | float length = 8f; 97 | Draw.rect("select-arrow", x2 + Angles.trnsx(rot, length), y2 + Angles.trnsy(rot, length), length / 1.9f, length / 1.9f, rot - 135f); 98 | } 99 | } 100 | Draw.reset(); 101 | }); 102 | Events.run(Trigger.update, () -> { 103 | if(state.isGame()){ 104 | //sk7725/whynotteleport 105 | if(canTeleport() && click()){ 106 | player.shooting(false); 107 | if(teleport) return; 108 | teleport = true; 109 | 110 | float oldX = player.x, oldY = player.y; 111 | 112 | player.unit().set(input.mouseWorld()); 113 | player.snapInterpolation(); 114 | 115 | TUFx.teleport.at( 116 | input.mouseWorldX(), input.mouseWorldY(), 117 | player.unit().rotation - 90f, player.team().color, 118 | new TPData(player.unit().type, oldX, oldY) 119 | ); 120 | }else{ 121 | teleport = false; 122 | } 123 | } 124 | }); 125 | } 126 | 127 | private static void setupZoom(){ 128 | if(settings.getBool("tu-disable-zoom", false)){ 129 | settings.put("tu-disable-zoom", false); 130 | return; 131 | } 132 | 133 | //Increase zoom range 134 | renderer.minZoom = 0.667f; //Zoom out farther 135 | renderer.maxZoom = 24f; //Zoom in closer 136 | Events.run(Trigger.update, () -> { 137 | if(state.isGame()){ 138 | if(control.input.logicCutscene){ //Dynamically change zoom range to not break cutscene zoom 139 | renderer.minZoom = 1.5f; 140 | renderer.maxZoom = 6f; 141 | }else{ 142 | renderer.minZoom = 0.667f; 143 | renderer.maxZoom = 24f; 144 | } 145 | } 146 | }); 147 | } 148 | 149 | public static boolean disableTeleport(){ 150 | return TUVars.foos || net.client() || disableCampaign(); 151 | } 152 | 153 | public static boolean canTeleport(){ 154 | return !mobile && !disableTeleport() && player.unit() != null && !player.unit().type.internal && input.alt(); 155 | } 156 | 157 | public static boolean disableCampaign(){ 158 | return state.isCampaign() && !(settings.getBool("tu-cheating")); 159 | } 160 | 161 | public static boolean click(){ 162 | return mobile ? input.isTouched() : input.keyDown(KeyCode.mouseLeft); 163 | } 164 | 165 | public static boolean anyClick(){ 166 | return mobile ? input.isTouched() : (input.keyDown(KeyCode.mouseLeft) || input.keyDown(KeyCode.mouseRight) || input.keyDown(KeyCode.mouseMiddle)); 167 | } 168 | 169 | public static KeyCode getClick(){ 170 | if(input.keyDown(KeyCode.mouseLeft)) return KeyCode.mouseLeft; 171 | if(input.keyDown(KeyCode.mouseRight)) return KeyCode.mouseRight; 172 | if(input.keyDown(KeyCode.mouseMiddle)) return KeyCode.mouseLeft; 173 | return null; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/testing/buttons/Console.java: -------------------------------------------------------------------------------- 1 | package testing.buttons; 2 | 3 | import arc.scene.style.*; 4 | import arc.scene.ui.*; 5 | import arc.scene.ui.layout.*; 6 | import blui.*; 7 | import blui.ui.*; 8 | import mindustry.*; 9 | import mindustry.gen.*; 10 | import testing.ui.*; 11 | 12 | public class Console{ 13 | static boolean shown; 14 | 15 | public static void addToggleButton(Table t){ 16 | Cell i = t.button(new TextureRegionDrawable(Icon.eye), TUStyles.tuImageStyle, BLVars.iconSize, () -> { 17 | shown = !shown; 18 | Vars.ui.consolefrag.visible(() -> shown); 19 | }); 20 | 21 | ImageButton b = i.get(); 22 | BLElements.boxTooltip(b, "@tu-tooltip.button-console-show"); 23 | } 24 | 25 | public static void addRefreshButton(Table t){ 26 | Cell i = t.button(new TextureRegionDrawable(Icon.trash), TUStyles.tuImageStyle, BLVars.iconSize, () -> Vars.ui.consolefrag.clearMessages()); 27 | 28 | ImageButton b = i.get(); 29 | BLElements.boxTooltip(b, "@tu-tooltip.button-console-clear"); 30 | 31 | } 32 | 33 | public static void addTerminalButton(Table t){ 34 | Cell i = t.button(new TextureRegionDrawable(Icon.terminal), TUStyles.tuImageStyle, BLVars.iconSize, () -> Vars.ui.consolefrag.toggle()); 35 | 36 | ImageButton b = i.get(); 37 | BLElements.boxTooltip(b, "@tu-tooltip.button-console-input"); 38 | 39 | } 40 | 41 | public static void addButtons(Table t){ 42 | addToggleButton(t); 43 | addRefreshButton(t); 44 | addTerminalButton(t); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/testing/buttons/Death.java: -------------------------------------------------------------------------------- 1 | package testing.buttons; 2 | 3 | import arc.graphics.*; 4 | import arc.graphics.g2d.*; 5 | import arc.scene.style.*; 6 | import arc.scene.ui.*; 7 | import arc.scene.ui.layout.*; 8 | import arc.util.*; 9 | import blui.*; 10 | import blui.scene.ui.*; 11 | import blui.ui.*; 12 | import mindustry.content.*; 13 | import mindustry.gen.*; 14 | import mindustry.type.*; 15 | import testing.content.*; 16 | import testing.ui.*; 17 | import testing.util.*; 18 | 19 | import static arc.Core.*; 20 | import static mindustry.Vars.*; 21 | 22 | public class Death{ 23 | /** SPONTANIUM COMBUSTUM! That's a spell that makes the person who said it e x p l o - */ 24 | public static void spontaniumCombustum(){ 25 | Unit u = player.unit(); 26 | if(u == null) return; 27 | if(settings.getBool("tu-instakill")){ 28 | u.destroy(); 29 | }else{ 30 | u.kill(); 31 | } 32 | killLightning(); 33 | } 34 | 35 | public static void killLightning(){ 36 | if(!settings.getBool("tu-death-effect", true)) return; 37 | 38 | Unit u = player.unit(); 39 | if(u != null){ 40 | Sounds.spark.at(u); 41 | for(int i = 0; i < Math.max(1f, u.hitSize / 4f); i++){ 42 | TUFx.deathLightning.at(u, true); 43 | } 44 | } 45 | } 46 | 47 | public static void mitosis(){ 48 | Unit u = player.unit(); 49 | if(u == null) return; 50 | u.type.spawn(u.team, u).rotation(u.rotation); 51 | Fx.spawn.at(u); 52 | } 53 | 54 | public static void seppuku(Table t){ 55 | HoldImageButton b = new HoldImageButton(Icon.units, TUStyles.tuHoldImageStyle); 56 | b.clicked(Death::spontaniumCombustum); 57 | b.held(Death::spontaniumCombustum); 58 | b.resizeImage(BLVars.iconSize); 59 | b.setRepeat(true); 60 | 61 | BLElements.boxTooltip(b, "@tu-tooltip.button-seppuku"); 62 | UnitStack kill = new UnitStack(TUIcons.seppuku); 63 | b.replaceImage(kill); 64 | 65 | b.setDisabled(() -> player.unit() == null || player.unit().type.internal); 66 | b.update(() -> { 67 | updateIcon(kill); 68 | kill.setColor(b.isDisabled() ? Color.gray : Color.white); 69 | }); 70 | 71 | t.add(b); 72 | } 73 | 74 | public static void clone(Table t){ 75 | HoldImageButton b = new HoldImageButton(Icon.units, TUStyles.tuHoldImageStyle); 76 | b.clicked(Death::mitosis); 77 | b.held(Death::mitosis); 78 | b.resizeImage(BLVars.iconSize); 79 | b.setRepeat(true); 80 | 81 | BLElements.boxTooltip(b, "@tu-tooltip.button-clone"); 82 | UnitStack dupe = new UnitStack(TUIcons.clone); 83 | b.replaceImage(dupe); 84 | 85 | b.setDisabled(() -> player.unit() == null || player.unit().type.internal); 86 | b.update(() -> { 87 | updateIcon(dupe); 88 | dupe.setColor(b.isDisabled() ? Color.gray : Color.white); 89 | }); 90 | 91 | t.add(b); 92 | } 93 | 94 | public static void addButtons(Table t){ 95 | clone(t); 96 | seppuku(t); 97 | } 98 | 99 | private static void updateIcon(UnitStack stack){ 100 | Unit u = player.unit(); 101 | if(u != null && u.type != stack.lastType){ 102 | if(u.type.internal){ 103 | stack.setImage(Icon.none); 104 | }else{ 105 | stack.setImage(u.type.uiIcon); 106 | } 107 | stack.lastType = u.type; 108 | } 109 | } 110 | 111 | private static class UnitStack extends Stack{ 112 | private final Image image, icon; 113 | public UnitType lastType; 114 | 115 | public UnitStack(TextureRegionDrawable icon){ 116 | image = new Image(UnitTypes.alpha.uiIcon).setScaling(Scaling.fit); 117 | add(image); 118 | 119 | this.icon = new Image(new TextureRegionDrawable(icon)).setScaling(Scaling.fit); 120 | add(this.icon); 121 | } 122 | 123 | public void setImage(TextureRegion unit){ 124 | image.setDrawable(new TextureRegionDrawable(unit)); 125 | } 126 | 127 | public void setImage(TextureRegionDrawable drawable){ 128 | image.setDrawable(drawable); 129 | } 130 | 131 | @Override 132 | public void setColor(Color color){ 133 | super.setColor(color); 134 | image.setColor(color); 135 | icon.setColor(color); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/testing/buttons/Effect.java: -------------------------------------------------------------------------------- 1 | package testing.buttons; 2 | 3 | import arc.scene.style.*; 4 | import arc.scene.ui.*; 5 | import arc.scene.ui.layout.*; 6 | import blui.ui.*; 7 | import testing.ui.*; 8 | 9 | import static testing.ui.TUDialogs.*; 10 | 11 | public class Effect{ 12 | public static void statusButton(Table t){ 13 | ImageButton b = new ImageButton(statusDialog.getStatus().uiIcon, TUStyles.tuImageStyle); 14 | BLElements.boxTooltip(b, "@tu-tooltip.button-status"); 15 | b.clicked(statusDialog::show); 16 | b.update(() -> { 17 | ((TextureRegionDrawable)(b.getStyle().imageUp)).setRegion(statusDialog.getStatus().uiIcon); 18 | }); 19 | 20 | t.add(b); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/testing/buttons/Environment.java: -------------------------------------------------------------------------------- 1 | package testing.buttons; 2 | 3 | import arc.scene.ui.*; 4 | import arc.scene.ui.layout.*; 5 | import blui.*; 6 | import blui.ui.*; 7 | import testing.ui.*; 8 | import testing.util.*; 9 | 10 | import static testing.ui.TUDialogs.*; 11 | 12 | public class Environment{ 13 | public static void worldButton(Table t){ 14 | ImageButton b = new ImageButton(TUIcons.weather, TUStyles.tuImageStyle); 15 | BLElements.boxTooltip(b, "@tu-tooltip.button-world"); 16 | b.clicked(worldDialog::show); 17 | b.resizeImage(BLVars.iconSize); 18 | 19 | t.add(b); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/testing/buttons/Health.java: -------------------------------------------------------------------------------- 1 | package testing.buttons; 2 | 3 | import arc.scene.ui.*; 4 | import arc.scene.ui.layout.*; 5 | import blui.*; 6 | import blui.ui.*; 7 | import mindustry.gen.*; 8 | import testing.ui.*; 9 | import testing.util.*; 10 | 11 | import static mindustry.Vars.*; 12 | 13 | public class Health{ 14 | public static void heal(boolean invincibility){ 15 | Unit u = player.unit(); 16 | if(u == null) return; 17 | if(u instanceof BlockUnitc bu){ 18 | Building b = bu.tile(); 19 | b.maxHealth(invincibility ? Float.POSITIVE_INFINITY : b.block.health); 20 | b.health = b.maxHealth; 21 | }else{ 22 | u.maxHealth(invincibility ? Float.POSITIVE_INFINITY : u.type.health); 23 | u.health = u.maxHealth; 24 | } 25 | } 26 | 27 | public static void healing(Table t){ 28 | Cell i = t.button(TUIcons.heal, TUStyles.tuImageStyle, BLVars.iconSize, () -> { 29 | heal(false); 30 | }).growX(); 31 | 32 | ImageButton b = i.get(); 33 | BLElements.boxTooltip(b, "@tu-tooltip.button-heal"); 34 | b.update(() -> { 35 | b.setColor(player.team().color != null ? player.team().color : TUVars.curTeam.color); 36 | }); 37 | 38 | } 39 | 40 | public static void invincibility(Table t){ 41 | Cell i = t.button(TUIcons.invincibility, TUStyles.tuImageStyle, BLVars.iconSize, () -> { 42 | heal(true); 43 | }).growX(); 44 | 45 | ImageButton b = i.get(); 46 | BLElements.boxTooltip(b, "@tu-tooltip.button-invincibility"); 47 | b.update(() -> { 48 | b.setColor(player.team().color != null ? player.team().color : TUVars.curTeam.color); 49 | }); 50 | 51 | } 52 | 53 | public static void addButtons(Table t){ 54 | healing(t); 55 | invincibility(t); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/testing/buttons/LightSwitch.java: -------------------------------------------------------------------------------- 1 | package testing.buttons; 2 | 3 | import arc.graphics.*; 4 | import arc.scene.ui.layout.*; 5 | import blui.*; 6 | import blui.scene.ui.*; 7 | import blui.ui.*; 8 | import testing.ui.*; 9 | import testing.util.*; 10 | 11 | import static mindustry.Vars.*; 12 | 13 | public class LightSwitch{ 14 | public static void lightButton(Table t){ 15 | HoldImageButton b = new HoldImageButton(TUIcons.lightOff, TUStyles.tuHoldImageStyle); 16 | b.clicked(() -> state.rules.lighting = !state.rules.lighting); 17 | b.held(() -> ui.picker.show(state.rules.ambientLight, true, res -> state.rules.ambientLight.set(res))); 18 | b.resizeImage(BLVars.iconSize); 19 | 20 | BLElements.boxTooltip(b, "@tu-tooltip.button-light"); 21 | b.getStyle().imageChecked = TUIcons.lightOn; 22 | b.getStyle().imageCheckedColor = new Color(); 23 | 24 | b.update(() -> { 25 | b.setChecked(state.rules.lighting); 26 | if(b.isChecked()){ 27 | b.getStyle().imageCheckedColor.set(state.rules.ambientLight).a(1); 28 | } 29 | }); 30 | 31 | t.add(b); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/testing/buttons/Sandbox.java: -------------------------------------------------------------------------------- 1 | package testing.buttons; 2 | 3 | import arc.scene.ui.*; 4 | import arc.scene.ui.layout.*; 5 | import blui.*; 6 | import blui.scene.ui.*; 7 | import blui.ui.*; 8 | import mindustry.world.blocks.storage.CoreBlock.*; 9 | import testing.ui.*; 10 | import testing.util.*; 11 | 12 | import static arc.Core.*; 13 | import static mindustry.Vars.*; 14 | 15 | public class Sandbox{ 16 | static boolean fill = true; 17 | 18 | public static void toggle(){ 19 | Utils.spawnIconEffect(state.rules.infiniteResources ? "survival" : "sandbox"); 20 | state.rules.infiniteResources = !state.rules.infiniteResources; 21 | } 22 | 23 | public static void coreItems(){ 24 | if(fill){ 25 | fillCore(); 26 | }else{ 27 | dumpCore(); 28 | } 29 | } 30 | 31 | public static void fillCore(){ 32 | CoreBuild core = player.core(); 33 | if(core != null){ 34 | content.items().each( 35 | i -> settings.getBool("tu-fill-all") || i.shownPlanets.contains(state.rules.planet), 36 | i -> core.items.set(i, core.storageCapacity) 37 | ); 38 | Utils.spawnIconEffect("core"); 39 | } 40 | } 41 | 42 | public static void dumpCore(){ 43 | if(player.core() != null){ 44 | player.core().items.clear(); 45 | Utils.spawnIconEffect("dump"); 46 | } 47 | } 48 | 49 | public static void toggling(Table t){ 50 | Cell i = t.button(TUIcons.survival, TUStyles.tuImageStyle, Sandbox::toggle); 51 | 52 | ImageButton b = i.get(); 53 | BLElements.boxTooltip(b, "@tu-tooltip.button-sandbox"); 54 | b.update(() -> { 55 | b.getStyle().imageUp = state.rules.infiniteResources ? TUIcons.survival : TUIcons.sandbox; 56 | }); 57 | 58 | } 59 | 60 | public static void filling(Table t){ 61 | HoldImageButton b = new HoldImageButton(TUIcons.core, TUStyles.tuHoldImageStyle); 62 | b.clicked(Sandbox::coreItems); 63 | b.held(() -> fill = !fill); 64 | b.resizeImage(BLVars.iconSize); 65 | 66 | BLElements.boxTooltip(b, "@tu-tooltip.button-fill"); 67 | b.update(() -> b.getStyle().imageUp = fill ? TUIcons.core : TUIcons.dump); 68 | 69 | t.add(b); 70 | } 71 | 72 | public static void addButtons(Table t){ 73 | toggling(t); 74 | filling(t); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/testing/buttons/Spawn.java: -------------------------------------------------------------------------------- 1 | package testing.buttons; 2 | 3 | import arc.scene.style.*; 4 | import arc.scene.ui.*; 5 | import arc.scene.ui.layout.*; 6 | import blui.*; 7 | import blui.ui.*; 8 | import mindustry.content.*; 9 | import testing.ui.*; 10 | 11 | import static mindustry.Vars.*; 12 | import static testing.ui.TUDialogs.*; 13 | 14 | public class Spawn{ 15 | public static boolean spawnHover, blockHover; 16 | 17 | public static void unitMenu(Table t){ 18 | ImageButton b = new ImageButton(unitDialog.getUnit().uiIcon, TUStyles.tuImageStyle); 19 | BLElements.boxTooltip(b, "@tu-tooltip.button-units"); 20 | b.clicked(unitDialog::show); 21 | b.resizeImage(BLVars.iconSize); 22 | b.update(() -> { 23 | ((TextureRegionDrawable)(b.getStyle().imageUp)).setRegion(unitDialog.getUnit().uiIcon); 24 | }); 25 | b.hovered(() -> spawnHover = true); 26 | b.exited(() -> spawnHover = false); 27 | 28 | t.add(b); 29 | } 30 | 31 | public static void blockMenu(Table t){ 32 | ImageButton b = new ImageButton(blockDialog.getBlock().uiIcon, TUStyles.tuImageStyle); 33 | BLElements.boxTooltip(b, "@tu-tooltip.button-block"); 34 | b.clicked(blockDialog::show); 35 | b.resizeImage(BLVars.iconSize); 36 | b.update(() -> { 37 | ((TextureRegionDrawable)(b.getStyle().imageUp)).setRegion((net.client() ? Blocks.coreShard : blockDialog.getBlock()).uiIcon); 38 | }); 39 | b.hovered(() -> blockHover = true); 40 | b.exited(() -> blockHover = false); 41 | 42 | t.add(b); 43 | } 44 | 45 | public static void addButtons(Table t){ 46 | unitMenu(t); 47 | blockMenu(t); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/testing/buttons/TeamChanger.java: -------------------------------------------------------------------------------- 1 | package testing.buttons; 2 | 3 | import arc.scene.ui.layout.*; 4 | import arc.util.*; 5 | import blui.scene.ui.*; 6 | import blui.ui.*; 7 | import mindustry.game.*; 8 | import mindustry.gen.*; 9 | import testing.ui.*; 10 | 11 | import static mindustry.Vars.*; 12 | import static testing.ui.TUDialogs.*; 13 | 14 | public class TeamChanger{ 15 | public static void changeTeam(Team team){ 16 | player.team(team); 17 | } 18 | 19 | public static Cell teamChanger(Table t){ 20 | return t.table(teams -> { 21 | BLElements.boxTooltip(teams, "@tu-tooltip.button-team"); 22 | int i = 0; 23 | for(Team team : Team.baseTeams){ 24 | HoldImageButton button = new HoldImageButton(Tex.whiteui, TUStyles.teamChanger); 25 | button.clicked(() -> changeTeam(team)); 26 | button.held(() -> teamDialog.show(curTeam(), TeamChanger::changeTeam)); 27 | 28 | button.getImageCell().scaling(Scaling.stretch).grow().color(team.color); 29 | button.update(() -> button.setChecked(player.team() == team)); 30 | 31 | teams.add(button).grow().center().margin(4f).color(Tmp.c1.set(team.color).mul(0.7f)); 32 | if(++i % 3 == 0){ 33 | teams.row(); 34 | } 35 | } 36 | }).grow(); 37 | } 38 | 39 | public static Team curTeam(){ 40 | return player.team(); 41 | } 42 | 43 | public static Team getNextTeam(){ 44 | if(player.team() == state.rules.defaultTeam){ 45 | return state.rules.waveTeam; 46 | }else{ 47 | return state.rules.defaultTeam; 48 | } 49 | } 50 | 51 | public static void addButton(Table t){ 52 | teamChanger(t).width(100); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/testing/content/TUFx.java: -------------------------------------------------------------------------------- 1 | package testing.content; 2 | 3 | import arc.*; 4 | import arc.graphics.*; 5 | import arc.graphics.g2d.*; 6 | import arc.math.*; 7 | import arc.util.*; 8 | import mindustry.entities.*; 9 | import mindustry.gen.*; 10 | import mindustry.graphics.*; 11 | import mindustry.type.*; 12 | 13 | import static arc.graphics.g2d.Draw.rect; 14 | import static arc.graphics.g2d.Draw.*; 15 | import static arc.graphics.g2d.Lines.*; 16 | 17 | public class TUFx{ 18 | private static final Rand rand = new Rand(); 19 | 20 | public static Effect 21 | 22 | iconEffect = new Effect(60f, e -> { 23 | if(e.data instanceof String s){ 24 | float rise = e.finpow() * 28f; 25 | float opacity = Mathf.curve(e.fin(), 0f, 0.2f) - Mathf.curve(e.fin(), 0.9f, 1f); 26 | alpha(opacity); 27 | rect(Core.atlas.find(s), e.x, e.y + rise); 28 | } 29 | }).layer(Layer.flyingUnit + 1), 30 | 31 | deathLightning = new Effect(20f, 300f, e -> { 32 | if(!(e.data instanceof Unit u)) return; 33 | rand.setSeed(e.id); 34 | Tmp.v1.setToRandomDirection(rand).setLength(u.hitSize * 0.75f * Mathf.sqrt(rand.random(1f))); 35 | Tmp.v2.trns(rand.random(-45f, 45f) + 90f, u.hitSize * (1f + rand.random(1f))); 36 | float tx = u.x + Tmp.v1.x, ty = u.y + Tmp.v1.y, 37 | ex = tx + Tmp.v2.x, ey = ty + Tmp.v2.y, 38 | dst = Mathf.dst(ex, ey, tx, ty); 39 | Tmp.v1.set(u).sub(ex, ey).nor(); 40 | 41 | float normx = Tmp.v1.x, normy = Tmp.v1.y; 42 | float range = 6f; 43 | int links = Mathf.ceil(dst / range); 44 | float spacing = dst / links; 45 | 46 | stroke(2.5f * e.fout()); 47 | color(Color.white, e.color, e.fin()); 48 | 49 | beginLine(); 50 | 51 | linePoint(ex, ey); 52 | 53 | rand.setSeed(e.id + 1L); 54 | 55 | for(int i = 0; i < links; i++){ 56 | float nx, ny; 57 | if(i == links - 1){ 58 | nx = tx; 59 | ny = ty; 60 | }else{ 61 | float len = (i + 1) * spacing; 62 | Tmp.v1.setToRandomDirection(rand).scl(range/2f); 63 | nx = ex + normx * len + Tmp.v1.x; 64 | ny = ey + normy * len + Tmp.v1.y; 65 | } 66 | 67 | Lines.linePoint(nx, ny); 68 | } 69 | 70 | Lines.endLine(); 71 | }).followParent(false), 72 | 73 | teleport = new Effect(100f, e -> { 74 | if(!(e.data instanceof TPData data)) return; 75 | 76 | //Copied from Fx.unitDespawn 77 | float scl = e.fout(Interp.pow2Out); 78 | float p = Draw.scl; 79 | Draw.scl *= scl; 80 | 81 | mixcol(e.color, 1f); 82 | rect(data.type.fullIcon, data.oldX, data.oldY, e.rotation); 83 | rect(data.type.fullIcon, e.x, e.y, e.rotation); 84 | reset(); 85 | 86 | Draw.scl = p; 87 | 88 | //Teleportation trail 89 | Draw.color(e.color); 90 | float stroke = data.type.hitSize / 4f * scl, 91 | angle = Angles.angle(data.oldX, data.oldY, e.x, e.y), 92 | cos = Mathf.cosDeg(angle + 90) * stroke, 93 | sin = Mathf.sinDeg(angle + 90) * stroke; 94 | 95 | Fill.quad( 96 | data.oldX + cos / 4f, data.oldY + sin / 4f, 97 | e.x + cos, e.y + sin, 98 | e.x - cos, e.y - sin, 99 | data.oldX - cos / 4f, data.oldY - sin / 4f 100 | ); 101 | }); 102 | 103 | public static class TPData{ 104 | public UnitType type; 105 | public float oldX, oldY; 106 | 107 | public TPData(UnitType type, float oldX, float oldY){ 108 | this.type = type; 109 | this.oldX = oldX; 110 | this.oldY = oldY; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/testing/dialogs/BlockDialog.java: -------------------------------------------------------------------------------- 1 | package testing.dialogs; 2 | 3 | import arc.*; 4 | import arc.graphics.*; 5 | import arc.graphics.g2d.*; 6 | import arc.input.*; 7 | import arc.math.*; 8 | import arc.math.geom.*; 9 | import arc.scene.event.*; 10 | import arc.scene.style.*; 11 | import arc.scene.ui.*; 12 | import arc.scene.ui.layout.*; 13 | import arc.struct.*; 14 | import arc.util.*; 15 | import blui.*; 16 | import blui.ui.*; 17 | import mindustry.content.*; 18 | import mindustry.core.*; 19 | import mindustry.game.EventType.*; 20 | import mindustry.game.*; 21 | import mindustry.gen.*; 22 | import mindustry.graphics.*; 23 | import mindustry.ui.*; 24 | import mindustry.world.*; 25 | import mindustry.world.blocks.*; 26 | import mindustry.world.blocks.environment.*; 27 | import mindustry.world.blocks.legacy.*; 28 | import testing.*; 29 | import testing.buttons.*; 30 | import testing.ui.*; 31 | import testing.util.*; 32 | 33 | import static arc.Core.*; 34 | import static mindustry.Vars.*; 35 | import static testing.ui.TUDialogs.*; 36 | 37 | public class BlockDialog extends TUBaseDialog{ 38 | private final Table selection = new Table(); 39 | private TextField search; 40 | private Block block = Blocks.coreShard; 41 | private Team placeTeam = Team.get(settings.getInt("tu-default-team", 1)); 42 | private int placePos = -1, rotation = 1; 43 | private boolean expectingPos, initialized; 44 | 45 | public BlockDialog(){ 46 | super("@tu-block-menu.name"); 47 | 48 | cont.table(s -> { 49 | s.image(Icon.zoom).padRight(8); 50 | search = s.field(null, text -> rebuild()).growX().get(); 51 | search.setMessageText("@players.search"); 52 | }).fillX().padBottom(4).row(); 53 | 54 | cont.label(() -> bundle.get("tu-menu.selection") + block.localizedName).padBottom(6).row(); 55 | 56 | cont.pane(all -> all.add(selection)).fillX().row(); 57 | 58 | cont.table(t -> { 59 | BLElements.imageButton( 60 | t, TUIcons.get(Icon.defense), TUStyles.lefti, BLVars.buttonSize, 61 | () -> teamDialog.show(placeTeam, team -> placeTeam = team), 62 | () -> bundle.format("tu-unit-menu.set-team", "[#" + placeTeam.color + "]" + teamName() + "[]"), 63 | "@tu-tooltip.block-set-team" 64 | ); 65 | 66 | BLElements.imageButton( 67 | t, TUIcons.get(Icon.map), TUStyles.toggleRighti, BLVars.buttonSize, 68 | () -> { 69 | hide(); 70 | expectingPos = true; 71 | }, 72 | () -> bundle.format("tu-unit-menu.pos", Point2.x(placePos), Point2.y(placePos)), 73 | "@tu-tooltip.block-pos" 74 | ); 75 | }).padTop(6).row(); 76 | 77 | cont.table(p -> { 78 | ImageButton rb = BLElements.imageButton( 79 | p, TUIcons.get(Icon.up), TUStyles.lefti, BLVars.buttonSize, 80 | () -> rotation = Mathf.mod(rotation - 1, 4), 81 | null, "@tu-tooltip.block-rotate" 82 | ).get(); 83 | rb.setDisabled(() -> !block.rotate); 84 | rb.update(() -> ((TextureRegionDrawable)(rb.getStyle().imageUp)).setRegion(getDirection())); 85 | 86 | ImageButton pb = BLElements.imageButton( 87 | p, new TextureRegionDrawable(block.uiIcon), TUStyles.centeri, BLVars.buttonSize, 88 | this::placeBlock, 89 | () -> "@tu-block-menu.place", 90 | "@tu-tooltip.block-place" 91 | ).get(); 92 | pb.setDisabled(() -> world.tile(placePos) == null); 93 | pb.update(() -> { 94 | ((TextureRegionDrawable)(pb.getStyle().imageUp)).setRegion(block.uiIcon); 95 | }); 96 | 97 | ImageButton cb = BLElements.imageButton( 98 | p, TUIcons.get(Icon.cancel), TUStyles.righti, BLVars.buttonSize, 99 | this::deleteBlock, 100 | () -> "@tu-block-menu.delete", 101 | "@tu-tooltip.block-delete" 102 | ).get(); 103 | }).padTop(6f).row(); 104 | 105 | ImageButton pb = BLElements.imageButton( 106 | cont, TUIcons.get(Icon.terrain), Styles.defaulti, BLVars.buttonSize, 107 | () -> { 108 | Setup.terrainFrag.show(); 109 | hide(); 110 | }, 111 | () -> "@tu-block-menu.open-painter", 112 | "@tu-tooltip.block-terrain-painter-open" 113 | ).get(); 114 | pb.setDisabled(() -> net.client() || Setup.terrainFrag.shown()); 115 | 116 | if(!initialized){ 117 | Events.on(WorldLoadEndEvent.class, e -> { 118 | placePos = Point2.pack(world.width() / 2, world.height() / 2); 119 | }); 120 | 121 | Events.run(Trigger.update, () -> { 122 | if(expectingPos){ 123 | if(!state.isGame()){ 124 | expectingPos = false; 125 | }else if(TestUtils.click()){ 126 | if(!Utils.hasMouse()){ 127 | int x = World.toTile(input.mouseWorldX()), 128 | y = World.toTile(input.mouseWorldY()); 129 | placePos = Point2.pack(x, y); 130 | ui.showInfoToast(bundle.format("tu-unit-menu.set-pos", x, y), 4f); 131 | show(); 132 | }else{ 133 | ui.showInfoToast("@tu-unit-menu.cancel", 4f); 134 | } 135 | expectingPos = false; 136 | } 137 | } 138 | }); 139 | initialized = true; 140 | } 141 | } 142 | 143 | public void drawPos(){ 144 | if(net.client()) return; 145 | float size = block.size * tilesize, 146 | offset = (1 - block.size % 2) * tilesize / 2f, 147 | x, y; 148 | if(expectingPos && state.isGame() && !Utils.hasMouse()){ 149 | x = World.toTile(input.mouseWorldX()) * tilesize; 150 | y = World.toTile(input.mouseWorldY()) * tilesize; 151 | }else if(Spawn.blockHover){ 152 | x = Point2.x(placePos) * tilesize; 153 | y = Point2.y(placePos) * tilesize; 154 | }else{ 155 | return; 156 | } 157 | Draw.z(Layer.overlayUI); 158 | Lines.stroke(1f, placeTeam.color); 159 | Lines.rect(x - size / 2 + offset, y - size / 2 + offset, size, size); 160 | Draw.rect(Icon.cancel.getRegion(), x, y, tilesize / 2f, tilesize / 2f); 161 | } 162 | 163 | @Override 164 | protected void rebuild(){ 165 | expectingPos = false; 166 | selection.clear(); 167 | String text = search.getText(); 168 | 169 | Seq array = content.blocks() 170 | .select(b -> !b.isFloor() && !b.isStatic() && 171 | !(b instanceof Prop) && 172 | !(b instanceof TallBlock) && 173 | !(b instanceof TreeBlock) && 174 | !(b instanceof ConstructBlock) && 175 | !(b instanceof LegacyBlock) && 176 | (!b.isHidden() || settings.getBool("tu-show-hidden")) && 177 | (text.isEmpty() || b.localizedName.toLowerCase().contains(text.toLowerCase()))); 178 | if(array.size == 0) return; 179 | 180 | selection.table(list -> { 181 | list.left(); 182 | 183 | float iconMul = 1.25f; 184 | int cols = (int)Mathf.clamp((graphics.getWidth() - Scl.scl(30)) / Scl.scl(32 + 10) / iconMul, 1, 22 / iconMul); 185 | int count = 0; 186 | 187 | for(Block b : array){ 188 | Image image = new Image(b.uiIcon).setScaling(Scaling.fit); 189 | list.add(image).size(8 * 4 * iconMul).pad(3); 190 | 191 | ClickListener listener = new ClickListener(); 192 | image.addListener(listener); 193 | if(!mobile){ 194 | image.addListener(new HandCursorListener()); 195 | image.update(() -> image.color.lerp(listener.isOver() || block == b ? Color.white : Color.lightGray, Mathf.clamp(0.4f * TUVars.delta()))); 196 | }else{ 197 | image.update(() -> image.color.lerp(block == b ? Color.white : Color.lightGray, Mathf.clamp(0.4f * TUVars.delta()))); 198 | } 199 | 200 | image.clicked(() -> { 201 | if(input.keyDown(KeyCode.shiftLeft) && Fonts.getUnicode(b.name) != 0){ 202 | app.setClipboardText((char)Fonts.getUnicode(b.name) + ""); 203 | ui.showInfoFade("@copied"); 204 | }else{ 205 | block = b; 206 | } 207 | }); 208 | BLElements.boxTooltip(image, b.localizedName + (settings.getBool("console") ? "\n[gray]" + b.name : "")); 209 | 210 | if((++count) % cols == 0){ 211 | list.row(); 212 | } 213 | } 214 | }).growX().left().padBottom(10); 215 | } 216 | 217 | TextureRegion getDirection(){ 218 | TextureRegionDrawable tex = switch(rotation){ 219 | case 1 -> Icon.up; 220 | case 2 -> Icon.left; 221 | case 3 -> Icon.down; 222 | default -> Icon.right; 223 | }; 224 | return tex.getRegion(); 225 | } 226 | 227 | void placeBlock(){ 228 | world.tile(placePos).setBlock(block, placeTeam, rotation); 229 | } 230 | 231 | void deleteBlock(){ 232 | world.tile(placePos).setAir(); 233 | } 234 | 235 | String teamName(){ 236 | return teamDialog.teamName(placeTeam); 237 | } 238 | 239 | public Block getBlock(){ 240 | return block; 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/testing/dialogs/InterpGraph.java: -------------------------------------------------------------------------------- 1 | package testing.dialogs; 2 | 3 | import arc.*; 4 | import arc.graphics.*; 5 | import arc.graphics.g2d.*; 6 | import arc.math.*; 7 | import arc.scene.ui.layout.*; 8 | import arc.util.*; 9 | import arc.util.pooling.*; 10 | import mindustry.gen.*; 11 | import mindustry.ui.*; 12 | import testing.util.*; 13 | 14 | public class InterpGraph extends Table{ 15 | private int points; 16 | private Interp oldInterp = Interp.linear; 17 | private Interp interp = Interp.linear; 18 | private float oldMinVal = 0f, oldMaxVal = 1f, 19 | minVal = 0f, maxVal = 1f; 20 | private float lerp = 1; 21 | 22 | public InterpGraph(){ 23 | background(Tex.pane); 24 | 25 | float dotColumnWidth = 40f; 26 | rect((x, y, width, height) -> { 27 | Lines.stroke(Scl.scl(3f)); 28 | 29 | GlyphLayout lay = Pools.obtain(GlyphLayout.class, GlyphLayout::new); 30 | Font font = Fonts.outline; 31 | 32 | lay.setText(font, "-0.00"); 33 | 34 | boolean interpColumn = width > 300 + dotColumnWidth; 35 | float min = min(), max = max(), range = max - min; 36 | float offsetX = Scl.scl(lay.width + 6f), offsetY = Scl.scl(5f); 37 | 38 | float graphX = x + offsetX, graphW = width - offsetX; 39 | float baseY = y + offsetY, baseH = height - offsetY; 40 | float graphY = baseY + baseH * (-min / range), graphH = baseH - baseH * ((-min + (max - 1)) / range); 41 | 42 | if(interpColumn) graphW -= dotColumnWidth; 43 | float colCenter = x + width - dotColumnWidth / 2f; 44 | 45 | points = Mathf.round(graphW / 4, 2) + 1; //Ensure a center (0.5) point 46 | float spacing = graphW / (points - 1); 47 | 48 | float tickMin = graphY; 49 | while(tickMin - (graphH / 4) > baseY){ 50 | tickMin -= graphH / 4; 51 | } 52 | 53 | Draw.color(Color.darkGray); 54 | float gTick = tickMin; 55 | while(gTick < baseY + baseH){ 56 | if(gTick != graphY && gTick != graphY + graphH) Lines.line(graphX, gTick, graphX + graphW, gTick); 57 | gTick += graphH / 4; 58 | } 59 | 60 | Draw.color(Color.lightGray); 61 | Lines.line(graphX, graphY, graphX + graphW, graphY); 62 | Lines.line(graphX, graphY + graphH, graphX + graphW, graphY + graphH); 63 | 64 | if(range != 1){ 65 | Lines.line(graphX, baseY, graphX + graphW, baseY); 66 | Lines.line(graphX, baseY + baseH, graphX + graphW, baseY + baseH); 67 | } 68 | 69 | if(interpColumn){ 70 | Draw.color(Color.darkGray); 71 | float colTick = tickMin; 72 | while(colTick < baseY + baseH){ 73 | if(colTick != graphY && colTick != graphY + graphH) Lines.lineAngleCenter(colCenter, colTick, 0, dotColumnWidth / 3f, false); 74 | colTick += graphH / 4; 75 | } 76 | 77 | Draw.color(Color.lightGray); 78 | Lines.lineAngleCenter(colCenter, graphY, 0, dotColumnWidth / 3f, false); 79 | Lines.lineAngleCenter(colCenter, graphY + graphH, 0, dotColumnWidth / 3f, false); 80 | 81 | if(range != 1){ 82 | Lines.lineAngleCenter(colCenter, baseY, 0, dotColumnWidth / 3f, false); 83 | Lines.lineAngleCenter(colCenter, baseY + baseH, 0, dotColumnWidth / 3f, false); 84 | } 85 | } 86 | 87 | Draw.color(Color.red); 88 | Lines.beginLine(); 89 | for(int i = 0; i < points; i++){ 90 | float a = i / (points - 1f); 91 | float cx = graphX + i * spacing, cy = graphY + applyInterp(a) * graphH; 92 | Lines.linePoint(cx, cy); 93 | } 94 | Lines.endLine(); 95 | 96 | float a = Time.globalTime % 180f / 180f; 97 | Fill.circle(graphX + graphW * a, graphY + applyInterp(a) * graphH, 4f); 98 | 99 | if(interpColumn){ 100 | Fill.circle(colCenter, graphY + applyInterp(a) * graphH, 4f); 101 | } 102 | 103 | lay.setText(font, "0.00"); 104 | font.draw("0.00", graphX, graphY + lay.height / 2f, Align.right); 105 | lay.setText(font, "1.00"); 106 | font.draw("1.00", graphX, graphY + graphH + lay.height / 2f, Align.right); 107 | 108 | if(range != 1){ 109 | String s = Strings.fixed(min, 2); 110 | lay.setText(font, s); 111 | font.draw(s, graphX, baseY + lay.height / 2f, Align.right); 112 | s = Strings.fixed(max, 2); 113 | lay.setText(font, s); 114 | font.draw(s, graphX, baseY + baseH + lay.height / 2f, Align.right); 115 | } 116 | 117 | font.setColor(Color.white); 118 | Pools.free(lay); 119 | 120 | Draw.reset(); 121 | }).pad(4).padBottom(10).grow(); 122 | 123 | update(() -> { 124 | if(lerp < 1){ 125 | float t = Core.settings.getInt("tu-lerp-time") / 4f * 60f; 126 | if(t <= 0){ 127 | lerp = 1; 128 | }else{ 129 | lerp = Mathf.clamp(lerp + (TUVars.delta()) / t); 130 | } 131 | } 132 | }); 133 | } 134 | 135 | float applyInterp(float a){ 136 | if(lerp >= 1){ 137 | return interp.apply(a); 138 | } 139 | return Mathf.lerp(oldInterp.apply(a), interp.apply(a), lerp()); 140 | } 141 | 142 | float min(){ 143 | if(lerp >= 1){ 144 | return minVal; 145 | } 146 | return Mathf.lerp(oldMinVal, minVal, lerp()); 147 | } 148 | 149 | float max(){ 150 | if(lerp >= 1){ 151 | return maxVal; 152 | } 153 | return Mathf.lerp(oldMaxVal, maxVal, lerp()); 154 | } 155 | 156 | float lerp(){ 157 | return Interp.smoother.apply(lerp); 158 | } 159 | 160 | public void setInterp(Interp newInterp){ 161 | if(lerp < 1){ 162 | Interp o = oldInterp; 163 | Interp i = interp; 164 | float l = Interp.smoother.apply(lerp); 165 | 166 | oldInterp = a -> Mathf.lerp(o.apply(a), i.apply(a), l); 167 | 168 | oldMinVal = min(); 169 | oldMaxVal = max(); 170 | }else{ 171 | oldInterp = interp; 172 | oldMinVal = minVal; 173 | oldMaxVal = maxVal; 174 | } 175 | 176 | interp = newInterp; 177 | lerp = 0; 178 | 179 | minVal = Float.MAX_VALUE; 180 | maxVal = Float.MIN_VALUE; 181 | for(int i = 0; i < points; i++){ 182 | float v = newInterp.apply(i / (points - 1f)); 183 | if(v < minVal){ 184 | minVal = v; 185 | } 186 | if(v > maxVal){ 187 | maxVal = v; 188 | } 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/testing/dialogs/StatusDialog.java: -------------------------------------------------------------------------------- 1 | package testing.dialogs; 2 | 3 | import arc.graphics.*; 4 | import arc.input.*; 5 | import arc.math.*; 6 | import arc.scene.event.*; 7 | import arc.scene.ui.*; 8 | import arc.scene.ui.TextField.*; 9 | import arc.scene.ui.layout.*; 10 | import arc.struct.*; 11 | import arc.util.*; 12 | import blui.*; 13 | import blui.ui.*; 14 | import mindustry.content.*; 15 | import mindustry.gen.*; 16 | import mindustry.type.*; 17 | import mindustry.ui.*; 18 | import testing.ui.*; 19 | import testing.util.*; 20 | 21 | import static arc.Core.*; 22 | import static mindustry.Vars.*; 23 | 24 | public class StatusDialog extends TUBaseDialog{ 25 | private static final float minDur = 0.125f, maxDur = 60f; 26 | private final Table selection = new Table(); 27 | private TextField search; 28 | private StatusEffect status = StatusEffects.burning; 29 | private float duration = 10f; 30 | private boolean perma; 31 | 32 | public StatusDialog(){ 33 | super("@tu-status-menu.name"); 34 | 35 | perma = settings.getBool("tu-permanent", false); 36 | 37 | cont.table(s -> { 38 | s.image(Icon.zoom).padRight(8); 39 | search = s.field(null, text -> rebuild()).growX().get(); 40 | search.setMessageText("@players.search"); 41 | }).fillX().padBottom(4).row(); 42 | 43 | cont.pane(all -> all.add(selection)).row(); 44 | cont.table(null, b -> { 45 | b.table(sl -> { 46 | sl.collapser(s -> BLElements.sliderSet( 47 | s, text -> duration = Strings.parseFloat(text), () -> String.valueOf(duration), 48 | TextFieldFilter.floatsOnly, Strings::canParsePositiveFloat, 49 | minDur, maxDur, 0.125f, duration, (n, f) -> { 50 | duration = n; 51 | f.setText(String.valueOf(n)); 52 | }, 53 | "@tu-status-menu.duration", 54 | "@tu-tooltip.status-duration" 55 | ), true, () -> !perma && !status.permanent).bottom().get().setDuration(0.06f); 56 | }).colspan(2); 57 | b.row(); 58 | ImageButton ab = b.button(TUIcons.get(Icon.add), TUStyles.lefti, BLVars.buttonSize, this::apply) 59 | .wrapLabel(false).right().get(); 60 | BLElements.boxTooltip(ab, "@tu-tooltip.status-apply"); 61 | ab.label(() -> "@tu-status-menu.apply").padLeft(6); 62 | 63 | ImageButton pb = b.button(TUIcons.get(Icon.refresh), TUStyles.toggleRighti, BLVars.buttonSize, () -> perma = !perma) 64 | .wrapLabel(false).left().get(); 65 | BLElements.boxTooltip(pb, "@tu-tooltip.status-perma"); 66 | Label pl = pb.label(() -> "@tu-status-menu.perma").padLeft(6).get(); 67 | pb.setDisabled(() -> status.permanent); 68 | pb.update(() -> { 69 | pb.setChecked(perma); 70 | pl.setColor(pb.isDisabled() ? Color.gray : Color.white); 71 | }); 72 | }).padTop(6); 73 | 74 | BLElements.boxTooltip( 75 | buttons.button("$tu-status-menu.clear", Icon.cancel, this::clearStatus).get(), 76 | "@tu-tooltip.status-clear" 77 | ); 78 | } 79 | 80 | @Override 81 | protected void rebuild(){ 82 | selection.clear(); 83 | String text = search.getText(); 84 | 85 | selection.label( 86 | () -> bundle.get("tu-menu.selection") + "[#" + status.color + "]" + 87 | status.localizedName + 88 | (status.permanent ? bundle.get("tu-status-menu.permaeff") : "") 89 | ).padBottom(6); 90 | selection.row(); 91 | 92 | Seq array = content.statusEffects().select(e -> e != StatusEffects.none && (text.isEmpty() || e.localizedName.toLowerCase().contains(text.toLowerCase()))); 93 | selection.table(list -> { 94 | list.left(); 95 | 96 | float iconMul = 1.5f; 97 | int cols = (int)Mathf.clamp((graphics.getWidth() - Scl.scl(30)) / Scl.scl(32 + 10) / iconMul, 1, 22 / iconMul); 98 | int count = 0; 99 | 100 | for(StatusEffect s : array){ 101 | Image image = new Image(s.uiIcon).setScaling(Scaling.fit); 102 | list.add(image).size(8 * 4 * iconMul).pad(3); 103 | 104 | ClickListener listener = new ClickListener(); 105 | image.addListener(listener); 106 | if(!mobile){ 107 | image.addListener(new HandCursorListener()); 108 | image.update(() -> image.color.lerp(listener.isOver() || status == s ? Color.white : Color.lightGray, Mathf.clamp(0.4f * TUVars.delta()))); 109 | }else{ 110 | image.update(() -> image.color.lerp(status == s ? Color.white : Color.lightGray, Mathf.clamp(0.4f * TUVars.delta()))); 111 | } 112 | 113 | image.clicked(() -> { 114 | if(input.keyDown(KeyCode.shiftLeft) && Fonts.getUnicode(s.name) != 0){ 115 | app.setClipboardText((char)Fonts.getUnicode(s.name) + ""); 116 | ui.showInfoFade("@copied"); 117 | }else{ 118 | status = s; 119 | } 120 | }); 121 | BLElements.boxTooltip(image, s.localizedName + (settings.getBool("console") ? "\n[gray]" + s.name : "")); 122 | 123 | if((++count) % cols == 0){ 124 | list.row(); 125 | } 126 | } 127 | }).growX().left().padBottom(10); 128 | } 129 | 130 | void apply(){ 131 | Unit u = player.unit(); 132 | if(u == null) return; 133 | u.apply(status, perma ? Float.MAX_VALUE : duration * 60); 134 | } 135 | 136 | void clearStatus(){ 137 | Unit u = player.unit(); 138 | if(u == null) return; 139 | u.clearStatuses(); 140 | } 141 | 142 | public StatusEffect getStatus(){ 143 | return status; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/testing/dialogs/TUBaseDialog.java: -------------------------------------------------------------------------------- 1 | package testing.dialogs; 2 | 3 | import arc.*; 4 | import mindustry.game.EventType.*; 5 | import mindustry.ui.dialogs.*; 6 | import testing.util.*; 7 | 8 | public class TUBaseDialog extends BaseDialog{ 9 | public TUBaseDialog(String title){ 10 | super(title); 11 | 12 | shouldPause = false; 13 | addCloseButton(); 14 | shown(this::rebuild); 15 | onResize(this::rebuild); 16 | shown(() -> { 17 | TUVars.activeDialog = this; 18 | }); 19 | 20 | Events.on(GameOverEvent.class, e -> hide()); 21 | } 22 | 23 | protected void rebuild(){ 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/testing/dialogs/TeamDialog.java: -------------------------------------------------------------------------------- 1 | package testing.dialogs; 2 | 3 | import arc.func.*; 4 | import arc.graphics.*; 5 | import arc.math.*; 6 | import arc.scene.event.*; 7 | import arc.scene.ui.*; 8 | import arc.scene.ui.TextField.*; 9 | import arc.scene.ui.layout.*; 10 | import arc.util.*; 11 | import blui.*; 12 | import blui.ui.*; 13 | import mindustry.game.*; 14 | import mindustry.graphics.*; 15 | import testing.util.*; 16 | 17 | import static arc.Core.*; 18 | import static mindustry.Vars.*; 19 | 20 | public class TeamDialog extends TUBaseDialog{ 21 | private final Table all = new Table(); 22 | private Team curTeam; 23 | private Cons changed; 24 | 25 | public TeamDialog(){ 26 | super("@tu-unit-menu.team"); 27 | 28 | all.margin(20).marginTop(0f); 29 | 30 | cont.pane(all); 31 | } 32 | 33 | public void show(Team startTeam, Cons changed){ 34 | curTeam = startTeam; 35 | this.changed = changed; 36 | show(); 37 | } 38 | 39 | @Override 40 | protected void rebuild(){ 41 | all.clear(); 42 | 43 | all.table(t -> { 44 | t.add("@tu-unit-menu.teams-id").right().color(Pal.accent); 45 | 46 | TextField tField = BLElements.textField( 47 | String.valueOf(curTeam.id), 48 | text -> { 49 | Team team = Team.get(Strings.parseInt(text)); 50 | changed.get(team); 51 | curTeam = team; 52 | }, 53 | () -> String.valueOf(curTeam.id), 54 | TextFieldFilter.digitsOnly, 55 | Strings::canParsePositiveInt 56 | ); 57 | t.add(tField).left().padLeft(6).width(BLVars.fieldWidth); 58 | }).left().padBottom(6); 59 | all.row(); 60 | 61 | all.add("@tu-unit-menu.teams-main").growX().left().color(Pal.accent); 62 | all.row(); 63 | all.image().growX().pad(5).padLeft(0).padRight(0).height(3).color(Pal.accent); 64 | all.row(); 65 | all.table(t -> { 66 | float iconMul = 2f; 67 | int cols = (int)Mathf.clamp((graphics.getWidth() - Scl.scl(30)) / Scl.scl(32 + 10) / iconMul, 1, 6); 68 | int count = 0; 69 | for(Team team : Team.baseTeams){ 70 | addButton(t, team, 8 * 4 * iconMul); 71 | if((++count) % cols == 0){ 72 | t.row(); 73 | } 74 | } 75 | }); 76 | all.row(); 77 | 78 | all.add("@tu-unit-menu.teams-other").growX().left().color(Pal.accent); 79 | all.row(); 80 | all.image().growX().pad(5).padLeft(0).padRight(0).height(3).color(Pal.accent); 81 | all.row(); 82 | all.table(t -> { 83 | float iconMul = 1.5f; 84 | int cols = (int)Mathf.clamp((graphics.getWidth() - Scl.scl(30)) / Scl.scl(32 + 10) / iconMul, 1, 22 / iconMul); 85 | int count = 0; 86 | for(int i = 6; i < Team.all.length; i++){ 87 | addButton(t, Team.get(i), 8 * 4 * iconMul); 88 | if((++count) % cols == 0){ 89 | t.row(); 90 | } 91 | } 92 | }); 93 | } 94 | 95 | void addButton(Table t, Team team, float size){ 96 | Image image = t.image().size(size).pad(3).color(team.color).get(); 97 | 98 | ClickListener listener = new ClickListener(); 99 | image.addListener(listener); 100 | if(!mobile){ 101 | image.addListener(new HandCursorListener()); 102 | Color lerpColor = team.color.cpy().lerp(Color.white, 0.5f); 103 | image.update(() -> image.color.lerp(!listener.isOver() ? team.color : lerpColor, Mathf.clamp(0.4f * TUVars.delta()))); 104 | } 105 | 106 | image.clicked(() -> { 107 | changed.get(team); 108 | curTeam = team; 109 | hide(); 110 | }); 111 | BLElements.boxTooltip(image, () -> teamName(team)); 112 | } 113 | 114 | public String teamName(Team team){ 115 | return bundle.has("team." + team.name + ".name") ? bundle.get("team." + team.name + ".name") : String.valueOf(team.id); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/testing/dialogs/WaveChangeDialog.java: -------------------------------------------------------------------------------- 1 | package testing.dialogs; 2 | 3 | import arc.scene.style.*; 4 | import arc.scene.ui.*; 5 | import arc.scene.ui.TextField.*; 6 | import arc.scene.ui.layout.*; 7 | import arc.util.*; 8 | import blui.*; 9 | import blui.ui.*; 10 | import mindustry.content.*; 11 | import mindustry.game.*; 12 | import mindustry.gen.*; 13 | import mindustry.graphics.*; 14 | import testing.util.*; 15 | 16 | import static arc.Core.*; 17 | import static mindustry.Vars.*; 18 | 19 | public class WaveChangeDialog extends TUBaseDialog{ 20 | private final Table all = new Table(); 21 | private static int amountCounter; 22 | private int startWave = 1, waves = 50; 23 | 24 | public WaveChangeDialog(){ 25 | super("@tu-unit-menu.waves"); 26 | 27 | all.margin(20).marginTop(0f); 28 | 29 | cont.add("@tu-unit-menu.wave-range").right(); 30 | cont.table(w -> { 31 | w.add("@tu-unit-menu.wave-start").left(); 32 | TextField minField = BLElements.textField( 33 | String.valueOf(startWave), 34 | text -> { 35 | startWave = Math.max(Strings.parseInt(text), 1); 36 | rebuild(); 37 | }, 38 | () -> String.valueOf(startWave), 39 | TextFieldFilter.digitsOnly, 40 | Strings::canParsePositiveInt 41 | ); 42 | w.add(minField).left().padLeft(6).width(BLVars.fieldWidth); 43 | w.row(); 44 | 45 | w.add("@tu-unit-menu.wave-waves").left(); 46 | TextField maxField = BLElements.textField( 47 | String.valueOf(waves), 48 | text -> { 49 | waves = Math.max(Strings.parseInt(text), 1); 50 | rebuild(); 51 | }, 52 | () -> String.valueOf(waves), 53 | TextFieldFilter.digitsOnly, 54 | Strings::canParsePositiveInt 55 | ); 56 | w.add(maxField).left().padLeft(6).width(BLVars.fieldWidth); 57 | }).left(); 58 | cont.row(); 59 | cont.label(() -> bundle.format("tu-unit-menu.wave-current", state.wave)).colspan(2); 60 | cont.row(); 61 | cont.pane(all).colspan(2); 62 | 63 | BLElements.boxTooltip( 64 | buttons.button("@tu-unit-menu.wave-send", Icon.upload, this::sendWave).get(), 65 | "@tu-tooltip.unit-send-wave" 66 | ); 67 | } 68 | 69 | @Override 70 | protected void rebuild(){ 71 | title.setText(bundle.format("tu-unit-menu.waves-menu", state.map.name())); 72 | 73 | all.clear(); 74 | 75 | float iconMul = 2f; 76 | all.table(t -> { 77 | for(int i = startWave; i < startWave + waves; i++){ 78 | int ii = i; 79 | TextButton b = t.button(String.valueOf(i), () -> setWave(ii)).right().grow().padRight(6f).padTop(4f).get(); 80 | b.getLabel().setWrap(false); 81 | b.getLabelCell().center(); 82 | b.setDisabled(() -> net.client()); 83 | 84 | amountCounter = 0; 85 | t.table(w -> { 86 | int wave = ii - 1; 87 | for(SpawnGroup group : state.rules.spawns){ 88 | if(group.getSpawned(wave) <= 0) continue; 89 | w.table(u -> { 90 | int a = group.getSpawned(wave) * Utils.countSpawns(group); 91 | u.add(BLElements.itemImage( 92 | new TextureRegionDrawable(group.type.uiIcon), 93 | () -> String.valueOf(a) 94 | )).size(8 * 4 * iconMul).top().grow(); 95 | amountCounter += a; 96 | boolean hasEffect = group.effect != null && group.effect != StatusEffects.none, 97 | hasShield = group.getShield(wave) > 0; 98 | if(hasEffect || hasShield){ 99 | u.row(); 100 | u.table(e -> { 101 | if(hasEffect){ 102 | e.add(new Image(group.effect.uiIcon).setScaling(Scaling.fit)).size(8 * 2 * iconMul); 103 | } 104 | if(hasShield){ 105 | e.add(BLElements.itemImage( 106 | Icon.defense, 107 | () -> Utils.round(group.getShield(wave)), 108 | Pal.accentBack, 109 | Pal.accent, 110 | 1f, 111 | Align.center 112 | )).size(8 * 2 * iconMul).growX(); 113 | } 114 | }).top().grow(); 115 | } 116 | }).grow(); 117 | } 118 | }).growY().left().padTop(4f); 119 | t.label(() -> bundle.format("tu-unit-menu.wave-total", amountCounter)).grow().left().padLeft(6f).padTop(4f); 120 | t.row(); 121 | } 122 | }); 123 | } 124 | 125 | void sendWave(){ 126 | logic.runWave(); 127 | } 128 | 129 | void setWave(int wave){ 130 | state.wave = wave; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/testing/dialogs/sound/FilterTable.java: -------------------------------------------------------------------------------- 1 | package testing.dialogs.sound; 2 | 3 | import arc.scene.ui.*; 4 | import arc.scene.ui.layout.*; 5 | import mindustry.ui.*; 6 | import testing.dialogs.sound.TUFilters.*; 7 | 8 | public class FilterTable extends Table{ 9 | private final Table config = new Table(); 10 | private FilterModule lastFilter; 11 | 12 | public FilterTable(){ 13 | TUFilters.init(); 14 | table(sel -> { 15 | int col = 0; 16 | for(int i = 0; i < TUFilters.filters.length; i++){ 17 | FilterModule fm = TUFilters.filters[i]; 18 | col++; 19 | TextButton tb = sel.button("@tu-filters-" + fm.name, () -> setConfig(fm)) 20 | .checked(t -> fm == lastFilter) 21 | .wrapLabel(false).uniform().grow().get(); 22 | tb.setStyle(Styles.togglet); 23 | tb.check("", fm::enable).get().setChecked(fm.enabled); 24 | tb.getCells().reverse(); 25 | 26 | if(col == 4){ 27 | sel.row(); 28 | col = 0; 29 | } 30 | } 31 | }).fillX().row(); 32 | 33 | add(config).padTop(6); 34 | 35 | setConfig(TUFilters.filters[0]); 36 | } 37 | 38 | public void shown(){ 39 | setConfig(lastFilter); 40 | } 41 | 42 | private void setConfig(FilterModule fm){ 43 | config.clear(); 44 | lastFilter = fm; 45 | config.table(t -> { 46 | t.defaults().left().top(); 47 | fm.buildUI(t); 48 | }).growX().top(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/testing/dialogs/sound/LoadedSounds.java: -------------------------------------------------------------------------------- 1 | package testing.dialogs.sound; 2 | 3 | import arc.audio.*; 4 | import arc.files.*; 5 | import arc.struct.*; 6 | import arc.util.*; 7 | import mindustry.*; 8 | import mindustry.gen.*; 9 | 10 | import static arc.Core.settings; 11 | import static mindustry.Vars.*; 12 | 13 | public class LoadedSounds{ 14 | protected static final Seq vanillaMusic = Seq.with( 15 | Musics.menu, Musics.launch, Musics.land, Musics.editor, 16 | Musics.game1, Musics.game2, Musics.game3, Musics.game4, Musics.fine, 17 | Musics.game5, Musics.game6, Musics.game7, Musics.game8, Musics.game9, 18 | Musics.boss1, Musics.boss2 19 | ); 20 | 21 | protected static Seq vanillaSounds; 22 | protected static Seq modSounds; 23 | protected static ObjectMap soundOverrides; 24 | protected static ObjectMap soundMods; 25 | 26 | protected static Seq modMusic; 27 | protected static ObjectMap musicOverrides; 28 | protected static ObjectMap musicMods; 29 | 30 | protected static void init(){ 31 | if(vanillaSounds != null) return; 32 | 33 | vanillaSounds = new Seq<>(); 34 | int i = 0; 35 | while(true){ //Put vanilla sounds first 36 | Sound found = Sounds.getSound(i); 37 | if(found == null || found == Sounds.none) break; 38 | 39 | vanillaSounds.addUnique(found); 40 | i++; 41 | } 42 | 43 | modSounds = new Seq<>(); 44 | soundOverrides = new ObjectMap<>(); 45 | soundMods = new ObjectMap<>(); 46 | 47 | String sDir = "sounds/"; 48 | Vars.mods.eachEnabled(m -> { 49 | Fi musicFolder = m.root.child("sounds"); 50 | String mName = m.meta.displayName; 51 | if(musicFolder.exists() && musicFolder.isDirectory()){ 52 | musicFolder.walk(f -> { 53 | String ext = f.extension(); 54 | if(ext.equals("mp3") || ext.equals("ogg")){ 55 | //Check for override 56 | int vanillaIndex = vanillaSounds.indexOf(s -> getSoundName(s).equals(f.nameWithoutExtension())); 57 | if(vanillaIndex != -1){ 58 | Sound overwritten = vanillaSounds.get(vanillaIndex); 59 | modSounds.addUnique(overwritten); 60 | soundOverrides.put(overwritten, mName); 61 | soundMods.put(overwritten, mName); 62 | }else{ //Add 63 | String path = f.pathWithoutExtension(); 64 | int folderIndex = f.pathWithoutExtension().indexOf(sDir); 65 | String loc = path.substring(folderIndex + sDir.length()); 66 | //assets.getAssetType(loc) seems to always be null for some reason. Use a try/catch instead I guess. 67 | try{ //Prevent crash if non-Sound file is in sounds/ 68 | Sound sou = tree.loadSound(loc); 69 | modSounds.addUnique(sou); 70 | soundMods.put(sou, mName); 71 | }catch(Exception ignored){ 72 | Log.warn("[TU] File @ is not a Sound", path); 73 | } 74 | } 75 | } 76 | }); 77 | } 78 | }); 79 | modSounds.sort(Structs.comparing(o -> soundMods.get(o))); 80 | 81 | if(!settings.getBool("tu-music-enabled", false)) return; 82 | 83 | //For some reason modded music is not included in assets.getAll. Walk through mod files instead. 84 | modMusic = new Seq<>(); 85 | musicOverrides = new ObjectMap<>(); 86 | musicMods = new ObjectMap<>(); 87 | 88 | String mDir = "music/"; 89 | Vars.mods.eachEnabled(m -> { 90 | Fi musicFolder = m.root.child("music"); 91 | String mName = m.meta.displayName; 92 | if(musicFolder.exists() && musicFolder.isDirectory()){ 93 | musicFolder.walk(f -> { 94 | String ext = f.extension(); 95 | if(ext.equals("mp3") || ext.equals("ogg")){ 96 | //Check for override 97 | int vanillaIndex = vanillaMusic.indexOf(s -> getMusicName(s).equals(f.nameWithoutExtension())); 98 | if(vanillaIndex != -1){ 99 | Music overwritten = vanillaMusic.get(vanillaIndex); 100 | modMusic.addUnique(overwritten); 101 | musicOverrides.put(overwritten, mName); 102 | musicMods.put(overwritten, mName); 103 | }else{ //Add 104 | String path = f.pathWithoutExtension(); 105 | int folderIndex = f.pathWithoutExtension().indexOf(mDir); 106 | String loc = path.substring(folderIndex + mDir.length()); 107 | try{ //Why a non-music file would be in musics is beyond me, but just in case. 108 | Music mus = tree.loadMusic(loc); 109 | modMusic.addUnique(mus); 110 | musicMods.put(mus, mName); 111 | }catch(Exception ignored){ 112 | Log.warn("[TU] File @ is not a Music", path); 113 | } 114 | } 115 | } 116 | }); 117 | } 118 | }); 119 | modMusic.sort(Structs.comparing(o -> musicMods.get(o))); 120 | } 121 | 122 | protected static String getSoundName(Sound s){ 123 | String full = s.toString(); 124 | return full.substring(full.lastIndexOf("/") + 1, full.length() - 4); 125 | } 126 | 127 | protected static String getMusicName(Music s){ 128 | if(s == null) return "none"; 129 | String full = s.toString(); 130 | return full.substring(full.lastIndexOf("/") + 1, full.length() - 4); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/testing/dialogs/sound/MusicsTable.java: -------------------------------------------------------------------------------- 1 | package testing.dialogs.sound; 2 | 3 | import arc.*; 4 | import arc.audio.*; 5 | import arc.graphics.*; 6 | import arc.graphics.g2d.*; 7 | import arc.input.*; 8 | import arc.math.*; 9 | import arc.scene.*; 10 | import arc.scene.event.*; 11 | import arc.scene.ui.*; 12 | import arc.scene.ui.layout.*; 13 | import arc.struct.*; 14 | import arc.util.*; 15 | import blui.ui.*; 16 | import mindustry.*; 17 | import mindustry.core.*; 18 | import mindustry.gen.*; 19 | import mindustry.graphics.*; 20 | import testing.ui.*; 21 | 22 | import static arc.Core.*; 23 | import static testing.dialogs.sound.LoadedSounds.*; 24 | 25 | public class MusicsTable extends STable{ 26 | private final Table selection = new Table(); 27 | private TextField search; 28 | private MusicProgressBar progressBar; 29 | private boolean paused; 30 | private float targetTime = 0f; 31 | private boolean queued = false; 32 | private Music selectedMusic = Musics.menu; 33 | protected Music playingMusic = null; 34 | 35 | public MusicsTable(){} 36 | 37 | public void createSelection(Table t, TextField search){ 38 | this.search = search; 39 | 40 | t.label(() -> bundle.get("tu-menu.selection") + getMusicName(selectedMusic)).padBottom(6).left().row(); 41 | 42 | t.pane(all -> all.add(selection).growX()); 43 | 44 | rebuild(); 45 | } 46 | 47 | public void createPlay(Table t){ 48 | BLElements.divider(t, "@tu-sound-menu.music", Pal.accent); 49 | t.table(s -> { 50 | s.label(() -> Core.bundle.format("tu-sound-menu.now-playing", getMusicName(playingMusic))).left().colspan(3).padBottom(6f); 51 | s.row(); 52 | s.button("@tu-sound-menu.switch", () -> switchMusic(selectedMusic)).wrapLabel(false) 53 | .left().colspan(3).disabled(b -> selectedMusic == playingMusic).get().setStyle(TUStyles.round); 54 | s.row(); 55 | s.add(progressBar = new MusicProgressBar(this)).growX(); 56 | s.button(Icon.play, () -> play(playingMusic)).disabled(b -> !paused); 57 | s.button(Icon.pause, this::pause).disabled(b -> paused); 58 | }).growX(); 59 | } 60 | 61 | public void rebuild(){ 62 | selection.clear(); 63 | String text = search.getText(); 64 | 65 | selection.table(list -> { 66 | Seq vSounds = vanillaMusic.select(s -> getMusicName(s).toLowerCase().contains(text.toLowerCase())); 67 | if(vSounds.size > 0){ 68 | BLElements.divider(list, "@tu-sound-menu.vanilla", Pal.accent); 69 | 70 | list.table(v -> vanillaMusicList(v, vSounds)).growX(); 71 | list.row(); 72 | } 73 | 74 | Seq mSounds = modMusic.select(s -> getMusicName(s).toLowerCase().contains(text.toLowerCase())); 75 | if(mSounds.size > 0){ 76 | BLElements.divider(list, "@tu-sound-menu.modded", Pal.accent); 77 | 78 | list.table(m -> modMusicList(m, mSounds)).growX(); 79 | } 80 | }).growX().padBottom(10); 81 | } 82 | 83 | public void vanillaMusicList(Table t, Seq musics){ 84 | int cols = 4; 85 | int count = 0; 86 | for(Music m : musics){ 87 | TextButton mb = t.button(getMusicName(m), () -> selectedMusic = m) 88 | .uniformX().grow().checked(b -> selectedMusic == m).get(); 89 | mb.setStyle(TUStyles.toggleCentert); 90 | 91 | if(musicOverrides.containsKey(m)){ 92 | mb.setDisabled(true); 93 | BLElements.boxTooltip(mb, bundle.format("tu-sound-menu.music-overwritten", musicOverrides.get(m))); 94 | } 95 | 96 | if((++count) % cols == 0){ 97 | t.row(); 98 | } 99 | } 100 | } 101 | 102 | public void modMusicList(Table t, Seq musics){ 103 | int cols = 4; 104 | int count = 0; 105 | String lastMod = null; 106 | for(Music m : musics){ 107 | String curMod = musicMods.get(m); 108 | if(!curMod.equals(lastMod)){ 109 | lastMod = curMod; 110 | if(count % cols != 0) t.row(); 111 | count = 0; 112 | BLElements.divider(t, curMod, Color.lightGray, 4); 113 | t.row(); 114 | } 115 | 116 | t.button(getMusicName(m), () -> selectedMusic = m) 117 | .uniformX().grow().checked(b -> selectedMusic == m) 118 | .get().setStyle(TUStyles.toggleCentert); 119 | 120 | if((++count) % cols == 0){ 121 | t.row(); 122 | } 123 | } 124 | } 125 | 126 | private void switchMusic(Music music){ 127 | if(playingMusic != null) playingMusic.stop(); 128 | if(playingMusic != music) paused = false; 129 | playingMusic = music; 130 | play(music); 131 | } 132 | 133 | private void play(Music music){ 134 | float length = 1f; 135 | if(music != null){ 136 | music.play(); 137 | music.setVolume(1f); 138 | music.setLooping(false); 139 | if(paused){ 140 | paused = false; 141 | setTime(music); 142 | } 143 | 144 | length = musicLength(music); 145 | } 146 | if(progressBar != null) progressBar.musicLength = length; 147 | } 148 | 149 | private void pause(){ 150 | if(playingMusic != null){ 151 | paused = true; 152 | playingMusic.pause(true); 153 | targetTime = playingMusic.getPosition(); 154 | } 155 | } 156 | 157 | public void stopSounds(){ 158 | switchMusic(null); 159 | } 160 | 161 | public void update(){ 162 | Music playing = SoundDialog.soundControlPlaying(); 163 | if(playingMusic != playing){ 164 | Reflect.invoke(Vars.control.sound, "silence"); 165 | if(playing != null) Reflect.invoke(Vars.control.sound, "silence"); //Counteract fade in 166 | }else{ 167 | if(paused && playing != null) playing.pause(true); 168 | Reflect.set(Vars.control.sound, "fade", 1f); 169 | } 170 | 171 | if(playingMusic == null) return; 172 | playingMusic.setVolume(1f); 173 | playingMusic.setLooping(false); 174 | if(!paused && !queued) targetTime = playingMusic.getPosition(); 175 | } 176 | 177 | private void setTime(Music m){ 178 | if(!m.isPlaying() && !paused) m.play(); 179 | m.setPosition(targetTime); 180 | if(!queued && !Mathf.equal(m.getPosition(), targetTime)){ 181 | queued = true; 182 | app.post(() -> { 183 | queued = false; 184 | setTime(m); 185 | }); 186 | } 187 | } 188 | 189 | public static float musicLength(Music music){ 190 | return (float)(double)Reflect.invoke(Soloud.class, "streamLength", 191 | new Object[]{Reflect.get(AudioSource.class, music, "handle")}, 192 | long.class 193 | ); 194 | } 195 | 196 | private static class MusicProgressBar extends Table{ 197 | public float musicLength = 1f; 198 | 199 | public MusicProgressBar(MusicsTable musicsTable){ 200 | background(Tex.pane); 201 | 202 | Element bar = rect((x, y, width, height) -> { 203 | 204 | Music m = musicsTable.playingMusic; 205 | float progress = m != null ? musicsTable.targetTime : 0; 206 | float fin = progress / musicLength; 207 | 208 | Lines.stroke(Scl.scl(3f)); 209 | float mid = y + height / 2f; 210 | 211 | Draw.color(Color.lightGray); 212 | Lines.line(x, mid, x + width, mid); 213 | 214 | Draw.color(Color.red); 215 | Lines.line(x, mid, x + width * fin, mid); 216 | Fill.circle(x + width * fin, mid, 4f); 217 | }).grow().left().get(); 218 | bar.addListener(new InputListener(){ 219 | @Override 220 | public boolean touchDown(InputEvent event, float x, float y, int pointer, KeyCode button){ 221 | if(musicsTable.playingMusic == null) return false; 222 | calcPos(x); 223 | return true; 224 | } 225 | 226 | @Override 227 | public void touchDragged(InputEvent event, float x, float y, int pointer){ 228 | calcPos(x); 229 | } 230 | 231 | private void calcPos(float x){ 232 | if(musicsTable.playingMusic == null) musicsTable.play(musicsTable.selectedMusic); 233 | 234 | float width = bar.getWidth(); 235 | float prog = x / width; 236 | Music m = musicsTable.playingMusic; 237 | musicsTable.targetTime = prog * musicLength; 238 | musicsTable.setTime(m); 239 | } 240 | }); 241 | bar.addListener(new HandCursorListener()); 242 | 243 | label(() -> { 244 | Music m = musicsTable.playingMusic; 245 | return m != null ? 246 | UI.formatTime(musicsTable.targetTime * 60f) + " / " + UI.formatTime(musicLength * 60f) : 247 | "x:xx / x:xx"; 248 | }).padLeft(6f).width(128).right().labelAlign(Align.right); 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/testing/dialogs/sound/STable.java: -------------------------------------------------------------------------------- 1 | package testing.dialogs.sound; 2 | 3 | import arc.scene.ui.*; 4 | import arc.scene.ui.layout.*; 5 | 6 | public abstract class STable{ 7 | abstract void createSelection(Table t, TextField search); 8 | abstract void createPlay(Table t); 9 | abstract void rebuild(); 10 | abstract void stopSounds(); 11 | } 12 | -------------------------------------------------------------------------------- /src/testing/dialogs/sound/SoundDialog.java: -------------------------------------------------------------------------------- 1 | package testing.dialogs.sound; 2 | 3 | import arc.*; 4 | import arc.audio.*; 5 | import arc.graphics.*; 6 | import arc.scene.ui.*; 7 | import arc.scene.ui.layout.*; 8 | import arc.util.*; 9 | import blui.*; 10 | import blui.ui.*; 11 | import mindustry.gen.*; 12 | import mindustry.graphics.*; 13 | import testing.dialogs.*; 14 | import testing.util.*; 15 | 16 | import static arc.Core.*; 17 | import static mindustry.Vars.*; 18 | 19 | public class SoundDialog extends TUBaseDialog{ 20 | /** Audio bus for sounds played by the dialog. Will remain unpaused unlike other audio busses. */ 21 | private static final AudioBus soundRoomBus = new AudioBus(); 22 | 23 | private FilterTable filters = null; 24 | private SoundsTable soundsTable; 25 | private MusicsTable musicsTable; 26 | private STable current; 27 | private Table all; 28 | private TextField search; 29 | 30 | public SoundDialog(){ 31 | super("@tu-sound-menu.name"); 32 | 33 | Core.app.post(this::build); //Allow mods to load their sounds 34 | } 35 | 36 | private void build(){ 37 | LoadedSounds.init(); 38 | 39 | soundsTable = new SoundsTable(soundRoomBus); 40 | current = soundsTable; 41 | musicsTable = new MusicsTable(); 42 | 43 | cont.table(s -> { 44 | if(settings.getBool("tu-music-enabled", false)){ 45 | ImageButton ib = new ImageButton(TUIcons.sounds); 46 | ib.changed(() -> { 47 | current.stopSounds(); 48 | if(current == soundsTable){ 49 | current = musicsTable; 50 | ib.replaceImage(new Image(TUIcons.musics).setScaling(Scaling.fit)); 51 | }else{ 52 | current = soundsTable; 53 | ib.replaceImage(new Image(TUIcons.sounds).setScaling(Scaling.fit)); 54 | } 55 | makeUI(); 56 | }); 57 | ib.label(() -> current == soundsTable ? "@tu-sound-menu.sound" : "@tu-sound-menu.music").wrapLabel(false).left().padRight(8); 58 | s.add(ib).height(BLVars.iconSize); 59 | } 60 | s.image(Icon.zoom).padRight(8).padLeft(8); 61 | search = s.field(null, text -> rebuild()).growX().get(); 62 | search.setMessageText("@players.search"); 63 | }).fillX().padBottom(4).row(); 64 | 65 | cont.add(all = new Table()).grow().top(); 66 | makeUI(); 67 | 68 | shown(() -> { 69 | //Pause the ui audio bus while open so that button press sounds doesn't play. 70 | audio.setPaused(Sounds.press.bus.id, true); 71 | if(filters != null) filters.shown(); 72 | }); 73 | hidden(() -> { 74 | soundsTable.stopSounds(); 75 | musicsTable.stopSounds(); 76 | if(filters != null) TUFilters.closed(); 77 | audio.setPaused(Sounds.press.bus.id, false); 78 | }); 79 | 80 | if(settings.getBool("tu-music-enabled", false)){ 81 | update(() -> { 82 | if(isShown()){ 83 | if(current == musicsTable){ 84 | musicsTable.update(); 85 | }else{ 86 | Reflect.set(control.sound, "fade", 0f); 87 | Reflect.invoke(control.sound, "silence"); 88 | if(soundControlPlaying() != null) Reflect.invoke(control.sound, "silence"); //Counteract fade in 89 | } 90 | } 91 | }); 92 | } 93 | } 94 | 95 | private void makeUI(){ 96 | all.clear(); 97 | all.defaults().padLeft(32).padRight(32).center().top(); 98 | 99 | all.table(sel -> { 100 | sel.defaults().growX(); 101 | current.createSelection(sel, search); 102 | }).grow().row(); 103 | 104 | BLElements.divider(all, null, Color.lightGray); 105 | 106 | all.pane(t -> { 107 | t.defaults().top(); 108 | current.createPlay(t); 109 | t.row(); 110 | 111 | if(!Core.settings.getBool("tu-allow-filters", false)) return; 112 | BLElements.divider(t, "Audio Filters", Pal.accent); 113 | t.table(fil -> { 114 | if(filters == null) filters = new FilterTable(); 115 | fil.add(filters); 116 | }).center(); 117 | }).grow().minHeight(180); 118 | } 119 | 120 | @Override 121 | protected void rebuild(){ 122 | current.rebuild(); 123 | } 124 | 125 | /** What SoundControl is currently running play() on. Used to check if it needs to be counteracted. */ 126 | protected static Music soundControlPlaying(){ 127 | if(state.isMenu()){ 128 | if(ui.planet.isShown()){ 129 | return Musics.launch; 130 | }else if(ui.editor.isShown()){ 131 | return Musics.editor; 132 | }else{ 133 | return Musics.menu; 134 | } 135 | }else if(state.rules.editor){ 136 | return Musics.editor; 137 | } 138 | return null; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/testing/dialogs/sound/SoundsTable.java: -------------------------------------------------------------------------------- 1 | package testing.dialogs.sound; 2 | 3 | import arc.*; 4 | import arc.audio.*; 5 | import arc.graphics.*; 6 | import arc.math.*; 7 | import arc.scene.ui.*; 8 | import arc.scene.ui.TextField.*; 9 | import arc.scene.ui.layout.*; 10 | import arc.struct.*; 11 | import arc.util.*; 12 | import blui.ui.*; 13 | import mindustry.gen.*; 14 | import mindustry.graphics.*; 15 | import testing.ui.*; 16 | import testing.util.*; 17 | 18 | import static arc.Core.*; 19 | import static testing.dialogs.sound.LoadedSounds.*; 20 | 21 | public class SoundsTable extends STable{ 22 | private final AudioBus soundRoomBus; 23 | private final Table selection = new Table(); 24 | private TextField search; 25 | private Sound sound = Sounds.pew; 26 | private int loopSoundID = -1; 27 | 28 | private float minVol = 1, maxVol = 1, minPitch = 0.8f, maxPitch = 1.2f; 29 | private float loopVol = 1, loopPitch = 1; 30 | 31 | public SoundsTable(AudioBus soundRoomBus){ 32 | this.soundRoomBus = soundRoomBus; 33 | } 34 | 35 | public void createSelection(Table t, TextField search){ 36 | this.search = search; 37 | 38 | t.label(() -> bundle.get("tu-menu.selection") + getSoundName(sound)).padBottom(6).left().row(); 39 | 40 | t.pane(all -> all.add(selection).growX()); 41 | 42 | rebuild(); 43 | } 44 | 45 | public void createPlay(Table t){ 46 | BLElements.divider(t, "@tu-sound-menu.sound", Pal.accent); 47 | t.table(s -> { 48 | s.button(Icon.play, () -> { 49 | AudioBus prev = sound.bus; 50 | sound.setBus(soundRoomBus); 51 | sound.play(Mathf.random(minVol, maxVol), Mathf.random(minPitch, maxPitch), 0f, false, false); 52 | sound.setBus(prev); 53 | }).grow().center().right(); 54 | s.table(f -> { 55 | f.defaults().left().growX(); 56 | f.add("@tu-sound-menu.min-vol"); 57 | TextField[] maxVolF = {null}; 58 | f.field("" + minVol, TextFieldFilter.floatsOnly, v -> { 59 | minVol = Strings.parseFloat(v); 60 | if(minVol > maxVol){ 61 | maxVol = minVol; 62 | maxVolF[0].setText("" + maxVol); 63 | } 64 | }).padLeft(6f); 65 | f.add("-").padLeft(6f).padRight(6f); 66 | f.add("@tu-sound-menu.max-vol").padLeft(6f); 67 | maxVolF[0] = f.field("" + maxVol, TextFieldFilter.floatsOnly, v -> maxVol = Strings.parseFloat(v)).get(); 68 | maxVolF[0].setValidator(v -> Strings.parseFloat(v) >= minVol); 69 | f.row(); 70 | f.add("@tu-sound-menu.min-pitch"); 71 | TextField[] maxPitchF = {null}; 72 | f.field("" + minPitch, TextFieldFilter.floatsOnly, v -> { 73 | minPitch = Strings.parseFloat(v); 74 | if(minPitch > maxPitch){ 75 | maxPitch = minPitch; 76 | maxPitchF[0].setText("" + maxPitch); 77 | } 78 | }); 79 | f.add("-").padLeft(6f).padRight(6f); 80 | f.add("@tu-sound-menu.max-pitch").padLeft(6f); 81 | maxPitchF[0] = f.field("" + maxPitch, TextFieldFilter.floatsOnly, v -> maxPitch = Strings.parseFloat(v)).get(); 82 | maxPitchF[0].setValidator(v -> Strings.parseFloat(v) >= minPitch); 83 | }).padLeft(6f).left(); 84 | }).row(); 85 | BLElements.divider(t, "@tu-sound-menu.sound-loop", Pal.accent); 86 | t.table(l -> { 87 | l.defaults().left(); 88 | 89 | l.button(Icon.play, () -> { 90 | AudioBus prev = sound.bus; 91 | sound.setBus(soundRoomBus); 92 | loopSoundID = sound.loop(loopVol, loopPitch, 0); 93 | sound.setBus(prev); 94 | }).disabled(b -> loopSoundID >= 0).uniform().grow(); 95 | l.button(TUIcons.stop, () -> { 96 | Core.audio.stop(loopSoundID); 97 | loopSoundID = -1; 98 | }).disabled(b -> loopSoundID < 0).uniform().grow(); 99 | 100 | l.add("@tu-sound-menu.vol").padLeft(6f).growX(); 101 | l.field("" + loopVol, TextFieldFilter.floatsOnly, v -> { 102 | loopVol = Strings.parseFloat(v); 103 | if(loopSoundID >= 0){ 104 | Core.audio.setVolume(loopSoundID, loopVol); 105 | } 106 | }).padLeft(6f).growX(); 107 | l.add("@tu-sound-menu.pitch").padLeft(6f).growX(); 108 | l.field("" + loopPitch, TextFieldFilter.floatsOnly, v -> { 109 | loopPitch = Strings.parseFloat(v); 110 | if(loopSoundID >= 0){ 111 | Core.audio.setPitch(loopSoundID, loopPitch); 112 | } 113 | }).padLeft(6f).growX(); 114 | }); 115 | } 116 | 117 | public void rebuild(){ 118 | selection.clear(); 119 | String text = search.getText(); 120 | 121 | selection.table(list -> { 122 | Seq vSounds = vanillaSounds.select(s -> getSoundName(s).toLowerCase().contains(text.toLowerCase())); 123 | if(vSounds.size > 0){ 124 | BLElements.divider(list, "@tu-sound-menu.vanilla", Pal.accent); 125 | 126 | list.table(v -> vanillaSoundList(v, vSounds)).growX(); 127 | list.row(); 128 | } 129 | 130 | Seq mSounds = modSounds.select(s -> getSoundName(s).toLowerCase().contains(text.toLowerCase())); 131 | if(mSounds.size > 0){ 132 | BLElements.divider(list, "@tu-sound-menu.modded", Pal.accent); 133 | 134 | list.table(m -> modSoundList(m, mSounds)).growX(); 135 | } 136 | }).growX().padBottom(10); 137 | } 138 | 139 | public void vanillaSoundList(Table t, Seq sounds){ 140 | int cols = 4; 141 | int count = 0; 142 | for(Sound s : sounds){ 143 | TextButton sb = t.button(getSoundName(s), () -> { 144 | stopSounds(); 145 | sound = s; 146 | }).uniformX().grow().checked(b -> sound == s).get(); 147 | sb.setStyle(TUStyles.toggleCentert); 148 | 149 | if(soundOverrides.containsKey(s)){ 150 | sb.setDisabled(true); 151 | BLElements.boxTooltip(sb, bundle.format("tu-sound-menu.sound-overwritten", soundOverrides.get(s))); 152 | } 153 | 154 | if((++count) % cols == 0){ 155 | t.row(); 156 | } 157 | } 158 | } 159 | 160 | public void modSoundList(Table t, Seq sounds){ 161 | int cols = 4; 162 | int count = 0; 163 | String lastMod = null; 164 | for(Sound s : sounds){ 165 | String curMod = soundMods.get(s); 166 | if(!curMod.equals(lastMod)){ 167 | lastMod = curMod; 168 | if(count % cols != 0) t.row(); 169 | count = 0; 170 | BLElements.divider(t, curMod, Color.lightGray, 4); 171 | t.row(); 172 | } 173 | 174 | t.button(getSoundName(s), () -> { 175 | stopSounds(); 176 | sound = s; 177 | }).uniformX().grow().checked(b -> sound == s) 178 | .get().setStyle(TUStyles.toggleCentert); 179 | 180 | if((++count) % cols == 0){ 181 | t.row(); 182 | } 183 | } 184 | } 185 | 186 | public void stopSounds(){ 187 | sound.stop(); 188 | 189 | if(loopSoundID >= 0){ 190 | Core.audio.stop(loopSoundID); 191 | loopSoundID = -1; 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/testing/dialogs/world/PlanetTable.java: -------------------------------------------------------------------------------- 1 | package testing.dialogs.world; 2 | 3 | import arc.scene.ui.*; 4 | import arc.scene.ui.layout.*; 5 | import arc.struct.*; 6 | import blui.*; 7 | import blui.ui.*; 8 | import mindustry.*; 9 | import mindustry.content.*; 10 | import mindustry.gen.*; 11 | import mindustry.type.*; 12 | import testing.ui.*; 13 | import testing.util.*; 14 | 15 | import static arc.Core.*; 16 | import static mindustry.Vars.*; 17 | 18 | public class PlanetTable extends Table{ 19 | private final Table selection = new Table(); 20 | private TextField search; 21 | private Planet planet = Planets.serpulo; 22 | 23 | public PlanetTable(){ 24 | table(s -> { 25 | s.image(Icon.zoom).padRight(8); 26 | search = s.field(null, text -> rebuild()).growX().get(); 27 | search.setMessageText("@players.search"); 28 | }).fillX().padBottom(4).row(); 29 | 30 | pane(all -> all.add(selection)).row(); 31 | 32 | table(b -> { 33 | ImageButton pb = b.button(TUIcons.get(Icon.editor), TUStyles.lefti, BLVars.buttonSize, this::setPlanet).get(); 34 | BLElements.boxTooltip(pb, "@tu-tooltip.planet-set"); 35 | pb.label(() -> "@tu-planet-menu.set").padLeft(6).growX(); 36 | 37 | ImageButton tb = b.button(TUIcons.get(Icon.book), TUStyles.righti, BLVars.buttonSize, this::setPlanetRules).get(); 38 | BLElements.boxTooltip(tb, "@tu-tooltip.planet-rules"); 39 | tb.setDisabled(() -> planet == null); 40 | tb.label(() -> "@tu-planet-menu.rules").padLeft(6).growX(); 41 | }).padTop(6f); 42 | } 43 | 44 | protected void rebuild(){ 45 | selection.clear(); 46 | String text = search.getText(); 47 | 48 | selection.label( 49 | () -> bundle.get("tu-menu.selection") + (planet == null ? bundle.get("rules.anyenv") : "[#" + planet.iconColor + "]" + planet.localizedName) 50 | ).padBottom(6); 51 | selection.row(); 52 | Seq array = content.planets().select(e -> text.isEmpty() || e.localizedName.toLowerCase().contains(text.toLowerCase())); 53 | selection.table(list -> { 54 | list.left().defaults().minWidth(250); 55 | 56 | int cols = 3; 57 | int count = 0; 58 | 59 | for(Planet p : array){ 60 | TextButton button = list.button(p.localizedName, () -> planet = p).uniform().grow().get(); 61 | //button.getLabel().setWrap(false); 62 | button.image(Icon.icons.get(p.icon, Icon.commandRallySmall)).color(p.iconColor); 63 | button.getCells().reverse(); 64 | 65 | if((++count) % cols == 0){ 66 | list.row(); 67 | } 68 | } 69 | 70 | TextButton button = list.button("@rules.anyenv", () -> planet = null).uniform().grow().get(); 71 | //button.getLabel().setWrap(false); 72 | button.add(new Image(Icon.none)); 73 | button.getCells().reverse(); 74 | }).growX().left().padBottom(10); 75 | } 76 | 77 | private void setPlanet(){ 78 | if(planet == null){ 79 | state.rules.env = Vars.defaultEnv; 80 | state.rules.planet = Planets.sun; 81 | }else{ 82 | state.rules.env = planet.defaultEnv; 83 | state.rules.planet = planet; 84 | state.rules.attributes.clear(); 85 | state.rules.attributes.add(planet.defaultAttributes); 86 | } 87 | } 88 | 89 | private void setPlanetRules(){ 90 | if(planet == null) return; 91 | 92 | Vars.ui.showConfirm("@tu-planet-menu.rules-confirm", () -> { 93 | planet.ruleSetter.get(state.rules); 94 | }); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/testing/dialogs/world/WeatherTable.java: -------------------------------------------------------------------------------- 1 | package testing.dialogs.world; 2 | 3 | import arc.math.*; 4 | import arc.scene.ui.*; 5 | import arc.scene.ui.TextField.*; 6 | import arc.scene.ui.layout.*; 7 | import arc.struct.*; 8 | import arc.util.*; 9 | import blui.*; 10 | import blui.ui.*; 11 | import mindustry.content.*; 12 | import mindustry.ctype.*; 13 | import mindustry.gen.*; 14 | import mindustry.type.*; 15 | import testing.ui.*; 16 | import testing.util.*; 17 | 18 | import static arc.Core.*; 19 | import static mindustry.Vars.*; 20 | 21 | public class WeatherTable extends Table{ 22 | private final Table selection = new Table(); 23 | private final float minDur = 0.125f, maxDur = 600f; 24 | private TextField search; 25 | private Weather weather = Weathers.rain; 26 | private float intensity = 100f, duration = 60f; 27 | 28 | public WeatherTable(){ 29 | table(s -> { 30 | s.image(Icon.zoom).padRight(8); 31 | search = s.field(null, text -> rebuild()).growX().get(); 32 | search.setMessageText("@players.search"); 33 | }).fillX().padBottom(4).row(); 34 | 35 | pane(all -> all.add(selection)).row(); 36 | 37 | table(set -> { 38 | set.table(s -> { 39 | BLElements.sliderSet( 40 | s, text -> intensity = Mathf.clamp(Strings.parseFloat(text), 0f, 100f), () -> String.valueOf(intensity), 41 | TextFieldFilter.floatsOnly, Strings::canParsePositiveFloat, 42 | 0f, 100f, 1f, intensity, (n, f) -> { 43 | intensity = Mathf.clamp(n, 0f, 100f); 44 | f.setText(String.valueOf(intensity)); 45 | }, 46 | "@tu-weather-menu.intensity", 47 | "@tu-tooltip.weather-intensity" 48 | ); 49 | s.row(); 50 | 51 | BLElements.sliderSet( 52 | s, text -> duration = Strings.parseFloat(text), () -> String.valueOf(duration), 53 | TextFieldFilter.floatsOnly, Strings::canParsePositiveFloat, 54 | minDur, maxDur, 0.125f, duration, (n, f) -> { 55 | duration = n; 56 | f.setText(String.valueOf(n)); 57 | }, 58 | "@tu-status-menu.duration", 59 | "@tu-tooltip.weather-duration" 60 | ); 61 | }); 62 | set.row(); 63 | 64 | ImageButton wb = set.button(TUIcons.get(Icon.add), BLVars.buttonSize, this::createWeather).get(); 65 | BLElements.boxTooltip(wb, "@tu-tooltip.weather-create"); 66 | wb.label(() -> "@tu-weather-menu.create").padLeft(6).growX(); 67 | wb.setDisabled(() -> intensity <= 0 || duration <= 0); 68 | set.row(); 69 | 70 | set.table(b -> { 71 | ImageButton rb = b.button(TUIcons.get(Icon.cancel), TUStyles.lefti, BLVars.buttonSize, this::removeWeather).get(); 72 | BLElements.boxTooltip(rb, "@tu-tooltip.weather-remove"); 73 | rb.label(() -> "@tu-weather-menu.remove").padLeft(6).growX(); 74 | 75 | ImageButton cb = b.button(TUIcons.get(Icon.trash), TUStyles.righti, BLVars.buttonSize, this::clearWeather).get(); 76 | cb.label(() -> "@tu-weather-menu.clear").padLeft(6).growX(); 77 | }); 78 | }).padTop(6f); 79 | } 80 | 81 | protected void rebuild(){ 82 | selection.clear(); 83 | String text = search.getText(); 84 | 85 | selection.label( 86 | () -> bundle.get("tu-menu.selection") + weather.localizedName 87 | ).padBottom(6); 88 | selection.row(); 89 | 90 | Seq array = content.getBy(ContentType.weather).select(w -> w.localizedName.toLowerCase().contains(text.toLowerCase())); 91 | selection.table(list -> { 92 | list.left().defaults().minWidth(250); 93 | 94 | int cols = 3; 95 | int count = 0; 96 | 97 | for(Weather w : array){ 98 | TextButton button = list.button(w.localizedName, () -> weather = w).uniform().grow().get(); 99 | //button.getLabel().setWrap(false); 100 | if(w.fullIcon.found()){ 101 | button.add(new Image(w.fullIcon)); 102 | button.getCells().reverse(); 103 | } 104 | 105 | if((++count) % cols == 0){ 106 | list.row(); 107 | } 108 | } 109 | }).growX().left().padBottom(10); 110 | selection.row(); 111 | } 112 | 113 | private void createWeather(){ 114 | weather.create(intensity / 100f, duration * 60f); 115 | } 116 | 117 | private void removeWeather(){ 118 | Groups.weather.each(w -> w.weather == weather, WeatherState::remove); 119 | } 120 | 121 | private void clearWeather(){ 122 | Groups.weather.each(WeatherState::remove); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/testing/dialogs/world/WorldDialog.java: -------------------------------------------------------------------------------- 1 | package testing.dialogs.world; 2 | 3 | import blui.ui.*; 4 | import mindustry.graphics.*; 5 | import testing.dialogs.*; 6 | 7 | public class WorldDialog extends TUBaseDialog{ 8 | private final PlanetTable planetTable; 9 | private final WeatherTable weatherTable; 10 | 11 | public WorldDialog(){ 12 | super("@tu-world-menu.name"); 13 | 14 | BLElements.divider(cont, "@tu-planet-menu.name", Pal.accent); 15 | planetTable = new PlanetTable(); 16 | planetTable.top(); 17 | cont.add(planetTable).growX().top().row(); 18 | 19 | BLElements.divider(cont, "@tu-weather-menu.name", Pal.accent); 20 | weatherTable = new WeatherTable(); 21 | weatherTable.top(); 22 | cont.add(weatherTable).growX().top(); 23 | } 24 | 25 | @Override 26 | protected void rebuild(){ 27 | planetTable.rebuild(); 28 | weatherTable.rebuild(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/testing/editor/PaintOp.java: -------------------------------------------------------------------------------- 1 | package testing.editor; 2 | 3 | import mindustry.gen.*; 4 | 5 | /** Based on {@link TileOp}. Stores an extra byte for tile data. */ 6 | public class PaintOp{ 7 | private static final long xMask = 0xffffL; 8 | private static final long yMask = 0xffffL << 16; 9 | private static final long typeMask = 0xffL << 32; 10 | private static final long valueMask = 0xffffL << 40; 11 | private static final long dataMask = 0xffL << 56; 12 | 13 | public static short x(long paintOp){ 14 | return (short)(paintOp & xMask); 15 | } 16 | 17 | public static short y(long paintOp){ 18 | return (short)((paintOp & yMask) >>> 16); 19 | } 20 | 21 | public static byte type(long paintOp){ 22 | return (byte)((paintOp & typeMask) >>> 32); 23 | } 24 | 25 | public static short value(long paintOp){ 26 | return (short)((paintOp & valueMask) >>> 40); 27 | } 28 | 29 | public static byte data(long paintOp){ 30 | return (byte)((paintOp & dataMask) >>> 56); 31 | } 32 | 33 | public static long get(short x, short y, byte type, short value){ 34 | return get(x, y, type, value, (byte)0); 35 | } 36 | 37 | public static long get(short x, short y, byte type, short value, byte data){ 38 | return (long)x & xMask | (long)y << 16 & yMask | (long)type << 32 & typeMask | (long)value << 40 & valueMask | (long)data << 56 & dataMask; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/testing/editor/PaintOperation.java: -------------------------------------------------------------------------------- 1 | package testing.editor; 2 | 3 | import arc.struct.*; 4 | import mindustry.editor.*; 5 | import mindustry.editor.DrawOperation.*; 6 | import mindustry.game.*; 7 | import mindustry.world.*; 8 | import mindustry.world.blocks.environment.*; 9 | 10 | import static mindustry.Vars.*; 11 | import static testing.util.TUVars.*; 12 | 13 | /** Based on {@link DrawOperation} */ 14 | public class PaintOperation{ 15 | private final LongSeq array = new LongSeq(); 16 | 17 | public boolean isEmpty(){ 18 | return array.isEmpty(); 19 | } 20 | 21 | public void addOperation(long op){ 22 | array.add(op); 23 | } 24 | 25 | public void undo(){ 26 | for(int i = array.size - 1; i >= 0; i--){ 27 | updateTile(i); 28 | } 29 | } 30 | 31 | public void redo(){ 32 | for(int i = 0; i < array.size; i++){ 33 | updateTile(i); 34 | } 35 | } 36 | 37 | private void updateTile(int i){ 38 | long l = array.get(i); 39 | Tile tile = painter.tile(PaintOp.x(l), PaintOp.y(l)); 40 | array.set(i, PaintOp.get(PaintOp.x(l), PaintOp.y(l), PaintOp.type(l), getTile(tile, PaintOp.type(l)), tile.data)); 41 | setTile(painter.tile(PaintOp.x(l), PaintOp.y(l)), PaintOp.type(l), PaintOp.value(l), PaintOp.data(l)); 42 | } 43 | 44 | private short getTile(Tile tile, byte type){ 45 | if(type == OpType.floor.ordinal()){ 46 | return tile.floorID(); 47 | }else if(type == OpType.block.ordinal()){ 48 | return tile.blockID(); 49 | }else if(type == OpType.rotation.ordinal()){ 50 | return tile.build == null ? 0 : (byte)tile.build.rotation; 51 | }else if(type == OpType.team.ordinal()){ 52 | return (byte)tile.getTeamID(); 53 | }else if(type == OpType.overlay.ordinal()){ 54 | return tile.overlayID(); 55 | } 56 | throw new IllegalArgumentException("Invalid type."); 57 | } 58 | 59 | private void setTile(Tile tile, byte type, short to, byte data){ 60 | painter.load(() -> { 61 | if(type == OpType.floor.ordinal()){ 62 | if(content.block(to) instanceof Floor floor){ 63 | tile.setFloor(floor); 64 | } 65 | }else if(type == OpType.block.ordinal()){ 66 | Block block = content.block(to); 67 | 68 | if(block instanceof Cliff){ 69 | if(data == 0){ 70 | painter.pendingCliffs.add(tile); //Pending cliff was added 71 | }else{ 72 | painter.pendingCliffs.remove(tile); //Preexisting cliff was added 73 | } 74 | }else if(tile.block() instanceof Cliff){ 75 | if(tile.data == 0) painter.pendingCliffs.remove(tile); //Pending cliff was removed 76 | } 77 | 78 | tile.setBlock(block, tile.team(), tile.build == null ? 0 : tile.build.rotation); 79 | tile.data = data; 80 | }else if(type == OpType.rotation.ordinal()){ 81 | if(tile.build != null) tile.build.rotation = to; 82 | }else if(type == OpType.team.ordinal()){ 83 | tile.setTeam(Team.get(to)); 84 | }else if(type == OpType.overlay.ordinal()){ 85 | tile.setOverlayID(to); 86 | } 87 | }); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/testing/editor/PaintOperationStack.java: -------------------------------------------------------------------------------- 1 | package testing.editor; 2 | 3 | import arc.struct.*; 4 | import mindustry.editor.*; 5 | 6 | /** Based on {@link OperationStack} */ 7 | public class PaintOperationStack{ 8 | private static final int maxSize = 10; 9 | private Seq stack = new Seq<>(); 10 | private int index = 0; 11 | 12 | public PaintOperationStack(){ 13 | 14 | } 15 | 16 | public void clear(){ 17 | stack.clear(); 18 | index = 0; 19 | } 20 | 21 | public void add(PaintOperation action){ 22 | stack.truncate(stack.size + index); 23 | index = 0; 24 | stack.add(action); 25 | 26 | if(stack.size > maxSize){ 27 | stack.remove(0); 28 | } 29 | } 30 | 31 | public boolean canUndo(){ 32 | return !(stack.size - 1 + index < 0); 33 | } 34 | 35 | public boolean canRedo(){ 36 | return !(index > -1 || stack.size + index < 0); 37 | } 38 | 39 | public void undo(){ 40 | if(!canUndo()) return; 41 | 42 | stack.get(stack.size - 1 + index).undo(); 43 | index--; 44 | } 45 | 46 | public void redo(){ 47 | if(!canRedo()) return; 48 | 49 | index++; 50 | stack.get(stack.size - 1 + index).redo(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/testing/editor/PaintedTileData.java: -------------------------------------------------------------------------------- 1 | package testing.editor; 2 | 3 | import arc.func.*; 4 | import mindustry.content.*; 5 | import mindustry.editor.DrawOperation.*; 6 | import mindustry.editor.*; 7 | import mindustry.game.*; 8 | import mindustry.gen.*; 9 | import mindustry.world.*; 10 | import mindustry.world.blocks.environment.*; 11 | 12 | import static mindustry.Vars.*; 13 | import static testing.util.TUVars.*; 14 | 15 | /** Wrapper for {@link Tile} that functions similarly to {@link EditorTile}, but for use while in-game. */ 16 | public class PaintedTileData{ 17 | public Tile tile; 18 | 19 | public PaintedTileData(Tile tile){ 20 | this.tile = tile; 21 | } 22 | 23 | public void setFloor(Floor type){ 24 | if(skip()){ 25 | tile.setFloor(type); 26 | return; 27 | } 28 | 29 | Floor tFloor = floor(); 30 | if(type instanceof OverlayFloor){ 31 | //don't place on liquids 32 | if(tFloor.hasSurface() || !type.needsSurface){ 33 | setOverlayID(type.id); 34 | } 35 | return; 36 | } 37 | 38 | if(tFloor == type && overlayID() == 0) return; 39 | if(overlayID() != 0) op(OpType.overlay, overlayID()); 40 | if(tFloor != type) op(OpType.floor, tFloor.id); 41 | tile.setFloor(type); 42 | } 43 | 44 | /** Sets the floor, preserving overlay.*/ 45 | public void setFloorUnder(Floor floor){ 46 | Block overlay = overlay(); 47 | setFloor(floor); 48 | if(overlay() != overlay){ 49 | setOverlay(overlay); 50 | } 51 | } 52 | 53 | public void setBlock(Block type){ 54 | setBlock(type, Team.derelict); 55 | } 56 | 57 | public void setBlock(Block type, Team team){ 58 | setBlock(type, team, 0); 59 | } 60 | 61 | public void setBlock(Block type, Team team, int rotation){ 62 | setBlock(type, team, rotation, type::newBuilding); 63 | } 64 | 65 | public void setBlock(Block type, Team team, int rotation, Prov entityprov){ 66 | if(skip()){ 67 | tile.setBlock(type, team, rotation, entityprov); 68 | return; 69 | } 70 | 71 | Block tBlock = block(); 72 | Building tBuild = tile.build; 73 | if(tBlock == type && (tBuild == null || tBuild.rotation == rotation)){ 74 | return; 75 | } 76 | 77 | byte data = 0; 78 | if(type instanceof Cliff){ 79 | painter.pendingCliffs.add(tile); 80 | tile.data = 0; 81 | }else if(tBlock instanceof Cliff){ 82 | painter.pendingCliffs.remove(tile); 83 | data = tile.data; 84 | tile.data = 0; 85 | } 86 | 87 | if(!isCenter()){ 88 | PaintedTileData cen = painter.data(tBuild.tile); 89 | cen.op(OpType.rotation, (byte)tBuild.rotation); 90 | cen.op(OpType.team, (byte)tBuild.team.id); 91 | cen.op(OpType.block, tBlock.id, data); 92 | }else{ 93 | if(tBuild != null) op(OpType.rotation, (byte)tBuild.rotation); 94 | if(tBuild != null) op(OpType.team, (byte)tBuild.team.id); 95 | op(OpType.block, tBlock.id, data); 96 | } 97 | 98 | tile.setBlock(type, team, rotation, entityprov); 99 | } 100 | 101 | public void setTeam(Team team){ 102 | if(skip()){ 103 | tile.setTeam(team); 104 | return; 105 | } 106 | 107 | if(getTeamID() == team.id) return; 108 | op(OpType.team, (byte)getTeamID()); 109 | tile.setTeam(team); 110 | } 111 | 112 | public void setOverlay(Block overlay){ 113 | if(skip()){ 114 | tile.setOverlay(overlay); 115 | return; 116 | } 117 | 118 | Floor tFloor = tile.floor(); 119 | Floor tOverlay = tile.overlay(); 120 | if(!tFloor.hasSurface() && overlay.asFloor().needsSurface && (overlay instanceof OreBlock || !tFloor.supportsOverlay)) 121 | return; 122 | if(tOverlay == overlay) return; 123 | op(OpType.overlay, tOverlay.id); 124 | tile.setOverlay(overlay); 125 | } 126 | 127 | private boolean skip(){ 128 | return painter.isLoading() || world.isGenerating(); 129 | } 130 | 131 | public boolean isCenter(){ 132 | return tile.isCenter(); 133 | } 134 | 135 | public short x(){ 136 | return tile.x; 137 | } 138 | 139 | public short y(){ 140 | return tile.y; 141 | } 142 | 143 | public Team team(){ 144 | return tile.team(); 145 | } 146 | 147 | public int getTeamID(){ 148 | return team().id; 149 | } 150 | 151 | public Floor overlay(){ 152 | return tile.overlay(); 153 | } 154 | 155 | public short overlayID(){ 156 | return overlay().id; 157 | } 158 | 159 | public Block block(){ 160 | return tile.block(); 161 | } 162 | 163 | public short blockID(){ 164 | return block().id; 165 | } 166 | 167 | public Floor floor(){ 168 | return tile.floor(); 169 | } 170 | 171 | public short floorID(){ 172 | return floor().id; 173 | } 174 | 175 | public Building build(){ 176 | return tile.build; 177 | } 178 | 179 | public void setOverlayID(short ore){ 180 | setOverlay(content.block(ore)); 181 | } 182 | 183 | public void remove(){ 184 | setBlock(Blocks.air); 185 | } 186 | 187 | public void clearOverlay(){ 188 | setOverlayID((short)0); 189 | } 190 | 191 | private void op(OpType type, short value){ 192 | op(type, value, (byte)0); 193 | } 194 | 195 | private void op(OpType type, short value, byte data){ 196 | painter.addPaintOp(PaintOp.get(x(), y(), (byte)type.ordinal(), value, data)); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/testing/editor/TerrainPaintbrush.java: -------------------------------------------------------------------------------- 1 | package testing.editor; 2 | 3 | import arc.*; 4 | import arc.graphics.*; 5 | import arc.graphics.g2d.*; 6 | import arc.input.*; 7 | import arc.math.*; 8 | import arc.math.geom.*; 9 | import arc.scene.ui.layout.*; 10 | import arc.util.*; 11 | import mindustry.content.*; 12 | import mindustry.core.*; 13 | import mindustry.editor.*; 14 | import mindustry.game.EventType.*; 15 | import mindustry.graphics.*; 16 | import mindustry.world.*; 17 | import testing.*; 18 | import testing.util.*; 19 | 20 | import static arc.Core.*; 21 | import static mindustry.Vars.*; 22 | import static testing.util.TUVars.*; 23 | 24 | /** Based on {@link MapView} */ 25 | public class TerrainPaintbrush{ 26 | private final Vec2[][] brushPolygons = new Vec2[MapEditor.brushSizes.length][0]; 27 | 28 | private float hold = 0f; 29 | private KeyCode button; 30 | 31 | private final Point2 firstTouch = new Point2(); 32 | private PainterTool tool = PainterTool.pencil; 33 | private PainterTool lastTool; 34 | private boolean drawing; 35 | private int lastX, lastY; 36 | private int startX, startY; 37 | public boolean drawGrid; 38 | 39 | public TerrainPaintbrush(){ 40 | for(int i = 0; i < MapEditor.brushSizes.length; i++){ 41 | float size = MapEditor.brushSizes[i]; 42 | float mod = size % 1f; 43 | brushPolygons[i] = Geometry.pixelCircle(size, (index, x, y) -> Mathf.dst(x, y, index - mod, index - mod) <= size - 0.5f); 44 | } 45 | 46 | Events.run(Trigger.update, () -> { 47 | if(state.isGame() && Setup.terrainFrag.shown()){ 48 | if(Utils.hasMouse()){ 49 | touchUp(lastX, lastY); 50 | }else{ 51 | if(TestUtils.canTeleport()) return; 52 | 53 | int tx = World.toTile(input.mouseWorldX()), 54 | ty = World.toTile(input.mouseWorldY()); 55 | 56 | if(!mobile && input.keyDown(KeyCode.mouseMiddle)){ 57 | PainterTool.pick.touched(tx, ty); 58 | return; 59 | } 60 | 61 | if(!TestUtils.click()){ 62 | hold = 0f; 63 | touchUp(tx, ty); 64 | return; 65 | } 66 | player.shooting(false); 67 | 68 | if(mobile){ 69 | hold += TUVars.delta(); 70 | if(hold < 0.43f * 60f) return; 71 | } 72 | 73 | if(!drawing){ 74 | touchDown(tx, ty, mobile ? KeyCode.mouseLeft : TestUtils.getClick()); 75 | }else if(lastX != tx || lastY != ty){ 76 | touchDragged(tx, ty); 77 | } 78 | } 79 | } 80 | }); 81 | 82 | Events.run(Trigger.draw, () -> { 83 | Draw.z(Layer.overlayUI); 84 | 85 | if(state.isGame() && Setup.terrainFrag.shown()){ 86 | drawPendingCliffs(); 87 | drawGrid(); 88 | 89 | if(!Utils.hasMouse()){ 90 | Draw.z(Layer.overlayUI + 0.02f); 91 | drawBrush(); 92 | } 93 | } 94 | }); 95 | } 96 | 97 | private void drawGrid(){ 98 | if(!drawGrid) return; 99 | 100 | int x1 = (int)(Math.max(0, camera.position.x - camera.width / 2f - tilesize) / tilesize), 101 | y1 = (int)(Math.max(0, camera.position.y - camera.height / 2f - tilesize) / tilesize), 102 | x2 = (int)(Math.min(world.unitWidth(), camera.position.x + camera.width / 2f + tilesize) / tilesize), 103 | y2 = (int)(Math.min(world.unitHeight(), camera.position.y + camera.height / 2f + tilesize) / tilesize); 104 | 105 | Lines.stroke(1); 106 | for(int i = x1; i <= x2; i++){ 107 | setGridColor(i); 108 | Lines.line(i * tilesize - 4f, y1 * tilesize - 4f, i * tilesize - 4f, y2 * tilesize - 4f); 109 | } 110 | for(int i = y1; i <= y2; i++){ 111 | setGridColor(i); 112 | Lines.line(x1 * tilesize - 4f, i * tilesize - 4f, x2 * tilesize - 4f, i * tilesize - 4f); 113 | } 114 | Draw.color(); 115 | Draw.z(Layer.overlayUI); 116 | } 117 | 118 | private void setGridColor(int i){ 119 | if(i % 16 == 0){ 120 | Draw.color(Pal.accent); 121 | Draw.z(Layer.overlayUI + 0.01f); 122 | }else{ 123 | Draw.color(Color.gray); 124 | Draw.z(Layer.overlayUI); 125 | } 126 | Draw.alpha(0.5f); 127 | } 128 | 129 | private void drawPendingCliffs(){ 130 | if(painter.pendingCliffs.isEmpty()) return; 131 | 132 | Draw.alpha(0.75f + Mathf.sinDeg(Time.time * 2f) * 0.25f); 133 | for(Tile t : painter.pendingCliffs){ 134 | Draw.rect(Blocks.cliff.uiIcon, t.worldx(), t.worldy(), tilesize, tilesize); 135 | } 136 | Draw.color(); 137 | } 138 | 139 | private void drawBrush(){ 140 | if(TestUtils.canTeleport()) return; 141 | 142 | int index = 0; 143 | for(int i = 0; i < MapEditor.brushSizes.length; i++){ 144 | if(painter.brushSize() == MapEditor.brushSizes[i]){ 145 | index = i; 146 | break; 147 | } 148 | } 149 | 150 | Draw.color(Pal.accent); 151 | Lines.stroke(Scl.scl(2f)); 152 | 153 | float x = World.toTile(input.mouseWorldX()) * tilesize, 154 | y = World.toTile(input.mouseWorldY()) * tilesize; 155 | 156 | if((!painter.drawBlock.isMultiblock() || tool == PainterTool.eraser) && tool != PainterTool.fill){ 157 | if(tool == PainterTool.line && drawing){ 158 | Lines.poly(brushPolygons[index], startX * tilesize - 4f, startY * tilesize - 4f, tilesize); 159 | Lines.poly(brushPolygons[index], lastX * tilesize - 4f, lastY * tilesize - 4f, tilesize); 160 | } 161 | 162 | if((tool.edit || (tool == PainterTool.line && !drawing)) && (!mobile || drawing)){ 163 | //pencil square outline 164 | if(tool == PainterTool.pencil && tool.mode == 1){ 165 | Lines.square(x, y, (painter.brushSize == 1.5f ? 1f : painter.brushSize) * tilesize + 4f); 166 | }else{ 167 | Lines.poly(brushPolygons[index], x - 4f, y - 4f, tilesize); 168 | } 169 | } 170 | }else{ 171 | float size = painter.drawBlock.size * tilesize, 172 | offset = (1 - painter.drawBlock.size % 2) * tilesize / 2f; 173 | 174 | if(tool == PainterTool.line && drawing){ 175 | Lines.rect(startX * tilesize - size / 2 + offset, startY * tilesize - size / 2 + offset, size, size); 176 | Lines.rect(lastX * tilesize - size / 2 + offset, lastY * tilesize - size / 2 + offset, size, size); 177 | }else if((tool.edit || tool == PainterTool.line) && (!mobile || drawing)){ 178 | Lines.rect(x - size / 2 + offset, y - size / 2 + offset, size, size); 179 | } 180 | } 181 | Draw.color(); 182 | } 183 | 184 | private void touchDown(int x, int y, KeyCode button){ 185 | this.button = button; 186 | 187 | //These are already bound to other things - I don't have a way to stop them. 188 | /* 189 | if(button == KeyCode.mouseRight){ 190 | lastTool = tool; 191 | tool = PainterTool.eraser; 192 | } 193 | 194 | if(button == KeyCode.mouseMiddle){ 195 | lastTool = tool; 196 | tool = PainterTool.pick; 197 | } 198 | */ 199 | 200 | startX = x; 201 | startY = y; 202 | lastX = x; 203 | lastY = y; 204 | tool.touched(x, y); 205 | firstTouch.set(x, y); 206 | 207 | drawing = true; 208 | } 209 | 210 | private void touchDragged(int x, int y){ 211 | if(!drawing) return; 212 | 213 | if(tool.draggable && !(x == lastX && y == lastY)){ 214 | Bresenham2.line(lastX, lastY, x, y, (cx, cy) -> tool.touched(cx, cy)); 215 | } 216 | 217 | if(tool == PainterTool.line && tool.mode == 1){ 218 | if(Math.abs(x - firstTouch.x) > Math.abs(y - firstTouch.y)){ 219 | lastX = x; 220 | lastY = firstTouch.y; 221 | }else{ 222 | lastX = firstTouch.x; 223 | lastY = y; 224 | } 225 | }else{ 226 | lastX = x; 227 | lastY = y; 228 | } 229 | } 230 | 231 | private void touchUp(int x, int y){ 232 | if(!drawing) return; 233 | drawing = false; 234 | 235 | if(tool == PainterTool.line){ 236 | tool.touchedLine(startX, startY, x, y); 237 | } 238 | 239 | painter.flushOp(); 240 | 241 | if((button == KeyCode.mouseMiddle || button == KeyCode.mouseRight) && lastTool != null){ 242 | tool = lastTool; 243 | lastTool = null; 244 | } 245 | } 246 | 247 | public PainterTool getTool(){ 248 | return tool; 249 | } 250 | 251 | public void setTool(PainterTool tool){ 252 | this.tool = tool; 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /src/testing/editor/TerrainPainter.java: -------------------------------------------------------------------------------- 1 | package testing.editor; 2 | 3 | import arc.*; 4 | import arc.func.*; 5 | import arc.math.*; 6 | import arc.math.geom.*; 7 | import arc.struct.*; 8 | import mindustry.content.*; 9 | import mindustry.editor.DrawOperation.*; 10 | import mindustry.editor.*; 11 | import mindustry.game.EventType.*; 12 | import mindustry.game.*; 13 | import mindustry.world.*; 14 | import mindustry.world.blocks.environment.*; 15 | import testing.util.*; 16 | 17 | import static mindustry.Vars.*; 18 | import static testing.util.TUVars.*; 19 | 20 | /** Based on {@link MapEditor}. Made to operate in a live map instead of the editor. */ 21 | public class TerrainPainter{ 22 | private final PaintOperationStack stack = new PaintOperationStack(); 23 | private PaintOperation currentOp; 24 | private PaintedTileData[][] data; 25 | private boolean loading; 26 | 27 | public final Seq pendingCliffs = new Seq<>(); 28 | public float brushSize = 1; 29 | public int rotation; 30 | public Block drawBlock = Blocks.boulder; 31 | public Team drawTeam = Team.sharded; 32 | 33 | public boolean isLoading(){ 34 | return loading; 35 | } 36 | 37 | private void reset(){ 38 | flushCliffs(); 39 | clearOp(); 40 | } 41 | 42 | public void beginEditing(){ 43 | loading = true; 44 | Tiles tiles = tiles(); 45 | data = new PaintedTileData[width()][height()]; 46 | for(int x = 0; x < width(); x++){ 47 | for(int y = 0; y < height(); y++){ 48 | Tile t = tiles.get(x, y); 49 | data[x][y] = new PaintedTileData(t); 50 | } 51 | } 52 | loading = false; 53 | } 54 | 55 | /** Converts all tiles in the world to normal {@link Tile}s. */ 56 | public void endEditing(){ 57 | data = null; 58 | reset(); 59 | paintbrush.drawGrid = false; 60 | Events.fire(new WorldLoadEvent()); 61 | } 62 | 63 | public void load(Runnable r){ 64 | loading = true; 65 | r.run(); 66 | loading = false; 67 | } 68 | 69 | public Tiles tiles(){ 70 | return world.tiles; 71 | } 72 | 73 | public Tile tile(int x, int y){ 74 | return world.rawTile(x, y); 75 | } 76 | 77 | public int width(){ 78 | return world.width(); 79 | } 80 | 81 | public int height(){ 82 | return world.height(); 83 | } 84 | 85 | public float brushSize(){ 86 | return drawBlock instanceof SteamVent ? 2 : brushSize; 87 | } 88 | 89 | public void setDrawBlock(Block block){ 90 | drawBlock = block; 91 | Setup.terrainFrag.updateMenu(); 92 | } 93 | 94 | public void drawBlocksReplace(int x, int y){ 95 | drawBlocks(x, y, data -> data.block() != Blocks.air || drawBlock.isFloor()); 96 | } 97 | 98 | public void drawBlocks(int x, int y){ 99 | drawBlocks(x, y, false, false, data -> true); 100 | } 101 | 102 | public void drawBlocks(int x, int y, Boolf tester){ 103 | drawBlocks(x, y, false, false, tester); 104 | } 105 | 106 | public void drawBlocks(int x, int y, boolean square, boolean forceOverlay, Boolf tester){ 107 | if(drawBlock.isMultiblock()){ 108 | x = Mathf.clamp(x, (drawBlock.size - 1) / 2, width() - drawBlock.size / 2 - 1); 109 | y = Mathf.clamp(y, (drawBlock.size - 1) / 2, height() - drawBlock.size / 2 - 1); 110 | if(!hasOverlap(x, y)){ 111 | data(x, y).setBlock(drawBlock, drawTeam, rotation); 112 | } 113 | }else{ 114 | boolean isFloor = drawBlock.isFloor() && drawBlock != Blocks.air; 115 | 116 | Cons drawer = data -> { 117 | if(!tester.get(data)) return; 118 | 119 | if(isFloor){ 120 | if(forceOverlay){ 121 | data.setOverlay(drawBlock.asFloor()); 122 | }else{ 123 | if(!(drawBlock.asFloor().wallOre && !data.block().solid)){ 124 | data.setFloor(drawBlock.asFloor()); 125 | } 126 | } 127 | }else if(!(data.block().isMultiblock() && !drawBlock.isMultiblock())){ 128 | if(drawBlock.rotate && data.build() != null && data.build().rotation != rotation){ 129 | addPaintOp(PaintOp.get(data.x(), data.y(), (byte)OpType.rotation.ordinal(), (byte)rotation)); 130 | } 131 | 132 | data.setBlock(drawBlock, drawTeam, rotation); 133 | } 134 | }; 135 | 136 | if(drawBlock instanceof SteamVent){ //Always draw a single vent 137 | drawSquare(x, y, 1, drawer); 138 | }else if(square){ 139 | drawSquare(x, y, brushSize, drawer); 140 | }else{ 141 | drawCircle(x, y, brushSize, drawer); 142 | } 143 | } 144 | } 145 | 146 | boolean hasOverlap(int x, int y){ 147 | Tile tile = world.tile(x, y); 148 | //allow direct replacement of blocks of the same size 149 | if(tile != null && tile.isCenter() && tile.block() != drawBlock && tile.block().size == drawBlock.size && tile.x == x && tile.y == y){ 150 | return false; 151 | } 152 | 153 | //else, check for overlap 154 | int offsetx = -(drawBlock.size - 1) / 2; 155 | int offsety = -(drawBlock.size - 1) / 2; 156 | for(int dx = 0; dx < drawBlock.size; dx++){ 157 | for(int dy = 0; dy < drawBlock.size; dy++){ 158 | int worldx = dx + offsetx + x; 159 | int worldy = dy + offsety + y; 160 | Tile other = world.tile(worldx, worldy); 161 | 162 | if(other != null && other.block().isMultiblock()){ 163 | return true; 164 | } 165 | } 166 | } 167 | 168 | return false; 169 | } 170 | 171 | public void drawCircle(int x, int y, float brushSize, Cons drawer){ 172 | int clamped = (int)brushSize; 173 | for(int rx = -clamped; rx <= clamped; rx++){ 174 | for(int ry = -clamped; ry <= clamped; ry++){ 175 | if(Mathf.within(rx, ry, brushSize - 0.5f + 0.0001f)){ 176 | int wx = x + rx, wy = y + ry; 177 | 178 | if(wx < 0 || wy < 0 || wx >= width() || wy >= height()){ 179 | continue; 180 | } 181 | 182 | drawer.get(data(wx, wy)); 183 | } 184 | } 185 | } 186 | } 187 | 188 | public void drawSquare(int x, int y, float brushSize, Cons drawer){ 189 | int clamped = (int)brushSize; 190 | for(int rx = -clamped; rx <= clamped; rx++){ 191 | for(int ry = -clamped; ry <= clamped; ry++){ 192 | int wx = x + rx, wy = y + ry; 193 | 194 | if(wx < 0 || wy < 0 || wx >= width() || wy >= height()){ 195 | continue; 196 | } 197 | 198 | drawer.get(data(wx, wy)); 199 | } 200 | } 201 | } 202 | 203 | public void flushCliffs(){ 204 | flushCliffs(false); 205 | } 206 | 207 | public void flushCliffs(boolean indent){ 208 | if(pendingCliffs.isEmpty()) return; 209 | 210 | for(Tile tile : pendingCliffs){ 211 | if(!tile.block().isStatic() || tile.block() != Blocks.cliff) continue; 212 | int rotation = 0; 213 | for(int i = 0; i < 8; i++){ 214 | Tile other = world.tiles.get(tile.x + Geometry.d8[i].x, tile.y + Geometry.d8[i].y); 215 | if(other != null && !other.block().isStatic()){ 216 | rotation |= (1 << i); 217 | } 218 | } 219 | addPaintOp(PaintOp.get(tile.x, tile.y, (byte)OpType.block.ordinal(), Blocks.cliff.id, tile.data)); 220 | tile.data = (byte)rotation; 221 | } 222 | for(Tile tile : pendingCliffs){ 223 | if(tile.block() instanceof Cliff && tile.data == 0){ 224 | tile.setBlock(Blocks.air); 225 | } 226 | } 227 | if(indent){ //Invert in a 3rd pass. 228 | for(Tile tile : pendingCliffs){ 229 | int rotation = tile.data; 230 | if(tile.block() instanceof Cliff){ 231 | for(int i = 0; i < 8; i++){ 232 | Tile other = world.tiles.get(tile.x + Geometry.d8[i].x, tile.y + Geometry.d8[i].y); 233 | if(other != null && other.block() != Blocks.cliff){ 234 | rotation ^= (1 << i); 235 | } 236 | } 237 | } 238 | tile.data = (byte)rotation; 239 | } 240 | } 241 | 242 | flushOp(); 243 | pendingCliffs.clear(); 244 | } 245 | 246 | public void clearOp(){ 247 | stack.clear(); 248 | } 249 | 250 | public void undo(){ 251 | if(stack.canUndo()){ 252 | stack.undo(); 253 | } 254 | } 255 | 256 | public void redo(){ 257 | if(stack.canRedo()){ 258 | stack.redo(); 259 | } 260 | } 261 | 262 | public boolean canUndo(){ 263 | return stack.canUndo(); 264 | } 265 | 266 | public boolean canRedo(){ 267 | return stack.canRedo(); 268 | } 269 | 270 | public void flushOp(){ 271 | if(currentOp == null || currentOp.isEmpty()) return; 272 | stack.add(currentOp); 273 | currentOp = null; 274 | } 275 | 276 | public void addPaintOp(long data){ 277 | if(loading) return; 278 | 279 | if(currentOp == null) currentOp = new PaintOperation(); 280 | currentOp.addOperation(data); 281 | } 282 | 283 | public PaintedTileData data(int x, int y){ 284 | return data[x][y]; 285 | } 286 | 287 | public PaintedTileData data(Tile tile){ 288 | return data(tile.x, tile.y); 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /src/testing/ui/TUDialogs.java: -------------------------------------------------------------------------------- 1 | package testing.ui; 2 | 3 | import testing.dialogs.*; 4 | import testing.dialogs.sound.*; 5 | import testing.dialogs.world.*; 6 | 7 | public class TUDialogs{ 8 | public static UnitDialog unitDialog; 9 | public static BlockDialog blockDialog; 10 | public static TeamDialog teamDialog; 11 | public static WaveChangeDialog waveChangeDialog; 12 | public static StatusDialog statusDialog; 13 | public static WorldDialog worldDialog; 14 | public static InterpDialog interpDialog; 15 | public static SoundDialog soundDialog; 16 | 17 | public static void load(){ 18 | unitDialog = new UnitDialog(); 19 | blockDialog = new BlockDialog(); 20 | teamDialog = new TeamDialog(); 21 | waveChangeDialog = new WaveChangeDialog(); 22 | statusDialog = new StatusDialog(); 23 | worldDialog = new WorldDialog(); 24 | interpDialog = new InterpDialog(); 25 | soundDialog = new SoundDialog(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/testing/ui/TUStyles.java: -------------------------------------------------------------------------------- 1 | package testing.ui; 2 | 3 | import arc.scene.style.*; 4 | import arc.scene.ui.Button.*; 5 | import arc.scene.ui.ImageButton.*; 6 | import arc.scene.ui.TextButton.*; 7 | import blui.scene.ui.HoldImageButton.*; 8 | import mindustry.gen.*; 9 | import mindustry.ui.*; 10 | 11 | import static arc.Core.*; 12 | import static arc.graphics.Color.*; 13 | 14 | public class TUStyles{ 15 | public static Drawable 16 | buttonLeft, buttonLeftDown, buttonLeftOver, 17 | buttonCenter, buttonCenterDown, buttonCenterOver, buttonCenterDisabled, 18 | buttonRight, buttonRightOver, buttonRightDown, 19 | paneBottom; 20 | public static ButtonStyle right; 21 | public static TextButtonStyle round, toggleCentert; 22 | public static ImageButtonStyle 23 | tuImageStyle, 24 | togglei, 25 | lefti, toggleLefti, 26 | righti, toggleRighti, 27 | centeri; 28 | 29 | public static HoldImageButtonStyle 30 | tuHoldImageStyle, 31 | teamChanger; 32 | 33 | public static void init(){ 34 | buttonLeft = atlas.getDrawable("test-utils-button-left"); 35 | buttonLeftDown = atlas.getDrawable("test-utils-button-left-down"); 36 | buttonLeftOver = atlas.getDrawable("test-utils-button-left-over"); 37 | buttonCenter = atlas.getDrawable("test-utils-button-center"); 38 | buttonCenterDown = atlas.getDrawable("test-utils-button-center-down"); 39 | buttonCenterOver = atlas.getDrawable("test-utils-button-center-over"); 40 | buttonCenterDisabled = atlas.getDrawable("test-utils-button-center-disabled"); 41 | buttonRight = atlas.getDrawable("test-utils-button-right"); 42 | buttonRightDown = atlas.getDrawable("test-utils-button-right-down"); 43 | buttonRightOver = atlas.getDrawable("test-utils-button-right-over"); 44 | paneBottom = atlas.getDrawable("test-utils-pane-bottom"); 45 | 46 | right = new ButtonStyle(Styles.defaultb){{ 47 | up = buttonRight; 48 | down = buttonRightDown; 49 | over = buttonRightOver; 50 | }}; 51 | 52 | round = new TextButtonStyle(Styles.defaultt){{ 53 | checked = up; 54 | }}; 55 | 56 | toggleCentert = new TextButtonStyle(Styles.defaultt){{ 57 | up = buttonCenter; 58 | down = buttonCenterDown; 59 | over = buttonCenterOver; 60 | checked = buttonCenterOver; 61 | disabled = buttonCenterDisabled; 62 | }}; 63 | 64 | tuImageStyle = new ImageButtonStyle(Styles.logici){{ 65 | down = Styles.flatDown; 66 | over = Styles.flatOver; 67 | imageDisabledColor = gray; 68 | imageUpColor = white; 69 | }}; 70 | 71 | togglei = new ImageButtonStyle(Styles.defaulti){{ 72 | checked = Tex.buttonOver; 73 | }}; 74 | 75 | lefti = new ImageButtonStyle(Styles.defaulti){{ 76 | up = buttonLeft; 77 | down = buttonLeftDown; 78 | over = buttonLeftOver; 79 | }}; 80 | 81 | toggleLefti = new ImageButtonStyle(lefti){{ 82 | checked = buttonLeftOver; 83 | }}; 84 | 85 | righti = new ImageButtonStyle(Styles.defaulti){{ 86 | up = buttonRight; 87 | down = buttonRightDown; 88 | over = buttonRightOver; 89 | }}; 90 | 91 | toggleRighti = new ImageButtonStyle(righti){{ 92 | checked = buttonRightOver; 93 | }}; 94 | 95 | centeri = new ImageButtonStyle(Styles.defaulti){{ 96 | up = buttonCenter; 97 | down = buttonCenterDown; 98 | over = buttonCenterOver; 99 | }}; 100 | 101 | tuHoldImageStyle = new HoldImageButtonStyle(tuImageStyle); 102 | 103 | teamChanger = new HoldImageButtonStyle(Styles.clearNoneTogglei){{ 104 | down = Tex.whiteui; 105 | checked = Tex.whiteui; 106 | }}; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/testing/util/Setup.java: -------------------------------------------------------------------------------- 1 | package testing.util; 2 | 3 | import arc.*; 4 | import arc.scene.ui.*; 5 | import arc.scene.ui.layout.*; 6 | import arc.util.*; 7 | import blui.ui.*; 8 | import mindustry.*; 9 | import mindustry.core.*; 10 | import mindustry.game.EventType.*; 11 | import mindustry.gen.*; 12 | import mindustry.mod.Mods.*; 13 | import mindustry.world.*; 14 | import testing.*; 15 | import testing.buttons.*; 16 | import testing.ui.*; 17 | 18 | import static arc.Core.*; 19 | import static mindustry.Vars.*; 20 | 21 | public class Setup{ 22 | public static boolean posLabelAligned = false; 23 | 24 | public static TerrainPainterFragment terrainFrag; 25 | private static Table timeSlider; 26 | 27 | public static void init(){ 28 | TUDialogs.load(); 29 | 30 | BLSetup.addTable(table -> { 31 | if(mobile && settings.getBool("console")){ 32 | table.table(Tex.buttonEdge3, Console::addButtons); 33 | table.row(); 34 | } 35 | table.table(Tex.buttonEdge3, t -> { 36 | Spawn.addButtons(t); 37 | Environment.worldButton(t); 38 | Effect.statusButton(t); 39 | Sandbox.addButtons(t); 40 | }); 41 | table.row(); 42 | 43 | boolean timeControl = timeControlEnabled(); 44 | 45 | table.table(timeControl ? Tex.buttonEdge3 : Tex.pane, t -> { 46 | TeamChanger.addButton(t); 47 | Health.addButtons(t); 48 | Death.addButtons(t); 49 | LightSwitch.lightButton(t); 50 | }); 51 | 52 | if(timeControl){ 53 | table.row(); 54 | table.add(yoinkTimeSlider()); 55 | } 56 | }, () -> !net.client() && !TestUtils.disableCampaign()); 57 | 58 | BLSetup.addTable(table -> { 59 | if(timeControlEnabled()){ 60 | table.add(yoinkTimeSlider()); 61 | } 62 | 63 | table.table(Tex.pane, Death::seppuku); 64 | }, () -> !net.client() && state.isCampaign() && TestUtils.disableCampaign()); 65 | 66 | Table miniPos = ui.hudGroup.find("minimap/position"); 67 | Label pos = miniPos.find("position"); 68 | pos.setText(() -> { 69 | String playerPos = ""; 70 | if(settings.getBool("position")){ 71 | playerPos = player.tileX() + ", " + player.tileY() + "\n"; 72 | if(settings.getBool("tu-wu-coords", true)){ 73 | playerPos += "[accent]" + fix(player.x) + ", " + fix(player.y) + "\n"; 74 | } 75 | } 76 | 77 | int tx = World.toTile(Core.input.mouseWorldX()), 78 | ty = World.toTile(Core.input.mouseWorldY()); 79 | 80 | String cursorPos = ""; 81 | if(settings.getBool("mouseposition")){ 82 | cursorPos = "[lightgray]" + tx + ", " + ty + "\n"; 83 | if(settings.getBool("tu-wu-coords", true)){ 84 | cursorPos += "[#d4816b]" + fix(Core.input.mouseWorldX()) + ", " + fix(Core.input.mouseWorldY()) + "\n"; 85 | } 86 | 87 | if(settings.getBool("tu-tile-info", false)){ 88 | Tile tile = world.tile(tx, ty); 89 | cursorPos += "[#a9d8ff]"; 90 | if(tile == null){ 91 | cursorPos += "-----"; 92 | }else{ 93 | cursorPos += tile.floor().localizedName 94 | + " | " + tile.overlay().localizedName 95 | + " | " + tile.block().localizedName 96 | + " | data = "; 97 | StringBuilder data = new StringBuilder(); 98 | for(int i = 7; i >= 0; i--){ 99 | data.append((tile.data & (1 << i)) != 0 ? '1' : '0'); 100 | } 101 | cursorPos += data; 102 | } 103 | } 104 | } 105 | 106 | return playerPos + cursorPos; 107 | }); 108 | miniPos.getCell(miniPos.find("minimap")).top().right(); 109 | miniPos.getCell(pos).top().right(); 110 | 111 | terrainFrag = new TerrainPainterFragment(); 112 | Core.app.post(() -> terrainFrag.build(ui.hudGroup)); //Wait for BLUI to set up. 113 | 114 | Events.on(WorldLoadEvent.class, e -> { 115 | if(posLabelAligned) return; 116 | pos.setAlignment(Align.right, Align.right); 117 | posLabelAligned = true; 118 | }); 119 | } 120 | 121 | private static Table yoinkTimeSlider(){ 122 | if(timeSlider == null){ 123 | timeSlider = Vars.ui.hudGroup.find("tc-slidertable"); 124 | timeSlider.visible(() -> true); 125 | 126 | Vars.ui.hudGroup.find("tc-foldedtable").visible(() -> false); 127 | } 128 | return timeSlider; 129 | } 130 | 131 | public static boolean timeControlEnabled(){ 132 | LoadedMod timeControl = Vars.mods.getMod("time-control"); 133 | return timeControl != null && timeControl.isSupported() && timeControl.enabled(); 134 | } 135 | 136 | private static String fix(float f){ 137 | return Strings.autoFixed(f, 1); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/testing/util/TUIcons.java: -------------------------------------------------------------------------------- 1 | package testing.util; 2 | 3 | import arc.*; 4 | import arc.scene.style.*; 5 | import mindustry.content.*; 6 | 7 | public class TUIcons{ 8 | public static TextureRegionDrawable 9 | clone, seppuku, 10 | core, dump, 11 | survival, sandbox, 12 | heal, invincibility, 13 | weather, 14 | sounds, musics, stop, 15 | lightOff, lightOn, 16 | alpha; 17 | 18 | public static void init(){ 19 | clone = get("clone"); 20 | seppuku = get("seppuku"); 21 | core = get("core"); 22 | dump = get("dump"); 23 | survival = get("survival"); 24 | sandbox = get("sandbox"); 25 | heal = get("heal"); 26 | invincibility = get("invincibility"); 27 | weather = get("weather"); 28 | sounds = get("sounds"); 29 | musics = get("musics"); 30 | stop = get("stop"); 31 | lightOff = get("light-off"); 32 | lightOn = get("light-on"); 33 | alpha = new TextureRegionDrawable(UnitTypes.alpha.uiIcon); 34 | } 35 | 36 | static TextureRegionDrawable get(String name){ 37 | return new TextureRegionDrawable(Core.atlas.find("test-utils-" + name)); 38 | } 39 | 40 | public static TextureRegionDrawable get(TextureRegionDrawable icon){ 41 | return new TextureRegionDrawable(icon); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/testing/util/TUSettings.java: -------------------------------------------------------------------------------- 1 | package testing.util; 2 | 3 | import arc.scene.style.*; 4 | import arc.scene.ui.*; 5 | import arc.scene.ui.layout.*; 6 | import arc.scene.utils.*; 7 | import arc.util.*; 8 | import blui.*; 9 | import mindustry.game.*; 10 | import mindustry.gen.*; 11 | import mindustry.ui.dialogs.SettingsMenuDialog.*; 12 | import mindustry.ui.dialogs.SettingsMenuDialog.SettingsTable.*; 13 | import mindustry.world.meta.*; 14 | 15 | import static arc.Core.*; 16 | import static mindustry.Vars.*; 17 | import static testing.ui.TUDialogs.*; 18 | 19 | public class TUSettings{ 20 | public static void init(){ 21 | ui.settings.addCategory(bundle.get("setting.tu-title"), "test-utils-settings-icon", t -> { 22 | t.pref(new Banner("test-utils-settings-banner", -1)); 23 | t.checkPref("tu-instakill", true); 24 | t.checkPref("tu-death-effect", true); 25 | t.checkPref("tu-despawns", true); 26 | t.checkPref("tu-permanent", false); 27 | t.checkPref("tu-show-hidden", false); 28 | t.checkPref("tu-fill-all", false); 29 | t.checkPref("tu-wu-coords", true); 30 | t.checkPref("tu-tile-info", false); 31 | t.pref(new TeamSetting("tu-default-team")); 32 | t.pref(new Separator(8)); 33 | t.pref(new ButtonSetting("tu-interp", TUIcons.get(Icon.line), () -> interpDialog.show())); 34 | t.sliderPref("tu-lerp-time", 8, 0, 40, s -> Strings.autoFixed(s / 4f, 2) + " " + StatUnit.seconds.localized()); 35 | t.pref(new Separator(8)); 36 | t.pref(new ButtonSetting("tu-sounds", TUIcons.get(Icon.effect), () -> soundDialog.show())); 37 | t.checkPref("tu-music-enabled", false); 38 | t.checkPref("tu-allow-filters", false); 39 | 40 | if(OS.username.startsWith("MEEP")){ 41 | t.pref(new Separator(8)); 42 | t.checkPref("tu-mobile-test", false); 43 | } 44 | }); 45 | 46 | if(mobile) ui.settings.game.checkPref("console", true); 47 | } 48 | 49 | /** Not a setting, but rather adds an image to the settings menu. */ 50 | static class Banner extends Setting{ 51 | float width; 52 | 53 | public Banner(String name, float width){ 54 | super(name); 55 | this.width = width; 56 | } 57 | 58 | @Override 59 | public void add(SettingsTable table){ 60 | Image i = new Image(new TextureRegionDrawable(atlas.find(name)), Scaling.fit); 61 | Cell ci = table.add(i).padTop(3f); 62 | 63 | if(width > 0){ 64 | ci.width(width); 65 | }else{ 66 | ci.grow(); 67 | } 68 | 69 | table.row(); 70 | } 71 | } 72 | 73 | /** Not a setting, but rather a space between settings. */ 74 | static class Separator extends Setting{ 75 | float height; 76 | 77 | public Separator(float height){ 78 | super(""); 79 | this.height = height; 80 | } 81 | 82 | @Override 83 | public void add(SettingsTable table){ 84 | table.image(Tex.clear).height(height).padTop(3f); 85 | table.row(); 86 | } 87 | } 88 | 89 | /** Not a setting, but rather a button in the settings menu. */ 90 | static class ButtonSetting extends Setting{ 91 | Drawable icon; 92 | Runnable listener; 93 | 94 | public ButtonSetting(String name, Drawable icon, Runnable listener){ 95 | super(name); 96 | this.icon = icon; 97 | this.listener = listener; 98 | } 99 | 100 | @Override 101 | public void add(SettingsTable table){ 102 | ImageButton b = Elem.newImageButton(icon, listener); 103 | b.resizeImage(BLVars.iconSize); 104 | b.label(() -> title).padLeft(6).growX(); 105 | b.left(); 106 | 107 | addDesc(table.add(b).left().padTop(3f).get()); 108 | table.row(); 109 | } 110 | } 111 | 112 | static class TeamSetting extends Setting{ 113 | public TeamSetting(String name){ 114 | super(name); 115 | } 116 | 117 | @Override 118 | public void add(SettingsTable table){ 119 | ImageButton b = table.button(TUIcons.get(Icon.defense), BLVars.iconSize, () -> teamDialog.show(getTeam(), team -> settings.put("tu-default-team", team.id))).left().padTop(3f).get(); 120 | b.label(() -> bundle.format("setting." + name + ".name", "[#" + getTeam().color + "]" + teamDialog.teamName(getTeam()) + "[]")).padLeft(6).growX(); 121 | table.row(); 122 | 123 | addDesc(b); 124 | } 125 | 126 | public Team getTeam(){ 127 | return Team.get(settings.getInt("tu-default-team", Team.sharded.id)); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/testing/util/TUVars.java: -------------------------------------------------------------------------------- 1 | package testing.util; 2 | 3 | import arc.*; 4 | import arc.util.*; 5 | import mindustry.core.*; 6 | import mindustry.game.*; 7 | import testing.dialogs.*; 8 | import testing.editor.*; 9 | 10 | public class TUVars{ 11 | public static Team curTeam = Team.sharded; 12 | public static TUBaseDialog activeDialog; 13 | public static TerrainPainter painter = new TerrainPainter(); 14 | public static TerrainPaintbrush paintbrush = new TerrainPaintbrush(); 15 | public static boolean foos = Structs.contains(Version.class.getDeclaredFields(), var -> var.getName().equals("foos")); 16 | 17 | /** Delta time that is unaffected by time control. */ 18 | public static float delta(){ 19 | return Core.graphics.getDeltaTime() * 60; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/testing/util/Utils.java: -------------------------------------------------------------------------------- 1 | package testing.util; 2 | 3 | import arc.scene.*; 4 | import arc.util.*; 5 | import mindustry.core.*; 6 | import mindustry.game.*; 7 | import testing.content.*; 8 | 9 | import static arc.Core.*; 10 | import static mindustry.Vars.*; 11 | 12 | public class Utils{ 13 | public static void spawnIconEffect(String sprite){ 14 | TUFx.iconEffect.at(player.x, player.y, 0, "test-utils-" + sprite); 15 | } 16 | 17 | public static String round(float f){ 18 | if(f >= 1_000_000_000){ 19 | return Strings.autoFixed(f / 1_000_000_000, 1) + UI.billions; 20 | }else if(f >= 1_000_000){ 21 | return Strings.autoFixed(f / 1_000_000, 1) + UI.millions; 22 | }else if(f >= 1000){ 23 | return Strings.autoFixed(f / 1000, 1) + UI.thousands; 24 | }else{ 25 | return (int)f + ""; 26 | } 27 | } 28 | 29 | public static int countSpawns(SpawnGroup group){ 30 | if(group.spawn != -1) return 1; //If the group has a set spawn pos, assume it's a valid position and count it as 1 spawn. 31 | 32 | //Otherwise count all. 33 | if(group.type.flying){ 34 | return spawner.countFlyerSpawns(); 35 | } 36 | return spawner.countGroundSpawns(); 37 | } 38 | 39 | public static boolean hasMouse(){ 40 | Element e = scene.hit(input.mouseX(), input.mouseY(), false); 41 | return e != null && !e.fillParent; 42 | } 43 | } 44 | --------------------------------------------------------------------------------