├── .github ├── ISSUE_TEMPLATE │ └── bug-report-or-crash.md └── workflows │ └── gradle.yml ├── .gitignore ├── build.sh ├── dependencies.sh ├── mod.hjson ├── servers-claj.hjson └── src ├── java ├── schema │ ├── Main.java │ ├── Updater.java │ ├── input │ │ ├── DesktopInput.java │ │ ├── InputSystem.java │ │ ├── Keybind.java │ │ ├── Keymask.java │ │ └── Rotatable.java │ ├── tools │ │ ├── Builds.java │ │ ├── Overlay.java │ │ └── Units.java │ └── ui │ │ ├── Style.java │ │ ├── dialogs │ │ ├── BaseDialog.java │ │ └── KeybindDialog.java │ │ ├── fragments │ │ ├── CommandFragment.java │ │ ├── ConfigFragment.java │ │ ├── LoadingFragment.java │ │ └── MapFragment.java │ │ ├── hud │ │ ├── CoreInfo.java │ │ ├── HudFragment.java │ │ ├── UnitInfo.java │ │ └── WaveInfo.java │ │ └── polygon │ │ ├── BlockPolygon.java │ │ └── Polygon.java └── scheme │ ├── ClajIntegration.java │ ├── Main.java │ ├── SchemeVars.java │ ├── ServerIntegration.java │ ├── ai │ ├── GammaAI.java │ └── NetMinerAI.java │ ├── moded │ ├── ModedDesktopInput.java │ ├── ModedGlyphLayout.java │ ├── ModedInputHandler.java │ ├── ModedMobileInput.java │ └── ModedSchematics.java │ ├── tools │ ├── BuildingTools.java │ ├── ImageParser.java │ ├── MessageQueue.java │ ├── RainbowTeam.java │ ├── RendererTools.java │ └── admins │ │ ├── AdminsTools.java │ │ ├── Darkdustry.java │ │ ├── Internal.java │ │ └── SlashJs.java │ └── ui │ ├── ConsoleFragment.java │ ├── CoreInfoFragment.java │ ├── FlipButton.java │ ├── HexBar.java │ ├── HudFragment.java │ ├── List.java │ ├── MapResizeFix.java │ ├── PlayerListFragment.java │ ├── PowerBars.java │ ├── ShortcutFragment.java │ ├── TextSlider.java │ └── dialogs │ ├── AISelectDialog.java │ ├── AdminsConfigDialog.java │ ├── ContentSelectDialog.java │ ├── ImageParserDialog.java │ ├── JoinViaClajDialog.java │ ├── ListDialog.java │ ├── ManageRoomsDialog.java │ ├── RendererConfigDialog.java │ ├── SchemasDialog.java │ ├── SettingsMenuDialog.java │ ├── TagSelectDialog.java │ ├── TeamSelectDialog.java │ ├── TileSelectDialog.java │ └── WaveApproachingDialog.java └── resources ├── bundles ├── bundle.properties ├── bundle_ja.properties ├── bundle_ru.properties ├── bundle_uk_UA.properties ├── bundle_vi.properties └── bundle_zh_CN.properties ├── icon.png ├── scripts └── main.js ├── sprites-override └── status-invincible.png └── sprites ├── button-disabled.png ├── button-down.png ├── button-over.png ├── button.png ├── panel-bottom.png ├── panel-clear.png ├── panel-top.png ├── panel.png └── scroll-knob.png /.github/ISSUE_TEMPLATE/bug-report-or-crash.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report or crash 3 | about: Some steps to help me solve the problem faster 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A brief description of the bug and what you expected to see. 12 | If it is a crash while loading the game, **attach the crash log** instead of a description. 13 | 14 | **To Reproduce** 15 | As short as possible, make sure there are no extra steps. 16 | This can be skipped if your problem is a crash **during loading**. 17 | 18 | **System information** 19 | The most important thing is your operating system, game and mod version. 20 | All this information is present in the crash log, so there is no need to duplicate it. 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, workflow_dispatch] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Check out the repo 10 | uses: actions/checkout@v4 11 | 12 | - name: Set up PATH 13 | run: echo $ANDROID_HOME/build-tools/34.0.0 >> $GITHUB_PATH 14 | 15 | - name: Set up JDK 16 | uses: actions/setup-java@v4 17 | with: 18 | distribution: temurin 19 | java-version: 17 20 | 21 | - name: Change permissions 22 | run: chmod +x build.sh dependencies.sh 23 | 24 | - name: Download dependencies 25 | run: ./dependencies.sh dependencies-only 26 | 27 | - name: Build the project 28 | run: ./build.sh mobile 29 | 30 | - name: Upload built jar file 31 | uses: actions/upload-artifact@v4 32 | with: 33 | name: Schema 34 | path: build/Schema.jar 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/* 2 | .settings/* 3 | 4 | bin/* 5 | lib/* 6 | build/* 7 | 8 | *.classpath 9 | *.jar 10 | *.dex 11 | *.xml 12 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | b="\033[1;32m" 4 | n="\033[0;39m" 5 | 6 | project="Schema" 7 | 8 | 9 | 10 | [ "$1" != "desktop" ] && [ "$1" != "mobile" ] && echo -e "Use either$b desktop$n or$b mobile$n as an argument" && exit 1 11 | 12 | echo -e "=>$b Building desktop jar...$n" 13 | 14 | 15 | 16 | echo "Compiling the source code" 17 | 18 | rm -r bin 19 | mkdir bin 20 | 21 | lib=$(find lib -type f -name *.jar -print | paste -sd:) 22 | src=$(find src -type f -name *.java -print | paste -s) 23 | 24 | javac --release 16 --class-path $lib -d bin $src 25 | 26 | 27 | 28 | echo "Archiving the class files and resources" 29 | 30 | rm -r build 31 | mkdir build 32 | 33 | jar --create --file build/$project.jar -C bin . 34 | jar --update --file build/$project.jar -C src/resources . 35 | jar --update --file build/$project.jar mod.hjson 36 | 37 | 38 | 39 | [ "$1" != "mobile" ] && exit 0 40 | 41 | echo -e "=>$b Building mobile jar...$n" 42 | 43 | 44 | 45 | pf=$ANDROID_HOME/platforms 46 | 47 | lib=$(find lib -type f -name *.jar -print | sed -e "s/^/--classpath /" | paste -s) 48 | cls=$(find bin -type f -name *.class -print | paste -s) 49 | jar=$(find $pf -type f -name android.jar -print | sort --reverse | head --lines=1) 50 | 51 | echo "Found android.jar in $(dirname $jar)" 52 | 53 | d8 $lib --lib $jar --output bin $cls 54 | 55 | jar --update --file build/$project.jar -C bin classes.dex 56 | -------------------------------------------------------------------------------- /dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | b="\033[1;32m" 4 | n="\033[0;39m" 5 | 6 | jitpack="https://jitpack.io" 7 | 8 | 9 | 10 | [ "$1" != "dependencies-only" ] && [ "$1" != "sources-too" ] && echo -e "Use either$b dependencies-only$n or$b sources-too$n as an argument" && exit 1 11 | 12 | echo -e "=>$b Downloading dependencies...$n" 13 | 14 | 15 | 16 | rm -r lib 17 | mkdir lib 18 | 19 | function download 20 | { 21 | echo "Downloading $1/$2" 22 | wget --tries=3 --timeout=3 --quiet -P lib $jitpack/com/github/$1/$2/$3/$2-$3$end 23 | } 24 | 25 | end=".jar" 26 | download Anuken/Arc arc-core v146 27 | download Anuken/Arc arcnet v146 28 | download Anuken/Mindustry core v146 29 | download Anuken rhino 73a812444ac388ac2d94013b5cadc8f70b7ea027 30 | 31 | 32 | 33 | [ "$1" != "sources-too" ] && exit 0 34 | 35 | echo -e "=>$b Downloading sources...$n" 36 | 37 | 38 | 39 | end="-sources.jar" 40 | download Anuken/Arc arc-core v146 41 | download Anuken/Arc arcnet v146 42 | download Anuken/Mindustry core v146 43 | -------------------------------------------------------------------------------- /mod.hjson: -------------------------------------------------------------------------------- 1 | name: schema 2 | displayName: Schema 3 | subtitle: No longer just increases the schema size 4 | 5 | author: "[#0096FF]xzxADIxzx" 6 | version: 3.0.0 7 | 8 | description: 9 | "A mod that started out as a desire to increase the schematic size limit, but has evolved into a whole set of quality of life features and tools. 10 | [red] 11 | It may not be compatible with other mods. Therefore, it's recommended to enable it only in vanilla game. 12 | [orange] 13 | - Flexible control settings 14 | - Building tools 15 | - Shortcuts for schematics 16 | - Display of additional info 17 | - Advanced developer console 18 | - Copy Link and Join" 19 | 20 | minGameVersion: 146 21 | main: schema.Main 22 | hidden: true 23 | -------------------------------------------------------------------------------- /servers-claj.hjson: -------------------------------------------------------------------------------- 1 | // List of CLaJ servers that is displayed in the corresponding dialog 2 | // Each server is listed in two forms: domain:port aka URL and ip:port 3 | [ 4 | "claj://n3.xpdustry.com:7025", 5 | "claj://37.187.73.180:7025", 6 | "claj://uk1.eradication.fun:7025", 7 | "claj://in3.eradication.fun:7025", 8 | "claj://us1.eradication.fun:7025", 9 | "claj://hk1.eradication.fun:7025" 10 | ] 11 | -------------------------------------------------------------------------------- /src/java/schema/Main.java: -------------------------------------------------------------------------------- 1 | package schema; 2 | 3 | import arc.*; 4 | import arc.struct.*; 5 | import arc.util.*; 6 | import mindustry.mod.*; 7 | import schema.input.*; 8 | import schema.tools.*; 9 | import schema.ui.*; 10 | import schema.ui.dialogs.*; 11 | import schema.ui.fragments.*; 12 | import schema.ui.hud.*; 13 | import schema.ui.polygon.*; 14 | 15 | import static arc.Core.*; 16 | import static mindustry.Vars.*; 17 | 18 | /** Main class of the mod that loads, initializes and stores different components of it. */ 19 | public class Main extends Mod { 20 | 21 | // region components 22 | 23 | /** Combines the vanilla and schema overlay. */ 24 | public static Overlay overlay; 25 | /** Utility that helps with buildings. */ 26 | public static Builds builds; 27 | /** Utility that helps with units. */ 28 | public static Units units; 29 | 30 | /** Advanced input system lying in the foundation of the mod. */ 31 | public static InputSystem insys; 32 | 33 | /** List of servers' URLs that host Copy-Link-and-Join. */ 34 | public static Seq clajURLs = Seq.with("Couldn't fetch CLaJ URLs :("); 35 | /** List of {@link Events events} acquired via reflection. */ 36 | public static ObjectMap> events; 37 | 38 | // endregion 39 | // region dialogs 40 | 41 | public static KeybindDialog keybind; 42 | 43 | // endregion 44 | // region fragments 45 | 46 | // public static InventoryFragment inv; 47 | public static ConfigFragment config; 48 | 49 | public static HudFragment hudfrag; 50 | public static MapFragment mapfrag; 51 | public static CommandFragment cmndfrag; 52 | public static LoadingFragment loadfrag; 53 | 54 | public static Polygon polyplace; 55 | public static BlockPolygon polyblock; 56 | public static Polygon polyschema; 57 | 58 | // endregion 59 | 60 | public Main() { 61 | // this field is no longer final since 136th build 62 | maxSchematicSize = 128; 63 | } 64 | 65 | @Override 66 | public void init() { 67 | log("=> [green]Loading content..."); 68 | load(); 69 | 70 | log("=> [green]Initializing content..."); 71 | 72 | keybind.load(); 73 | keybind.resolve(); 74 | 75 | config.build(ui.hudGroup); 76 | 77 | hudfrag.build(ui.hudGroup); 78 | mapfrag.build(ui.hudGroup); 79 | cmndfrag.build(ui.hudGroup); 80 | loadfrag.build(scene.root); 81 | 82 | polyplace.build(ui.hudGroup); 83 | polyblock.build(ui.hudGroup); 84 | polyschema.build(ui.hudGroup); 85 | 86 | control.setInput(insys.getAgent()); 87 | // TODO hudfrag agent 88 | ui.minimapfrag=mapfrag.getAgent(); 89 | ui.loadfrag = loadfrag.getAgent(); 90 | 91 | Reflect.set(renderer, "overlays", overlay.getAgent()); 92 | // TODO override inventory too 93 | Reflect.set(mindustry.input.InputHandler.class, control.input, "config", config.getAgent()); 94 | 95 | log("=> [green]Running postinit hooks..."); 96 | 97 | Updater.load(); 98 | Updater.fetch(); 99 | 100 | try { // run the main script without wrapper to make constants available in the dev console 101 | Scripts scripts = mods.getScripts(); 102 | scripts.context.evaluateReader(scripts.scope, Updater.script().reader(), "main.js", 0); 103 | 104 | log("Added constants to the dev console"); 105 | } catch (Throwable e) { err(e); } 106 | 107 | log("=> [green]Unhooking events..."); 108 | clear(mindustry.graphics.OverlayRenderer.class); 109 | clear(mindustry.input.InputHandler.class); 110 | clear(mindustry.ui.fragments.HudFragment.class); 111 | clear(mindustry.ui.fragments.PlacementFragment.class); 112 | } 113 | 114 | /** Loads content such as dialogs, fragments and so on. */ 115 | public void load() { 116 | // these styles are used for building dialogs and fragments and thus are loaded here 117 | Style.load(); 118 | 119 | overlay = new Overlay(); 120 | builds = new Builds(); 121 | units = new Units(); 122 | 123 | insys = mobile ? null : new DesktopInput(); 124 | 125 | keybind = new KeybindDialog(); 126 | 127 | config = new ConfigFragment(); 128 | 129 | hudfrag = new HudFragment(); 130 | mapfrag = new MapFragment(); 131 | cmndfrag = new CommandFragment(); 132 | loadfrag = new LoadingFragment(); 133 | 134 | polyplace = new Polygon(); 135 | polyblock = new BlockPolygon(); 136 | polyschema = new Polygon(); 137 | } 138 | 139 | /** Prints the given info in the dev console. */ 140 | public static void log(String info) { app.post(() -> Log.infoTag("Schema", info)); } 141 | 142 | /** Prints the given error in the dev console. */ 143 | public static void err(Throwable e) { app.post(() -> Log.errTag("Schema", Strings.getStackTrace(e))); } 144 | 145 | /** Copies the given text to the clipboard. */ 146 | public static void copy(String text) { 147 | if (text == null) return; 148 | 149 | app.setClipboardText(text); 150 | ui.showInfoFade("@copied"); 151 | } 152 | 153 | /** Returns the character of the icon with the given name. */ 154 | public static char icon(String name) { return (char) mindustry.ui.Fonts.getUnicode(name); } 155 | 156 | /** Use this extremely carefully as it clears event listeners created by the given class. */ 157 | public static void clear(Class target) { 158 | if (events == null) events = Reflect.get(Events.class, "events"); 159 | 160 | int count = 0; 161 | for (var pair : events) count += pair.value.size - pair.value.removeAll(l -> l.toString().startsWith(target.getName())).size; 162 | log("Found [red]" + count + "[] events in " + target.getSimpleName()); 163 | } 164 | 165 | /** Returns the given number with fixed amount of decimal places. */ 166 | public static String format(float num, boolean flow) { 167 | if (num >= 100_000_000_000f) 168 | return Strings.fixed(num / 1_000_000_000f, 0) + "[light]b"; 169 | if (num >= 1_000_000_000f) 170 | return Strings.fixed(num / 1_000_000_000f, 1) + "[light]b"; 171 | 172 | if (num >= 100_000_000f) 173 | return Strings.fixed(num / 1_000_000f, 0) + "[light]m"; 174 | if (num >= 1_000_000f) 175 | return Strings.fixed(num / 1_000_000f, 1) + "[light]m"; 176 | 177 | if (num >= 100_000f) 178 | return Strings.fixed(num / 1_000f, 0) + "[light]k"; 179 | if (num >= 1_000f) 180 | return Strings.fixed(num / 1_000f, 1) + "[light]k"; 181 | 182 | return Strings.fixed(num, flow ? 1 : 0); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/java/schema/Updater.java: -------------------------------------------------------------------------------- 1 | package schema; 2 | 3 | import arc.files.*; 4 | import arc.util.*; 5 | import arc.util.serialization.*; 6 | import mindustry.mod.Mods.*; 7 | 8 | import static arc.Core.*; 9 | import static mindustry.Vars.*; 10 | import static schema.Main.*; 11 | 12 | /** Class that handles interactions with the repository of the mod. This includes checking for updates and fetching CLaJ URLs. */ 13 | public class Updater { 14 | 15 | /** Repository of the mod that... has the old name, but what can I do? */ 16 | public static final String repo = "xzxADIxzx/Scheme-Size"; 17 | /** Entry of the mod loader that represents this mod. */ 18 | public static LoadedMod mod; 19 | 20 | /** Loads the entry of the mod and its meta data. */ 21 | public static void load() { 22 | mod = mods.getMod(Main.class); 23 | 24 | // restore colors from the meta data file of the mod 25 | var meta = Jval.read(meta().reader()); 26 | mod.meta.author = meta.getString("author"); 27 | mod.meta.description = meta.getString("description"); 28 | } 29 | 30 | /** Fetches the latest version of the mod and CLaJ URLs from GitHub. */ 31 | public static void fetch() { 32 | Http.get(ghApi + "/repos/" + repo + "/releases/latest", res -> { 33 | 34 | var latest = Jval.read(res.getResult()).getString("tag_name").substring(1); 35 | if (latest.equals(mod.meta.version)) 36 | log("The mod is up to date"); 37 | else { 38 | log("The mod is outdated"); 39 | ui.showCustomConfirm("@update.name", bundle.format("update.info", mod.meta.version, latest), "@mods.browser.reinstall", "@ok", Updater::update, () -> {}); 40 | } 41 | 42 | }, Main::err); 43 | Http.get("https://raw.githubusercontent.com/" + repo + "/main/servers-claj.hjson", res -> { 44 | 45 | clajURLs = Jval.read(res.getResult()).asArray().map(Jval::asString); 46 | log("Fetched " + clajURLs.size + " CLaJ URLs"); 47 | 48 | }, Main::err); 49 | } 50 | 51 | /** Downloads the latest version of the mod from GitHub. */ 52 | public static void update() { ui.mods.githubImportMod(repo, true); } 53 | 54 | /** Returns the file that contains the meta data of the mod. */ 55 | public static Fi meta() { return mod.root.child("mod.hjson"); } 56 | 57 | /** Returns the file that contains the main script of the mod. */ 58 | public static Fi script() { return mod.root.child("scripts").child("main.js"); } 59 | } 60 | -------------------------------------------------------------------------------- /src/java/schema/input/Keymask.java: -------------------------------------------------------------------------------- 1 | package schema.input; 2 | 3 | import arc.func.*; 4 | 5 | import static arc.Core.*; 6 | 7 | /** Keymasks are used for advanced keybinds that require their mask key to be pressed alongside with the primary key. */ 8 | public enum Keymask { 9 | 10 | // region masks 11 | 12 | unset(() -> true), 13 | shift(input::shift), 14 | ctrl(input::ctrl), 15 | alt(input::alt); 16 | 17 | // endregion 18 | 19 | /** List of all keymasks used for loading. */ 20 | public static final Keymask[] all = values(); 21 | /** List of formatted names of the keymasks. */ 22 | public static final String[] names = { "[disabled]unset", "Shift", "Ctrl", "Alt" }; 23 | 24 | /** Whether the mask is held down. */ 25 | public Boolp down; 26 | 27 | private Keymask(Boolp down) { this.down = down; } 28 | } 29 | -------------------------------------------------------------------------------- /src/java/schema/input/Rotatable.java: -------------------------------------------------------------------------------- 1 | package schema.input; 2 | 3 | import arc.math.*; 4 | import arc.math.geom.*; 5 | import mindustry.entities.units.*; 6 | import mindustry.gen.*; 7 | 8 | import static mindustry.Vars.*; 9 | 10 | /** Represents a block that can be rotated. It can be either a building or plan. */ 11 | public class Rotatable implements Position { 12 | 13 | private Building build; 14 | private BuildPlan plan; 15 | 16 | /** Position of the tile that contains the block or its center. */ 17 | public int x, y; 18 | 19 | public Rotatable(Building build, BuildPlan plan) { 20 | if (plan != null) 21 | this.plan = plan; 22 | else 23 | this.build = build; 24 | 25 | x = plan != null ? plan.x : build != null ? build.tileX() : -1; 26 | y = plan != null ? plan.y : build != null ? build.tileY() : -1; 27 | } 28 | 29 | /** Whether the block is valid to rotate or not. */ 30 | public boolean valid() { return (plan != null && plan.block.rotate) || (build != null && build.block.rotate && build.team == player.team()); } 31 | 32 | /** Returns the radius of the block. */ 33 | public float radius() { 34 | if (plan != null) 35 | return plan.block.size * Mathf.sqrt2 * 4f; 36 | else 37 | return build.hitSize() * Mathf.sqrt2 / 2f; 38 | } 39 | 40 | /** Rotates the block by the given scroll. */ 41 | public void rotateBy(int scroll) { 42 | if (plan != null) 43 | plan.rotation = Mathf.mod(plan.rotation + scroll, 4); 44 | else 45 | Call.rotateBlock(player, build, scroll > 0); 46 | } 47 | 48 | /** Rotates the block to the given direction. */ 49 | public void rotateTo(int dir) { 50 | if (plan != null) 51 | plan.rotation = dir; 52 | else { 53 | boolean j = build.rotation < dir; 54 | for (int i = build.rotation; i != dir; i += Mathf.sign(j)) Call.rotateBlock(player, build, j); 55 | } 56 | } 57 | 58 | @Override 59 | public float getX() { return x * tilesize; } 60 | 61 | @Override 62 | public float getY() { return y * tilesize; } 63 | } 64 | -------------------------------------------------------------------------------- /src/java/schema/tools/Builds.java: -------------------------------------------------------------------------------- 1 | package schema.tools; 2 | 3 | import arc.func.*; 4 | import arc.math.*; 5 | import arc.struct.*; 6 | import arc.util.*; 7 | import mindustry.gen.*; 8 | import mindustry.world.*; 9 | import mindustry.world.blocks.distribution.ItemBridge.*; 10 | 11 | import static mindustry.Vars.*; 12 | 13 | /** Utility class that helps iterate buildings. */ 14 | public class Builds { 15 | 16 | /** List of buildings that was iterated. */ 17 | private Seq iterated = new Seq<>(); 18 | /** List of visible tiles acquired via reflection. Contains only the tiles with buildings or boulders on them. */ 19 | private Seq tiles; 20 | 21 | /** Clears the list of iterated buildings. Call it before iterating anything. */ 22 | public void clearIterated() { iterated.clear(); } 23 | 24 | /** Iterates visible tiles with buildings on them. */ 25 | public void iterateBuilds(Cons cons) { 26 | if (tiles == null) tiles = Reflect.get(renderer.blocks, "tileview"); 27 | tiles.each(t -> t.build != null, cons); 28 | } 29 | 30 | /** Iterates tiles around the given core and connected storages. */ 31 | public void iterateCore(Building build, Cons2 cons) { 32 | 33 | iterated.add(build); 34 | build.proximity.each(p -> p.items == build.items && !iterated.contains(p), p -> iterateCore(p, cons)); 35 | 36 | int bdx = build.tileX(), 37 | bdy = build.tileY(), 38 | min = (build.block.size - 1) / 2, 39 | max = (build.block.size / 2); 40 | 41 | for (int i = -min; i <= max; i++) { // there used to be complex logic here, but it was simplified 42 | 43 | cons.get(world.tile(bdx + i, bdy + max + 1), 1); // top 44 | cons.get(world.tile(bdx + i, bdy - min - 1), 3); // bottom 45 | 46 | cons.get(world.tile(bdx + max + 1, bdy + i), 0); // right 47 | cons.get(world.tile(bdx - min - 1, bdy + i), 2); // left 48 | } 49 | } 50 | 51 | /** Iterates the given and consequentially connected bridges. */ 52 | public void iterateBridge(ItemBridgeBuild build, Cons cons) { 53 | 54 | // limit the amount of bridges to iterate 55 | if (iterated.add(build).size > 16) return; 56 | 57 | var linked = world.build(build.link); 58 | if (linked instanceof ItemBridgeBuild b && !iterated.contains(b)) iterateBridge(b, cons); 59 | 60 | cons.get(build); 61 | } 62 | 63 | /** Calculates some values needed to draw a health bar for the given building. */ 64 | public void healthBar(Building build, float radius, boolean inner, Cons4 context) { 65 | 66 | // single block builds have smaller health and status bars 67 | float multiplier = build.block.size > 1 ? 1f : .64f; 68 | 69 | // the radius is taken from status indicator, but it is rotated by 45 degrees 70 | radius *= multiplier * Mathf.sqrt2; 71 | 72 | // padding from the edges of the building 73 | float padding = 4f * multiplier - radius; 74 | 75 | context.get(inner ? radius + multiplier * (Mathf.sqrt2 - 1) : radius, build.hitSize() / 2f - padding, build.x, build.y - build.hitSize() / 2f + padding + radius); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/java/schema/tools/Overlay.java: -------------------------------------------------------------------------------- 1 | package schema.tools; 2 | 3 | import arc.func.*; 4 | import arc.graphics.g2d.*; 5 | import arc.math.*; 6 | import arc.util.*; 7 | import mindustry.graphics.*; 8 | import mindustry.world.meta.*; 9 | import schema.input.*; 10 | 11 | import static arc.Core.*; 12 | import static mindustry.Vars.*; 13 | import static schema.Main.*; 14 | 15 | /** Component that renders overlay elements: command and control mode, blocks configuration, objectives and so on. */ 16 | public class Overlay { 17 | 18 | /** Distance from which spawners are visible. */ 19 | public static final float spawnerMargin = 16f * tilesize; 20 | /** Interpolation function applied to alpha. */ 21 | public static final Interp i = new Interp.PowIn(9f); 22 | 23 | /** Alpha value of certain overlay elements. */ 24 | public float fade; 25 | /** Whether certain overlay elements should be drawn or not. */ 26 | public boolean ruler, borderless; 27 | 28 | public Overlay() { 29 | renderer.addEnvRenderer(Env.none, () -> Draw.draw(Layer.turret + 1f, () -> { 30 | if (Keybind.display_xray.down()) drawXray(); 31 | })); 32 | renderer.addEnvRenderer(Env.none, () -> Draw.draw(Layer.power + 1f, () -> { 33 | if (settings.getBool("blockhealth", false)) drawBars(); 34 | })); 35 | } 36 | 37 | /** Draws the elements of both vanilla and schema overlay */ 38 | public void draw() { 39 | state.rules.objectives.eachRunning(o -> { 40 | for (var marker : o.markers) marker.draw(); 41 | }); 42 | 43 | insys.drawOverlay(); 44 | 45 | if (ruler) { 46 | var r = camera.bounds(Tmp.r1); 47 | var m = input.mouseWorld(); 48 | var x = Mathf.round(m.x + 4f, tilesize); 49 | var y = Mathf.round(m.y + 4f, tilesize); 50 | 51 | Lines.stroke(tilesize, Pal.accent); 52 | capture(.8f, .2f); 53 | 54 | Lines.line(r.x, y, r.x + r.width, y); 55 | Lines.line(x, r.y, x, r.y + r.height); 56 | 57 | render(); 58 | Draw.reset(); 59 | } 60 | 61 | if (state.hasSpawns()) { 62 | Lines.stroke(2f); 63 | Draw.color(Pal.remove, Pal.lightishGray, Mathf.absin(4f, 1f)); 64 | capture(4f); 65 | 66 | spawner.getSpawns().each(s -> s.within(player, state.rules.dropZoneRadius + spawnerMargin), s -> { 67 | 68 | Draw.alpha(1f - i.apply((player.dst(s) - state.rules.dropZoneRadius) / spawnerMargin)); 69 | Lines.dashCircle(s.worldx(), s.worldy(), state.rules.dropZoneRadius); 70 | }); 71 | 72 | render(); 73 | Draw.reset(); 74 | } 75 | 76 | if (config.shown()) config.selected().drawConfigure(); 77 | 78 | if (insys.block == null && !scene.hasMouse()) { 79 | 80 | var build = insys.selectedBuilding(); 81 | if (build != null) { 82 | 83 | config.draw(build); 84 | if (build.block.drawDisabled && !build.enabled) build.drawDisabled(); 85 | } 86 | } 87 | 88 | fade = Mathf.lerpDelta(fade, insys.block != null ? 1f : 0f, .06f); 89 | if (fade < .004f) return; 90 | 91 | Lines.stroke(fade * 2f); 92 | capture(4f); 93 | 94 | if (state.rules.polygonCoreProtection) { 95 | // TODO implement 96 | } else { 97 | state.teams.eachEnemyCore(player.team(), c -> { 98 | 99 | if (!camera.bounds(Tmp.r1).overlaps(Tmp.r2.setCentered(c.x, c.y, state.rules.enemyCoreBuildRadius * 2f))) return; 100 | 101 | Draw.color(Pal.accent, c.team.color, .5f + Mathf.absin(4f, .5f)); 102 | Lines.circle(c.x, c.y, state.rules.enemyCoreBuildRadius); 103 | }); 104 | } 105 | 106 | render(); 107 | Draw.reset(); 108 | } 109 | 110 | /** Draws floors above buildings to display ores under them. */ 111 | public void drawXray() { 112 | builds.iterateBuilds(t -> t.getLinkedTiles(l -> { 113 | Draw.alpha(.8f); 114 | l.floor().drawBase(l); 115 | })); 116 | } 117 | 118 | /** Draws health bars for buildings. The style was taken from {@link mindustry.gen.Building#drawStatus() block status}. */ 119 | public void drawBars() { 120 | Cons4 draw = (x, y, width, height) -> Fill.quad( 121 | x - width, y, 122 | x - width + Math.abs(height), y + height, 123 | x + width - Math.abs(height), y + height, 124 | x + width, y); 125 | 126 | builds.iterateBuilds(t -> { 127 | builds.healthBar(t.build, 2.5f, false, (radius, width, x, y) -> { 128 | Draw.color(Pal.gray); 129 | draw.get(x, y, width, radius); 130 | draw.get(x, y, width, -radius); 131 | }); 132 | builds.healthBar(t.build, 1.5f, true, (radius, width, x, y) -> { 133 | Draw.color(Pal.darkerGray); 134 | draw.get(x, y, width, radius); 135 | draw.get(x, y, width, -radius); 136 | 137 | Draw.color(Pal.remove); 138 | float progress = 2f * width * (1f - t.build.healthf()); 139 | float middle = 2f * (width - radius), l; 140 | 141 | l = Math.max(0, progress / radius); 142 | if (l < 1) Fill.quad( 143 | x + width - radius, y + radius, 144 | x + width - radius * l, y + radius * l, 145 | x + width - radius * l, y - radius * l, 146 | x + width - radius, y - radius); 147 | 148 | l = Math.max(0, (progress - radius) / middle); 149 | if (l < 1) Fill.quad( 150 | x - width + radius, y + radius, 151 | x + width - radius - middle * l, y + radius, 152 | x + width - radius - middle * l, y - radius, 153 | x - width + radius, y - radius); 154 | 155 | l = Math.max(0, (progress - radius - middle) / radius); 156 | if (l < 1) Fill.quad( 157 | x - width, y, 158 | x - width + radius * (1f - l), y + radius * (1f - l), 159 | x - width + radius * (1f - l), y - radius * (1f - l), 160 | x - width, y); 161 | }); 162 | }); 163 | } 164 | 165 | // region bloom 166 | 167 | /** Captures subsequent draw calls. */ 168 | public void capture(float... intensity) { 169 | if (renderer.bloom != null) { 170 | renderer.bloom.capture(); 171 | 172 | if (intensity.length > 0) renderer.bloom.setBloomIntensity(intensity[0]); 173 | if (intensity.length > 1) renderer.bloom.setOriginalIntensity(intensity[1]); 174 | } 175 | } 176 | 177 | /** Renders the {@link #capture(float) captured draw calls} with bloom effect. */ 178 | public void render() { 179 | if (renderer.bloom != null) { 180 | renderer.bloom.render(); 181 | renderer.bloom.setOriginalIntensity(1f); 182 | } 183 | } 184 | 185 | // endregion 186 | // region agent 187 | 188 | /** Returns the agent of this component. */ 189 | public Agent getAgent() { return new Agent(); } 190 | 191 | /** Agent that redirects method calls from the original component to the new one. */ 192 | public class Agent extends OverlayRenderer { 193 | 194 | @Override 195 | public void drawBottom() { insys.drawPlans(); } 196 | 197 | @Override 198 | public void drawTop() { draw(); } 199 | } 200 | 201 | // endregion 202 | } 203 | -------------------------------------------------------------------------------- /src/java/schema/tools/Units.java: -------------------------------------------------------------------------------- 1 | package schema.tools; 2 | 3 | import arc.*; 4 | import arc.func.*; 5 | import arc.struct.*; 6 | import arc.util.*; 7 | import mindustry.content.*; 8 | import mindustry.entities.*; 9 | import mindustry.entities.abilities.*; 10 | import mindustry.game.EventType.*; 11 | import mindustry.gen.*; 12 | import mindustry.type.*; 13 | import mindustry.world.blocks.storage.*; 14 | 15 | import static mindustry.Vars.*; 16 | 17 | /** Utility class that helps fetch info about units. */ 18 | public class Units { 19 | 20 | /** Units that are commonly spawn by core. */ 21 | public Seq coreUnits; 22 | /** Whether the unit of the local player was spawn by core. */ 23 | public boolean coreUnit; 24 | 25 | /** Item capacity of the unit of the local player. */ 26 | public int capacity; 27 | /** Whether to draw units or not. */ 28 | public boolean draw; 29 | 30 | /** Maximum health of the force field. */ 31 | private float maxShield; 32 | /** Current shield or null if absent. */ 33 | private ForceFieldAbility fldShield; 34 | /** Current shield or null if absent. */ 35 | private ShieldArcAbility arcShield; 36 | 37 | /** Summary health and shield of all units on the next wave. */ 38 | public float waveHealth, waveShield; 39 | /** Total amount of units and bosses on the next wave. */ 40 | public ObjectIntMap waveUnits = new ObjectIntMap<>(), waveBosses = new ObjectIntMap<>(); 41 | 42 | public Units() { 43 | Events.on(WorldLoadEvent.class, e -> draw = true); 44 | Events.on(UnitChangeEvent.class, e -> { 45 | if (e.player != player) return; 46 | 47 | coreUnit = player.unit() != null && coreUnits.contains(player.unit().type); 48 | capacity = player.unit() != null ? player.unit().type.itemCapacity : -1; 49 | 50 | maxShield = -1f; 51 | fldShield = null; 52 | arcShield = null; 53 | 54 | if (player.unit() != null) for (var ability : player.unit().abilities) { 55 | 56 | if (ability instanceof ForceFieldAbility fld) { 57 | maxShield = fld.max; 58 | fldShield = fld; 59 | break; 60 | } 61 | if (ability instanceof ShieldArcAbility arc) { 62 | maxShield = arc.max; 63 | arcShield = arc; 64 | break; 65 | } 66 | } 67 | }); 68 | 69 | // the units from this sequence have different movement type 70 | coreUnits = content.blocks().select(b -> b instanceof CoreBlock).as().map(b -> b.unitType); 71 | 72 | // this is, probably, kinda dangerous 73 | Groups.draw = new EntityGroup(Drawc.class, false, false, Reflect.get(Groups.draw, "indexer")) { 74 | 75 | @Override 76 | public void draw(Cons cons) { 77 | if (draw) 78 | super.draw(cons); 79 | else 80 | super.draw(d -> { if (!(d instanceof Unit u) || u.isPlayer()) cons.get(d); }); 81 | } 82 | }; 83 | } 84 | 85 | /** Returns the current health of the force field. */ 86 | public float shield() { return fldShield != null ? player.unit().shield : arcShield != null ? arcShield.data : 0f; } 87 | 88 | /** Returns the percentage health of the force field. */ 89 | public float shieldf() { return shield() / maxShield; } 90 | 91 | /** Refreshes the information about the next wave. */ 92 | public void refreshWaveInfo() { 93 | waveHealth = waveShield = 0f; 94 | waveUnits.clear(); 95 | waveBosses.clear(); 96 | 97 | state.rules.spawns.each(g -> g.type != null, g -> { 98 | int amount = g.getSpawned(state.wave - 1); 99 | if (amount == 0) return; 100 | 101 | waveHealth += g.type.health * amount; 102 | waveShield += g.getShield(state.wave - 1); 103 | 104 | (g.effect == StatusEffects.boss ? waveBosses : waveUnits).put(g.type, amount); 105 | }); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/java/schema/ui/Style.java: -------------------------------------------------------------------------------- 1 | package schema.ui; 2 | 3 | import mindustry.game.*; 4 | import mindustry.gen.*; 5 | import mindustry.graphics.*; 6 | import mindustry.ui.*; 7 | import arc.graphics.*; 8 | import arc.scene.style.*; 9 | import arc.scene.ui.*; 10 | import arc.scene.ui.Button.*; 11 | import arc.scene.ui.ImageButton.*; 12 | import arc.scene.ui.Label.*; 13 | import arc.scene.ui.TextButton.*; 14 | import arc.scene.ui.Tooltip.*; 15 | import arc.scene.ui.ScrollPane.*; 16 | 17 | import static arc.Core.*; 18 | import static schema.Main.*; 19 | 20 | /** List of all styles of the mod, the purpose of which is to modernize the look of the game. */ 21 | public class Style { 22 | 23 | // region styles 24 | 25 | /** Common button: default, empty and toggle variant. */ 26 | public static ButtonStyle cbd, cbe, cbt; 27 | /** Image button: default, empty and toggle variant. */ 28 | public static ImageButtonStyle ibd, ibe, ibt; 29 | /** Text button: default, empty and toggle variant. */ 30 | public static TextButtonStyle tbd, tbe, tbt; 31 | /** Scroll pane style, simple knob without anything else. */ 32 | public static ScrollPaneStyle scr; 33 | /** Special style for command fragment. */ 34 | public static ImageButtonStyle ibc; 35 | /** Label style that uses the outline font. */ 36 | public static LabelStyle outline; 37 | 38 | // endregion 39 | 40 | /** Updates the sprites' splits and loads the styles. */ 41 | public static void load() { 42 | for (var name : new String[] { 43 | "schema-button", 44 | "schema-button-over", 45 | "schema-button-down", 46 | "schema-button-disabled", 47 | "schema-scroll-knob", 48 | "schema-panel", 49 | "schema-panel-top", 50 | "schema-panel-bottom", 51 | "schema-panel-clear" }) 52 | atlas.find(name).splits = name.endsWith("knob") ? new int[] { 0, 0, 24, 16 } : new int[] { 16, 16, 16, 16 }; 53 | 54 | log("Loaded 9 sprites for UI"); 55 | 56 | ibe = new ImageButtonStyle() {{ 57 | over = find("button-over"); 58 | down = find("button-down"); 59 | disabled = find("button-disabled"); 60 | }}; 61 | ibd = new ImageButtonStyle(ibe) {{ up = find("button"); }}; 62 | ibt = new ImageButtonStyle(ibe) {{ checked = find("button-over"); }}; 63 | 64 | tbe = new TextButtonStyle() {{ 65 | over = find("button-over"); 66 | down = find("button-down"); 67 | disabled = find("button-disabled"); 68 | 69 | font = Fonts.def; 70 | }}; 71 | tbd = new TextButtonStyle(tbe) {{ up = find("button"); }}; 72 | tbt = new TextButtonStyle(tbe) {{ checked = find("button-over"); }}; 73 | 74 | cbe = tbe; 75 | cbd = tbd; 76 | cbt = tbt; 77 | 78 | scr = new ScrollPaneStyle() {{ vScrollKnob = find("scroll-knob"); }}; 79 | 80 | ibc = new ImageButtonStyle(ibt) {{ 81 | imageUpColor = Pal.accentBack; 82 | imageOverColor = Pal.accent; 83 | imageDownColor = Pal.accentBack; 84 | imageCheckedColor = Pal.accent; 85 | }}; 86 | 87 | outline = Styles.outlineLabel; 88 | 89 | log("Created 12 styles for UI"); 90 | 91 | // these are the colors that are used for disabled and light elements 92 | Colors.put("disabled", Pal.gray); 93 | Colors.put("light", Pal.lightishGray); 94 | 95 | // replace the background of tooltips to match the new style 96 | var background = find("panel-clear"); 97 | Tooltips.getInstance().textProvider = cont -> new Tooltip(t -> t.background(background).margin(4f).add(cont).style(outline)); 98 | } 99 | 100 | /** Returns the drawable with the given name and schema prefix. */ 101 | public static Drawable find(String name) { return atlas.drawable("schema-" + name); } 102 | 103 | /** Returns the drawable icon of the given team. */ 104 | public static Drawable icon(Team team) { 105 | if (team.id < 6) return atlas.drawable(new String[] { 106 | "team-derelict", 107 | "team-sharded", 108 | "team-crux", 109 | "team-malis", 110 | "status-electrified-ui", 111 | "status-wet-ui" 112 | }[team.id]); 113 | 114 | var white = (TextureRegionDrawable) Tex.whiteui; 115 | return white.tint(team.color); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/java/schema/ui/dialogs/BaseDialog.java: -------------------------------------------------------------------------------- 1 | package schema.ui.dialogs; 2 | 3 | import arc.scene.style.*; 4 | import arc.scene.ui.*; 5 | import arc.util.*; 6 | import mindustry.gen.*; 7 | import mindustry.graphics.*; 8 | 9 | /** Dialog that has a title header and buttons footer. */ 10 | public class BaseDialog extends Dialog { 11 | 12 | public BaseDialog(String name) { 13 | super(name); 14 | setFillParent(true); 15 | hidden(Sounds.back::play); 16 | 17 | this.margin(0f).getCells().each(c -> c.pad(0f)); 18 | cont.margin(4f).defaults().pad(4f); 19 | 20 | title.setAlignment(Align.center); 21 | titleTable.getCell(title).pad(4f); 22 | 23 | titleTable.row(); 24 | titleTable.image().growX().height(4f).pad(0f).color(Pal.accent); 25 | 26 | buttons.image().growX().height(4f).pad(0f).color(Pal.accent); 27 | buttons.row(); 28 | } 29 | 30 | /** Adds a button to the dialog footer. */ 31 | public void addButton(String text, Drawable icon, Runnable clicked) { 32 | buttons.button(text, icon, schema.ui.Style.tbd, clicked).size(196f, 48f).pad(8f, 4f, 8f, 4f); 33 | } 34 | 35 | /** Adds a button that closes the dialog. */ 36 | public void addCloseButton() { 37 | addButton("@back", Icon.left, this::hide); 38 | closeOnBack(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/java/schema/ui/dialogs/KeybindDialog.java: -------------------------------------------------------------------------------- 1 | package schema.ui.dialogs; 2 | 3 | import arc.input.*; 4 | import arc.math.geom.*; 5 | import arc.scene.event.*; 6 | import arc.scene.ui.*; 7 | import arc.util.*; 8 | import mindustry.gen.*; 9 | import mindustry.graphics.*; 10 | import schema.input.*; 11 | import schema.ui.*; 12 | 13 | import static arc.Core.*; 14 | import static schema.Main.*; 15 | 16 | /** Dialog that displays the list of keybinds. */ 17 | public class KeybindDialog extends BaseDialog { 18 | 19 | /** List of vanilla keybinds that conflict with the modded ones. */ 20 | private static final String[] override = { "keybind-default-keyboard-fullscreen-key", "keybind-default-keyboard-screenshot-key" }; 21 | 22 | /** Stores buttons used for reassigning keybinds' masks and keys. */ 23 | private Button[] mask = new Button[Keybind.all.length], keys = new Button[Keybind.all.length]; 24 | 25 | public KeybindDialog() { 26 | super("@keybind.name"); 27 | addCloseButton(); 28 | hidden(this::resolve); 29 | 30 | cont.pane(Style.scr, pane -> { 31 | pane.marginRight(4f); 32 | pane.defaults().pad(4f); 33 | 34 | for (var bind : Keybind.all) { 35 | 36 | if (bind.category != null) pane.table(t -> { 37 | t.add("@category." + bind.category, Pal.lightishGray).row(); 38 | t.image().growX().height(4f).padTop(4f).color(Pal.lightishGray); 39 | }).fillX().colspan(5).row(); 40 | 41 | pane.add("@keybind." + bind).left(); 42 | 43 | pane.button(b -> set(mask, bind, b).label(bind::formatMask).color(Pal.accent), Style.cbe, () -> rebindMask(bind)).size(256f, 48f).visible(bind::single); 44 | pane.button(b -> set(keys, bind, b).label(bind::formatKeys).color(Pal.accent), Style.cbe, () -> rebindKeys(bind)).size(256f, 48f); 45 | 46 | pane.button(Icon.rotate, Style.ibd, bind::reset).size(48f).tooltip("@keybind.reset"); 47 | pane.button(Icon.cancel, Style.ibd, bind::clear).size(48f).tooltip("@keybind.clear").row(); 48 | } 49 | pane.button("@keybind.reset-all", Style.tbd, this::reset).fillX().height(48f).padBottom(0f).colspan(5); 50 | }); 51 | } 52 | 53 | // region rebinding 54 | 55 | private Button set(Button[] arr, Keybind bind, Button btn) { return arr[bind.ordinal()] = btn; } 56 | 57 | private Vec2 get(Button[] arr, Keybind bind) { return arr[bind.ordinal()].localToStageCoordinates(new Vec2()); } 58 | 59 | /** Shows the rebind dialog used to reassign the mask of the keybind. */ 60 | public void rebindMask(Keybind bind) { 61 | new BaseDialog("") {{ 62 | bottom().left().clearChildren(); 63 | closeOnBack(); 64 | 65 | for (int i = 0; i < Keymask.all.length; i++) { 66 | int j = i; 67 | 68 | button(b -> { 69 | b.add(Keymask.names[j]).color(Pal.accent).size(256f, 48f).labelAlign(Align.center); 70 | b.translation = get(mask, bind).sub(256f * j, (48f + 8f) * (j - bind.mask())); 71 | }, Style.tbt, () -> { 72 | bind.rebind(j); 73 | bind.save(); 74 | hide(); 75 | }).size(256f, 48f).pad(0f).checked(i == bind.mask()); 76 | } 77 | }}.show(); 78 | } 79 | 80 | /** Shows the rebind dialog used to reassign the keys of the keybind. */ 81 | public void rebindKeys(Keybind bind) { 82 | new BaseDialog("") {{ 83 | bottom().left().clearChildren(); 84 | closeOnBack(); 85 | 86 | add("@keybind.press").color(Pal.accent).size(256f, 48f).pad(0f).labelAlign(Align.center).get().translation = get(keys, bind); 87 | addListener(new InputListener() { 88 | 89 | /** Keycode to be assigned as the minimum value of an axis keybind. */ 90 | private KeyCode min = KeyCode.unset; 91 | 92 | /** Main logic of rebinding. */ 93 | private void rebind(KeyCode key) { 94 | if (bind.single()) { 95 | bind.rebind(key); 96 | bind.save(); 97 | hide(); 98 | } else if (min == KeyCode.unset) { 99 | min = key; 100 | Sounds.back.play(16f); 101 | } else { 102 | bind.rebind(min, key); 103 | bind.save(); 104 | hide(); 105 | } 106 | } 107 | 108 | @Override 109 | public boolean touchDown(InputEvent event, float x, float y, int pointer, KeyCode key) { rebind(key); return false; } 110 | 111 | @Override 112 | public boolean keyDown(InputEvent event, KeyCode key) { rebind(key); return false; } 113 | }); 114 | }}.show(); 115 | } 116 | 117 | // endregion 118 | // region actions 119 | 120 | /** Loads the values of the mod keybinds. */ 121 | public void load() { 122 | for (var bind : Keybind.all) bind.load(); 123 | log("Loaded " + Keybind.all.length + " keybinds"); 124 | } 125 | 126 | /** Resets the values of the mod keybinds. */ 127 | public void reset() { 128 | for (var bind : Keybind.all) bind.reset(); 129 | log("Reset keybinds"); 130 | } 131 | 132 | /** Resolves conflicts of the mod keybinds. */ 133 | public void resolve() { 134 | int count = 0; 135 | for (var bind : Keybind.all) count += bind.resolveConflicts(); 136 | log("Resolved " + count + " conflicts"); 137 | 138 | for (var key : override) settings.put(key, KeyCode.unknown.ordinal()); 139 | Reflect.invoke(keybinds, "load"); 140 | for (var key : override) settings.remove(key); 141 | } 142 | 143 | // endregion 144 | } 145 | -------------------------------------------------------------------------------- /src/java/schema/ui/fragments/CommandFragment.java: -------------------------------------------------------------------------------- 1 | package schema.ui.fragments; 2 | 3 | import arc.input.*; 4 | import arc.scene.*; 5 | import arc.scene.event.*; 6 | import arc.scene.ui.layout.*; 7 | import arc.util.*; 8 | import mindustry.ai.*; 9 | import mindustry.graphics.*; 10 | import mindustry.ui.*; 11 | import schema.input.*; 12 | import schema.ui.*; 13 | 14 | import static arc.Core.*; 15 | import static mindustry.Vars.*; 16 | import static schema.Main.*; 17 | 18 | /** Fragment that is displayed when the {@link schema.Main#insys input system} is in unit command mode. */ 19 | public class CommandFragment extends Table { 20 | 21 | /** Amount of units that were controlled when the fragment was rebuild. */ 22 | private int lastAmountOfUnits; 23 | /** Command that is shared among the controlled units. */ 24 | private UnitCommand shared; 25 | 26 | /** Builds the fragment and override the original one. */ 27 | public void build(Group parent) { 28 | parent.addChild(this); 29 | parent.removeChild(Reflect.get(ui.hudfrag.blockfrag, "toggler")); 30 | 31 | setFillParent(true); 32 | rebuild(); 33 | update(() -> { 34 | boolean[] had = { false }; // java sucks, as always 35 | shared = null; 36 | 37 | insys.freeUnits(u -> { 38 | if (!u.isCommandable()) return true; 39 | 40 | if (had[0] == false) { 41 | had[0] = true; 42 | shared = u.command().command; 43 | } else if (u.command().command != shared) shared = null; 44 | 45 | return false; 46 | }); 47 | 48 | if (lastAmountOfUnits != insys.controlledUnitsAmount()) { 49 | lastAmountOfUnits = insys.controlledUnitsAmount(); 50 | rebuild(); 51 | } 52 | }).visible(insys::controlling); 53 | } 54 | 55 | /** Rebuilds the fragment in order to update the list of units */ 56 | public void rebuild() { 57 | bottom().clear(); 58 | table(Style.find("panel-bottom"), cont -> { 59 | cont.margin(12f, 12f, 4f, 12f); 60 | cont.defaults().pad(4f); 61 | 62 | if (lastAmountOfUnits == 0) 63 | cont.add("@cmnd.no-units").height(48f); 64 | else { 65 | var counts = insys.controlledUnitsAmountByType(); 66 | var commands = UnitCommand.all.copy(); 67 | 68 | for (int i = 0; i < counts.length; i++) if (counts[i] > 0) { 69 | 70 | var type = content.unit(i); 71 | int count = counts[i]; 72 | 73 | cont.table(t -> { 74 | t.add(new ItemImage(type.uiIcon, count)).size(32f, 32f).tooltip(type.localizedName); 75 | t.addListener(new HandCursorListener()); 76 | 77 | t.clicked(KeyCode.mouseLeft, () -> insys.freeUnits(u -> u.type != type)); 78 | t.clicked(KeyCode.mouseRight, () -> insys.freeUnits(u -> u.type == type)); 79 | 80 | t.hovered(() -> t.background(Style.find("button-over"))); 81 | t.exited(() -> t.background(null)); 82 | }).size(48f, 48f).touchable(Touchable.enabled); 83 | 84 | commands.retainAll(c -> Structs.contains(type.commands, c)); 85 | } 86 | 87 | cont.image().growY().width(4f).color(Pal.accent); 88 | cont.add(bundle.format("cmnd.clear", Keybind.deselect.format())); 89 | 90 | if (commands.size > 1) { 91 | cont.image().growY().width(4f).color(Pal.accent); 92 | commands.each(c -> { 93 | cont.button(ui.getIcon(c.icon), Style.ibc, () -> insys.commandUnits(c)).size(48f).checked(i -> c == shared).tooltip(c.localized()); 94 | }); 95 | } 96 | } 97 | }).bottom().touchable(Touchable.enabled); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/java/schema/ui/fragments/LoadingFragment.java: -------------------------------------------------------------------------------- 1 | package schema.ui.fragments; 2 | 3 | import arc.*; 4 | import arc.func.*; 5 | import arc.graphics.*; 6 | import arc.graphics.g2d.*; 7 | import arc.math.*; 8 | import arc.math.geom.*; 9 | import arc.scene.*; 10 | import arc.scene.actions.*; 11 | import arc.scene.ui.layout.*; 12 | import arc.struct.*; 13 | import arc.util.*; 14 | import mindustry.game.EventType.*; 15 | import mindustry.graphics.*; 16 | import mindustry.ui.*; 17 | 18 | import static arc.Core.*; 19 | import static mindustry.Vars.*; 20 | import static schema.Main.*; 21 | 22 | /** Fragment that is displayed during loading of any kind. */ 23 | public class LoadingFragment extends Table { 24 | 25 | /** Horizontal and vertical distance between hexes. */ 26 | public static final float spacing = 240f, height = Mathf.sqrt3 * spacing / 6f; 27 | /** Distance between bars and their size. */ 28 | public static final float step = 80f, skew = 32f; 29 | 30 | /** Provides the current loading progress. */ 31 | private Floatp progress; 32 | /** Interpolated value of the {@link #progress}. */ 33 | private float displayProgress; 34 | 35 | /** Post-processing component. */ 36 | private Bloom bloom = new Bloom(true); 37 | /** Collection of hexes' positions. */ 38 | private Seq hexes = new Seq<>(); 39 | 40 | public LoadingFragment() { super(Styles.black8); } 41 | 42 | /** Builds the fragment and override the original one. */ 43 | public void build(Group parent) { 44 | Events.run(ResizeEvent.class, () -> { 45 | int w = graphics.getWidth(), 46 | h = graphics.getHeight(); 47 | 48 | bloom.resize(w, h); 49 | bloom.blurPasses = 8; 50 | 51 | hexes.clear(); 52 | 53 | for (int x = 0; x <= w / spacing; x++) 54 | for (int y = 0; y <= h / height + 1; y++) 55 | hexes.add(new Vec2((x + (y % 2) * .5f) * spacing, y * height - 14f)); 56 | }); 57 | 58 | parent.addChild(this); 59 | parent.removeChild(Reflect.get(ui.loadfrag, "table")); 60 | 61 | setFillParent(true); 62 | hideImmediately(); 63 | label(() -> (int) (progress.get() * 100) + "%").style(Styles.techLabel).color(Pal.accent); 64 | } 65 | 66 | /** Shows the fragment with a simple animation. */ 67 | public void show() { 68 | hexes.shuffle(); 69 | visible = true; 70 | 71 | toFront(); 72 | actions(Actions.alpha(.0f), Actions.alpha(1f, .4f)); 73 | } 74 | 75 | /** Hides the fragment with a simple animation. */ 76 | public void hide() { 77 | progress = () -> 1f; 78 | actions(Actions.delay(.4f), Actions.alpha(0f, .4f), Actions.run(this::hideImmediately)); 79 | } 80 | 81 | /** Immediately hides the fragment without any animation. */ 82 | public void hideImmediately() { 83 | progress = () -> 0f; 84 | displayProgress = 0f; 85 | visible = false; 86 | } 87 | 88 | @Override 89 | public void draw() { 90 | super.draw(); 91 | 92 | float progress = displayProgress += Math.min(this.progress.get() - displayProgress, Time.delta / 20f); 93 | 94 | float w = graphics.getWidth(), h = graphics.getHeight(); 95 | float x = w / 2f, y = h / 2f; 96 | 97 | bloom.setBloomIntensity(1.5f + progress); 98 | bloom.capture(); 99 | 100 | // region hexes 101 | 102 | for (int i = 0; i < hexes.size; i++) { 103 | var alpha = Mathf.clamp(progress * hexes.size - i); 104 | if (alpha == 0f) break; // the rest of the hexes will have the same result 105 | 106 | Vec2 hex = hexes.get(i); 107 | 108 | Draw.color(Pal.accent, color.a * alpha); 109 | Fill.poly(hex.x, hex.y, 6, 48f); 110 | 111 | Draw.color(Color.black); 112 | Fill.poly(hex.x, hex.y, 6, 24f); 113 | } 114 | 115 | // endregion 116 | // region bars 117 | 118 | Draw.color(Color.black); 119 | Fill.rect(x, y, w, 138f); 120 | 121 | Draw.color(Color.black, color.a * .2f); 122 | Fill.rect(x, y, w, h); 123 | 124 | Draw.color(Pal.accent, color.a); 125 | Fill.rect(x, y + 50f, w, 12f); 126 | Fill.rect(x, y - 50f, w, 12f); 127 | 128 | int bars = (int) (w / step / 2f) + 1; 129 | 130 | for (int i = 2; i < bars; i++) { 131 | float fract = 1f - (i - 2f) / (bars - 1f); 132 | float alpha = Mathf.clamp(1f - (fract - progress) * bars); 133 | 134 | Draw.color(Pal.accent, color.a * alpha); 135 | 136 | for (int side : Mathf.signs) { 137 | float bx = x + i * step * side - skew / 2f; 138 | 139 | Fill.rects(bx, y, skew, skew, -skew * side); 140 | Fill.rects(bx, y, skew, -skew, -skew * side); 141 | } 142 | } 143 | 144 | // endregion 145 | 146 | bloom.render(); 147 | } 148 | 149 | // region agent 150 | 151 | /** Returns the agent of this fragment. */ 152 | public Agent getAgent() { return new Agent(); } 153 | 154 | /** Agent that redirects method calls from the original fragment to the new one. */ 155 | public class Agent extends mindustry.ui.fragments.LoadingFragment { 156 | 157 | @Override 158 | public void setProgress(Floatp p) { progress = p; } 159 | 160 | @Override 161 | public void setProgress(float p) { progress = () -> p; } 162 | 163 | @Override 164 | public void setButton(Runnable listener) {} // TODO implement 165 | 166 | @Override 167 | public void show() { loadfrag.show(); } 168 | 169 | @Override 170 | public void show(String text) { loadfrag.show(); } 171 | 172 | @Override 173 | public void hide() { loadfrag.hide(); } 174 | 175 | @Override 176 | public void toFront() {} 177 | 178 | @Override 179 | public void snapProgress() {} 180 | } 181 | 182 | // endregion 183 | } 184 | -------------------------------------------------------------------------------- /src/java/schema/ui/fragments/MapFragment.java: -------------------------------------------------------------------------------- 1 | package schema.ui.fragments; 2 | 3 | import arc.graphics.*; 4 | import arc.graphics.g2d.*; 5 | import arc.input.*; 6 | import arc.scene.*; 7 | import arc.scene.event.*; 8 | import arc.scene.ui.layout.*; 9 | import arc.util.*; 10 | import mindustry.gen.*; 11 | import mindustry.graphics.*; 12 | import mindustry.ui.fragments.*; 13 | import schema.ui.*; 14 | 15 | import static arc.Core.*; 16 | import static mindustry.Vars.*; 17 | import static schema.Main.*; 18 | 19 | /** Fragment that displays the sector map. */ 20 | public class MapFragment extends Table { 21 | 22 | /** Zoom of the camera that is not related to the game camera. */ 23 | private float zoom; 24 | /** Position of the camera, or rather the map itself. */ 25 | private float panx, pany; 26 | /** Width and height of the map after applying zoom. */ 27 | private float mw, mh; 28 | 29 | /** Whether the fragment is visible. */ 30 | public boolean shown; 31 | 32 | public MapFragment() { touchable = Touchable.enabled; } 33 | 34 | /** Builds the fragment and override the original one. */ 35 | public void build(Group parent) { 36 | parent.addChild(this); 37 | parent.getChildren().remove(parent.getChildren().indexOf(ui.minimapfrag.elem) + 1); 38 | parent.getChildren().remove(parent.getChildren().indexOf(ui.minimapfrag.elem)); 39 | 40 | setFillParent(true); 41 | update(() -> { 42 | if (!ui.chatfrag.shown()) requestScroll(); 43 | }).visible(() -> shown); 44 | 45 | bottom().table(Style.find("panel-bottom"), cont -> { 46 | cont.margin(12f, 12f, 4f, 12f); 47 | cont.defaults().pad(4f); 48 | 49 | cont.button(Icon.left, Style.ibc, () -> shown = false).size(48f).checked(i -> false).tooltip("@back"); 50 | cont.image().growY().width(4f).color(Pal.accent); 51 | cont.add("@map"); 52 | }); 53 | addListener(new ElementGestureListener() { 54 | 55 | @Override 56 | public void touchDown(InputEvent event, float x, float y, int pointer, KeyCode key) { 57 | if (key == KeyCode.mouseRight) pan2(x, y); 58 | } 59 | 60 | @Override 61 | public void pan(InputEvent event, float x, float y, float deltaX, float deltaY) { 62 | if (event.keyCode == KeyCode.mouseRight) pan2(x, y); 63 | else { 64 | panx += deltaX; 65 | pany += deltaY; 66 | } 67 | } 68 | }); 69 | addListener(new InputListener() { 70 | 71 | @Override 72 | public boolean scrolled(InputEvent event, float x, float y, float amountX, float amountY) { 73 | // the math equation below behaves a little strange when the zoom delta is zero 74 | // therefore, return is used instead of clamp 75 | if (amountY > 0f ? zoom <= .5f : zoom >= 4f) return true; 76 | 77 | zoom = zoom - amountY / 10f; 78 | 79 | panx += panx / (1f + amountY / 10f / zoom) - panx; 80 | pany += pany / (1f + amountY / 10f / zoom) - pany; 81 | 82 | return true; 83 | } 84 | }); 85 | } 86 | 87 | /** Toggles visibility of the fragment. */ 88 | public void toggle() { if (shown = !shown) { zoom = 1f; panx = pany = 0f; } } 89 | 90 | /** Pans the game camera to the world coordinates that correspond to the given local position. */ 91 | public void pan2(float localX, float localY) { insys.setCam(Tmp.v1.set( 92 | (localX - (graphics.getWidth() - mw) / 2f - panx) / mw * world.unitWidth(), 93 | (localY - (graphics.getHeight() - mh) / 2f - pany) / mh * world.unitHeight())); } 94 | 95 | @Override 96 | public void draw() { 97 | int w = graphics.getWidth(), 98 | h = graphics.getHeight(); 99 | 100 | Draw.color(Color.black); 101 | Fill.crect(0, 0, w, h); 102 | 103 | var tex = renderer.minimap.getTexture(); 104 | if (tex != null) { 105 | 106 | float tw = tex.width, th = tex.height; 107 | if (tw > th) { 108 | mw = zoom * w; 109 | mh = zoom * w * th / tw; 110 | } else { 111 | mw = zoom * h * tw / th; 112 | mh = zoom * h; 113 | } 114 | 115 | Draw.color(); 116 | Draw.rect(Draw.wrap(tex), w / 2f + panx, h / 2f + pany, mw, mh); 117 | 118 | renderer.minimap.drawEntities((w - mw) / 2f + panx, (h - mh) / 2f + pany, mw, mh, zoom, true); 119 | } 120 | 121 | Draw.reset(); 122 | super.draw(); 123 | } 124 | 125 | // region agent 126 | 127 | /** Returns the agent of this fragment. */ 128 | public Agent getAgent() { return new Agent(); } 129 | 130 | /** Agent that redirects method calls from the original fragment to the new one. */ 131 | public class Agent extends MinimapFragment { 132 | 133 | @Override 134 | public boolean shown() { return true; } // there is a check in Control#update that should always fail 135 | 136 | @Override 137 | public void hide() { shown = false; } // planet dialog calls it 138 | } 139 | 140 | // endregion 141 | } 142 | -------------------------------------------------------------------------------- /src/java/schema/ui/hud/CoreInfo.java: -------------------------------------------------------------------------------- 1 | package schema.ui.hud; 2 | 3 | import arc.*; 4 | import arc.graphics.*; 5 | import arc.math.*; 6 | import arc.scene.ui.layout.*; 7 | import arc.struct.*; 8 | import arc.util.*; 9 | import mindustry.game.*; 10 | import mindustry.game.EventType.*; 11 | import mindustry.gen.*; 12 | import mindustry.graphics.*; 13 | import mindustry.type.*; 14 | import mindustry.ui.*; 15 | import mindustry.world.blocks.power.*; 16 | import mindustry.world.modules.*; 17 | import schema.input.*; 18 | import schema.ui.*; 19 | 20 | import static arc.Core.*; 21 | import static mindustry.Vars.*; 22 | import static schema.Main.*; 23 | 24 | /** Subfragment that displays the core items, their flow and power grids. */ 25 | public class CoreInfo extends Table { 26 | 27 | /** Team whose core items and power grids are displayed. */ 28 | private Team team = Team.derelict; 29 | /** Whether the player is choosing a team or grid. */ 30 | private boolean chooseTeam, chooseGrid; 31 | 32 | /** Set of items that were used in this sector. */ 33 | private final ObjectSet used = new ObjectSet<>(); 34 | /** Item module obtained from the core of the selected team. */ 35 | private ItemModule core; 36 | 37 | /** Components used to calculate the mean of core items. */ 38 | private WindowedMean[] flow; 39 | /** Last amount of items of each type. */ 40 | private int[] last; 41 | 42 | /** Set of power graphs that were found on the map. */ 43 | private final ObjectSet graphs = new ObjectSet<>(); 44 | /** Power graph obtained from buildings' power modules. */ 45 | private PowerGraph graph; 46 | 47 | public CoreInfo() { super(Style.find("panel-top")); } 48 | 49 | /** Builds the subfragment. */ 50 | public void build() { 51 | Events.run(ResetEvent.class, used::clear); 52 | Events.run(ResetEvent.class, graphs::clear); 53 | Events.run(WorldLoadEvent.class, () -> { 54 | team = player.team(); 55 | graph = new PowerGraph(true); 56 | 57 | rebuild(); 58 | }); 59 | 60 | flow = new WindowedMean[content.items().size]; 61 | last = new int[content.items().size]; 62 | 63 | for (int i = 0; i < flow.length; i++) 64 | flow[i] = new WindowedMean(8); 65 | 66 | Timer.schedule(() -> { 67 | if (state.isMenu()) return; 68 | var toRebuild = new boolean[] { false }; 69 | 70 | core = team.data().hasCore() ? team.core().items : null; 71 | if (core != null) core.each((i, a) -> { if (used.add(i)) toRebuild[0] = true; }); 72 | 73 | if (core != null && state.isPlaying()) content.items().each(used::contains, i -> { 74 | flow[i.id].add(core.get(i) - last[i.id]); 75 | last[i.id] = core.get(i); 76 | }); 77 | 78 | graphs.each(g -> { 79 | if (valid(g) && Groups.powerGraph.contains(e -> e.graph() == g)) return; 80 | 81 | graphs.remove(g); 82 | toRebuild[0] = true; 83 | 84 | if (graph == g) graph = new PowerGraph(true); 85 | }); 86 | 87 | if (Groups.powerGraph.contains(e -> valid(e.graph()) && graphs.add(e.graph()))) toRebuild[0] = true; 88 | if (toRebuild[0]) rebuild(); 89 | }, 0f, .5f); 90 | } 91 | 92 | /** Rebuilds the subfragment to update the list of core items and power grids. */ 93 | public void rebuild() { 94 | margin(4f, 12f, 12f, 12f); 95 | defaults().pad(4f); 96 | 97 | clearChildren(); 98 | table(cont -> { 99 | 100 | cont.table(t -> content.items().each(used::contains, i -> { 101 | 102 | t.top().left(); 103 | 104 | t.image(i.uiIcon).size(24f); 105 | t.label(() -> core == null 106 | ? "[disabled]-" 107 | : Keybind.display_prod.down() 108 | ? formatFlow(flow[i.id].mean()) 109 | : format(core.get(i), false) 110 | ).minWidth(80f).padLeft(4f).left(); 111 | 112 | if (t.getChildren().size % 8 == 0) t.row(); 113 | 114 | })).growY().width(4f * (24f + 4f + 80f)).pad(0f, 0f, 4f, 0f).row(); 115 | 116 | cont.collapser(t -> { 117 | 118 | t.margin(4f, 4f, 4f, 0f).left(); 119 | 120 | for (var team : Team.all) { 121 | if (!team.active() && team.id >= 6) continue; 122 | 123 | t.button(Style.icon(team), Style.ibt, 32f, () -> { 124 | this.team = team; 125 | rebuild(); 126 | }).checked(i -> this.team == team).size(40f).padLeft(-4f).get().getImage().setColor(team.active() ? Color.white : Pal.lightishGray); 127 | } 128 | 129 | }, true, () -> chooseTeam).growX().row(); 130 | 131 | cont.add(balance(graph)).growX().height(20f).pad(4f, 0f, 4f, 0f).row(); 132 | cont.add(stored(graph)).growX().height(20f).pad(4f, 0f, 0f, 0f).row(); 133 | 134 | cont.collapser(t -> { 135 | 136 | t.margin(12f, 0f, 0f, 0f).top(); 137 | 138 | if (graphs.isEmpty()) t.add("@hud.no-grids").padTop(-4f); 139 | else graphs.each(graph -> { 140 | t.button(b -> { 141 | b.margin(8f); 142 | b.add(balance(graph)).grow().row(); 143 | b.add(stored(graph)).grow().row(); 144 | }, Style.cbt, () -> { 145 | this.graph = graph; 146 | rebuild(); 147 | }).checked(b -> this.graph == graph).growX().height(48f).padTop(-4f).row(); 148 | }); 149 | 150 | }, true, () -> chooseGrid).growX().row(); 151 | 152 | }).growY(); 153 | table(btns -> { 154 | btns.button(Style.icon(team), Style.ibt, 40f, () -> chooseTeam = !chooseTeam).checked(i -> chooseTeam).size(48f).padBottom(8f).row(); 155 | btns.button(Icon.menu, Style.ibc, () -> chooseGrid = !chooseGrid).checked(i -> chooseGrid).size(48f); 156 | }).top(); 157 | } 158 | 159 | /** Formats the given number as a flow. */ 160 | public String formatFlow(float num) { return (num > 0 ? "[green]+" : num < 0 ? "[scarlet]" : "") + format(num, true) + "[light]/s"; } 161 | 162 | /** Whether the given power graph is valid. */ 163 | public boolean valid(PowerGraph graph) { return graph.all.size > 1 && graph.all.peek().team == team; } 164 | 165 | /** Creates a power bar that displays the power balance. */ 166 | public Bar balance(PowerGraph graph) { 167 | return new Bar( 168 | () -> bundle.format("bar.powerbalance", (graph.getPowerBalance() >= 0f ? "+" : "") + format(graph.getPowerBalance() * 60f, false)), 169 | () -> Pal.powerBar, 170 | () -> graph.getSatisfaction()); 171 | } 172 | 173 | /** Creates a power bar that displays the power stored. */ 174 | public Bar stored(PowerGraph graph) { 175 | return new Bar( 176 | () -> bundle.format("bar.powerstored", format(graph.getLastPowerStored(), false), format(graph.getLastCapacity(), false)), 177 | () -> Pal.powerBar, 178 | () -> graph.getLastPowerStored() / graph.getLastCapacity()); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/java/schema/ui/hud/HudFragment.java: -------------------------------------------------------------------------------- 1 | package schema.ui.hud; 2 | 3 | import arc.scene.*; 4 | import arc.scene.ui.layout.*; 5 | 6 | /** Fragment that displays the core and power grid info, controlled unit and its configuration, minimap and wave info. */ 7 | public class HudFragment extends Table { 8 | 9 | public UnitInfo unit = new UnitInfo(); 10 | public CoreInfo core = new CoreInfo(); 11 | public WaveInfo wave = new WaveInfo(); 12 | 13 | /** Whether the fragment is visible. */ 14 | public boolean shown = true; 15 | 16 | /** Builds the fragment and override the original one. */ 17 | public void build(Group parent) { 18 | parent.addChild(this); 19 | parent.removeChild(parent.find("overlaymarker")); 20 | parent.removeChild(parent.find("coreinfo")); 21 | parent.removeChild(parent.find("minimap/position")); 22 | 23 | setFillParent(true); 24 | visible(() -> shown); 25 | top().add(unit, core, wave); 26 | 27 | core.build(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/java/schema/ui/hud/UnitInfo.java: -------------------------------------------------------------------------------- 1 | package schema.ui.hud; 2 | 3 | import arc.scene.ui.layout.*; 4 | 5 | /** Subfragment that displays the unit of the player and its configuration: task to perform and other options. */ 6 | public class UnitInfo extends Table { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/java/schema/ui/hud/WaveInfo.java: -------------------------------------------------------------------------------- 1 | package schema.ui.hud; 2 | 3 | import arc.scene.ui.layout.*; 4 | 5 | /** Subfragment that displays the sector minimap and information about the next wave: total health, shield, units and so on. */ 6 | public class WaveInfo extends Table { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/java/schema/ui/polygon/BlockPolygon.java: -------------------------------------------------------------------------------- 1 | package schema.ui.polygon; 2 | 3 | import arc.func.*; 4 | import arc.graphics.*; 5 | import arc.math.*; 6 | import arc.scene.*; 7 | import arc.scene.style.*; 8 | import arc.scene.ui.*; 9 | import arc.scene.ui.layout.*; 10 | import arc.util.*; 11 | import mindustry.gen.*; 12 | import mindustry.graphics.*; 13 | import mindustry.type.*; 14 | import mindustry.world.*; 15 | import schema.ui.Style; 16 | 17 | import static arc.Core.*; 18 | import static mindustry.Vars.*; 19 | import static schema.Main.*; 20 | 21 | import static mindustry.type.Category.*; 22 | import static mindustry.type.Category.logic; 23 | import static mindustry.type.Category.units; 24 | 25 | /** Polygon that displays the block selection wheel. */ 26 | public class BlockPolygon extends Polygon { 27 | 28 | /** Reordered list of the block categories. */ 29 | public static final Category[] categories = { distribution, liquid, production, crafting, power, defense, turret, units, effect, logic }; 30 | /** Reordered list of character icons of the corresponding categories. */ 31 | public static final char[] icons = new char[all.length]; 32 | { 33 | for (int i = 0; i < all.length; i++) 34 | icons[i] = Reflect.get(Iconc.class, categories[i].name()); 35 | } 36 | 37 | /** Rebuilds the list of blocks. */ 38 | private Runnable rebuild; 39 | 40 | @Override 41 | public void build(Group parent) { 42 | super.build(parent); 43 | for (int i = 0; i < all.length; i++) add(String.valueOf(icons[i]), true, j -> { 44 | draw = false; 45 | 46 | var index = new int[1]; 47 | var hover = new Block[2]; 48 | 49 | rebuild = () -> { 50 | index[0] = 0; 51 | 52 | removeChild(children.find(e -> e instanceof Table)); 53 | add(new Table(Style.find("panel"), list -> { 54 | list.margin(12f); 55 | list.defaults().pad(4f); 56 | 57 | list.update(() -> { 58 | if (hover[1] != hover[0]) { 59 | hover[1] = hover[0]; 60 | rebuild.run(); 61 | } 62 | }); 63 | 64 | if (hover[0] != null) { 65 | list.table(pane -> { 66 | 67 | pane.defaults().width(180f).left(); 68 | pane.add(hover[0].localizedName).wrap().row(); 69 | 70 | for (var stack : hover[0].requirements) pane.table(line -> { 71 | var rlabel = new Label[1]; 72 | 73 | line.image(stack.item.uiIcon).size(16f); 74 | line.add(stack.item.localizedName).growX().pad(0f, 4f, 0f, 4f).color(Pal.lightishGray).left().ellipsis(true).update(l -> { 75 | // for some reason, ellipsis does not work 76 | l.setWidth(180f - 24f - rlabel[0].getPrefWidth()); 77 | }); 78 | 79 | line.label(() -> { 80 | var core = player.core(); 81 | int required = Math.round(stack.amount * state.rules.buildCostMultiplier); 82 | 83 | if (core == null || state.rules.infiniteResources) return "*/" + format(required, false); 84 | 85 | int amount = core.items.get(stack.item); 86 | var color = amount < required / 2f ? "[scarlet]" : amount < required ? "[accent]" : "[white]"; 87 | 88 | return color + format(amount, false) + "[]/" + format(required, false); 89 | }).with(l -> rlabel[0] = l).fontScale(.9f); 90 | }).row(); 91 | 92 | if (!player.isBuilder() || !hover[0].isPlaceable()) 93 | pane.add(!player.isBuilder() ? "@poly.no-build" : !hover[0].supportsEnv(state.rules.env) ? "@poly.bad-env" : "@poly.banned"); 94 | 95 | }).row(); 96 | list.image().growX().height(4f).color(Pal.accent).row(); 97 | } 98 | 99 | list.table(pane -> { 100 | pane.margin(0f, 0f, 4f, 4f); 101 | pane.defaults().pad(0f, 0f, -4f, -4f); 102 | 103 | eachUnlocked(categories[j], b -> { 104 | var core = player.core(); 105 | var available = player.isBuilder() && (state.rules.infiniteResources || (core != null && core.items.has(b.requirements))); 106 | 107 | if (index[0]++ % 4 == 0) pane.row(); 108 | 109 | var button = pane.button(new TextureRegionDrawable(b.uiIcon), Style.ibt, () -> { 110 | 111 | if (input.shift() || input.ctrl() || input.alt()) 112 | copy(icon(b.name) + ""); 113 | else 114 | insys.block = b; 115 | 116 | hide(); 117 | }).size(48f).checked(insys.block == b).disabled(!b.isPlaceable() || !available).get(); 118 | 119 | if (button.isDisabled()) button.toBack(); 120 | 121 | button.resizeImage(32f); 122 | button.getImage().setColor(!b.isPlaceable() ? Pal.gray : !available ? Pal.lightishGray : Color.white); 123 | 124 | button.hovered(() -> hover[0] = b); 125 | button.exited(() -> { if (hover[0] == b) hover[0] = null; }); 126 | }); 127 | }); 128 | }) { 129 | @Override 130 | public void layout() { 131 | super.layout(); 132 | float 133 | width = 212f, 134 | height = Mathf.round(getPrefHeight()); 135 | 136 | setSize(width, height); 137 | translation.set(width, Mathf.ceil(index[0] / 4f) * 44f + 36f).scl(-.5f); 138 | } 139 | }); 140 | 141 | }; 142 | rebuild.run(); 143 | }); 144 | } 145 | 146 | @Override 147 | public void hideImmediately() { super.hideImmediately(); removeChild(children.find(e -> e instanceof Table)); } 148 | 149 | /** Whether the given block is unlocked. */ 150 | public boolean unlocked(Block block) { return block.placeablePlayer && block.unlockedNow() && block.environmentBuildable() && block.isVisible(); } 151 | 152 | /** Iterates unlocked blocks in the given category. */ 153 | public void eachUnlocked(Category cat, Cons cons) { 154 | content.blocks().select(b -> 155 | b.category == cat && unlocked(b) 156 | ).sort((b1, b2) -> 157 | Boolean.compare(!b1.isPlaceable(), !b2.isPlaceable()) 158 | ).each(cons); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/java/schema/ui/polygon/Polygon.java: -------------------------------------------------------------------------------- 1 | package schema.ui.polygon; 2 | 3 | import arc.func.*; 4 | import arc.graphics.*; 5 | import arc.graphics.g2d.*; 6 | import arc.input.*; 7 | import arc.math.*; 8 | import arc.scene.*; 9 | import arc.scene.actions.*; 10 | import arc.scene.ui.*; 11 | import arc.scene.ui.layout.*; 12 | import arc.struct.*; 13 | import arc.util.*; 14 | import mindustry.graphics.*; 15 | import schema.ui.*; 16 | 17 | import static arc.Core.*; 18 | import static schema.Main.*; 19 | 20 | /** Fragment that displays a polygonal selection wheel. */ 21 | public class Polygon extends Stack { 22 | 23 | /** List of vertices of the polygon. */ 24 | private Seq vertices = new Seq<>(); 25 | /** Radius the polygon and step between vertices in degrees. */ 26 | private float size, step; 27 | /** Array containing alpha values of the vertices. */ 28 | private float[] alpha = new float[16]; 29 | 30 | /** Whether to draw the polygon or not. */ 31 | public boolean draw; 32 | /** Index of the selected vertex. */ 33 | public int selected; 34 | 35 | /** Builds the fragment and, if extended, assigns actions to the sides. */ 36 | public void build(Group parent) { 37 | parent.addChild(this); 38 | 39 | setSize(0f); 40 | hideImmediately(); 41 | update(() -> { 42 | selected = Mathf.within(x, y, input.mouseX(), input.mouseY(), 64f) 43 | ? -1 44 | : Mathf.round(Angles.angle(x, y, input.mouseX(), input.mouseY()) / step) % vertices.size; 45 | 46 | for (int i = 0; i < vertices.size; i++) 47 | vertices.get(i).label.translation.trns(i * step, size + 6f * alpha[i]); 48 | }); 49 | keyDown(key -> { 50 | if (key == KeyCode.escape || key == KeyCode.back) app.post(this::hide); 51 | }); 52 | } 53 | 54 | /** Triggers the callback of the selected vertex. */ 55 | public void select() { if (draw && selected != -1) vertices.get(selected).clicked.run(); } 56 | 57 | /** Shows the fragment with a simple animation. */ 58 | public void show() { 59 | visible = true; 60 | x = input.mouseX(); 61 | y = input.mouseY(); 62 | 63 | size = vertices.size * 10f; 64 | step = 360f / vertices.size; 65 | 66 | requestKeyboard(); 67 | toFront(); 68 | actions(Actions.alpha(.0f), Actions.alpha(1f, .08f)); 69 | } 70 | 71 | /** Hides the fragment with a simple animation. */ 72 | public void hide() { actions(Actions.alpha(0f, .08f), Actions.run(this::hideImmediately)); } 73 | 74 | /** Immediately hides the fragment without any animation. */ 75 | public void hideImmediately() { visible = false; draw = true; selected = -1; } 76 | 77 | /** Adds a new vertex to the polygon. */ 78 | public void add(String text, boolean highlight, Cons clicked) { 79 | int index = vertices.size; 80 | var label = new Label(text, Style.outline); 81 | 82 | label.setAlignment(Align.center); 83 | label.visible(() -> draw); 84 | 85 | if (highlight) label.update(() -> label.color.set(Pal.accentBack).lerp(Pal.accent, alpha[index])); 86 | 87 | vertices.add(new Vertex(label, () -> clicked.get(index))); 88 | add(label); 89 | } 90 | 91 | /** Adds an empty vertex to the polygon. */ 92 | public void add() { add("", false, i -> {}); } 93 | 94 | /** Removes all vertices from the polygon. */ 95 | public void clear() { vertices.clear(); } 96 | 97 | @Override 98 | public void draw() { 99 | Draw.color(Color.black, color.a * .2f); 100 | Fill.crect(0f, 0f, graphics.getWidth(), graphics.getHeight()); 101 | 102 | if (draw) { 103 | overlay.capture(.8f); 104 | 105 | Draw.color(Pal.accentBack, color.a); 106 | Lines.stroke(16f); 107 | Lines.poly(x, y, vertices.size, size); 108 | 109 | for (int i = 0; i < alpha.length; i++) { 110 | 111 | var a = alpha[i] = Mathf.lerpDelta(alpha[i], i == selected ? 1f : 0f, .4f); 112 | if (a < .01f) continue; 113 | 114 | Draw.color(Pal.accent, color.a * a); 115 | Lines.stroke(12f); 116 | Lines.arc(x + Tmp.v1.trns(i * step, 10f * a).x, y + Tmp.v1.y, size - 2f, 2f / vertices.size, (i - 1) * step, vertices.size); 117 | } 118 | 119 | overlay.render(); 120 | } 121 | 122 | Draw.reset(); 123 | super.draw(); 124 | } 125 | 126 | /** Structure that represents a vertex of the polygon. */ 127 | public record Vertex(Label label, Runnable clicked) {} 128 | } 129 | -------------------------------------------------------------------------------- /src/java/scheme/ClajIntegration.java: -------------------------------------------------------------------------------- 1 | package scheme; 2 | 3 | import arc.Events; 4 | import arc.func.Cons; 5 | import arc.net.Client; 6 | import arc.net.Connection; 7 | import arc.net.DcReason; 8 | import arc.net.NetListener; 9 | import arc.struct.Seq; 10 | import arc.util.Reflect; 11 | import arc.util.Threads; 12 | import mindustry.game.EventType.*; 13 | import mindustry.gen.Call; 14 | import mindustry.io.TypeIO; 15 | import mindustry.net.ArcNetProvider.*; 16 | 17 | import static mindustry.Vars.*; 18 | 19 | import java.io.IOException; 20 | import java.nio.ByteBuffer; 21 | 22 | public class ClajIntegration { 23 | 24 | public static Seq clients = new Seq<>(); 25 | public static NetListener serverListener; 26 | 27 | public static void load() { 28 | Events.run(HostEvent.class, ClajIntegration::clear); 29 | Events.run(ClientPreConnectEvent.class, ClajIntegration::clear); 30 | 31 | var provider = Reflect.get(net, "provider"); 32 | if (steam) provider = Reflect.get(provider, "provider"); // thanks 33 | 34 | var server = Reflect.get(provider, "server"); 35 | serverListener = Reflect.get(server, "dispatchListener"); 36 | } 37 | 38 | // region room management 39 | 40 | public static Client createRoom(String ip, int port, Cons link, Runnable disconnected) throws IOException { 41 | Client client = new Client(8192, 8192, new Serializer()); 42 | Threads.daemon("CLaJ Room", client::run); 43 | 44 | client.addListener(new NetListener() { 45 | 46 | /** Used when creating redirectors. */ 47 | public String key; 48 | 49 | @Override 50 | public void connected(Connection connection) { 51 | client.sendTCP("new"); 52 | } 53 | 54 | @Override 55 | public void disconnected(Connection connection, DcReason reason) { 56 | disconnected.run(); 57 | } 58 | 59 | @Override 60 | public void received(Connection connection, Object object) { 61 | if (object instanceof String message) { 62 | if (message.startsWith("CLaJ")) { 63 | this.key = message; 64 | link.get(key + "#" + ip + ":" + port); 65 | } else if (message.equals("new")) { 66 | try { 67 | createRedirector(ip, port, key); 68 | } catch (Exception ignored) {} 69 | } else 70 | Call.sendMessage(message); 71 | } 72 | } 73 | }); 74 | 75 | client.connect(5000, ip, port, port); 76 | clients.add(client); 77 | 78 | return client; 79 | } 80 | 81 | public static void createRedirector(String ip, int port, String key) throws IOException { 82 | Client client = new Client(8192, 8192, new Serializer()); 83 | Threads.daemon("CLaJ Redirector", client::run); 84 | 85 | client.addListener(serverListener); 86 | client.addListener(new NetListener() { 87 | @Override 88 | public void connected(Connection connection) { 89 | client.sendTCP("host" + key); 90 | } 91 | }); 92 | 93 | client.connect(5000, ip, port, port); 94 | clients.add(client); 95 | } 96 | 97 | public static void joinRoom(String ip, int port, String key, Runnable success) { 98 | logic.reset(); 99 | net.reset(); 100 | 101 | netClient.beginConnecting(); 102 | net.connect(ip, port, () -> { 103 | if (!net.client()) return; 104 | success.run(); 105 | 106 | ByteBuffer buffer = ByteBuffer.allocate(8192); 107 | buffer.put(Serializer.linkID); 108 | TypeIO.writeString(buffer, "join" + key); 109 | 110 | buffer.limit(buffer.position()).position(0); 111 | net.send(buffer, true); 112 | }); 113 | } 114 | 115 | public static void clear() { 116 | clients.each(Client::close); 117 | clients.clear(); 118 | } 119 | 120 | // endregion 121 | 122 | public static Link parseLink(String link) throws IOException { 123 | link = link.trim(); 124 | if (!link.startsWith("CLaJ")) throw new IOException("@join.missing-prefix"); 125 | 126 | int hash = link.indexOf('#'); 127 | if (hash != 42 + 4) throw new IOException("@join.wrong-key-length"); 128 | 129 | int semicolon = link.indexOf(':'); 130 | if (semicolon == -1) throw new IOException("@join.semicolon-not-found"); 131 | 132 | int port; 133 | try { 134 | port = Integer.parseInt(link.substring(semicolon + 1)); 135 | } catch (Throwable ignored) { 136 | throw new IOException("@join.failed-to-parse"); 137 | } 138 | 139 | return new Link(link.substring(0, hash), link.substring(hash + 1, semicolon), port); 140 | } 141 | 142 | public record Link(String key, String ip, int port) {} 143 | 144 | public static class Serializer extends PacketSerializer { 145 | 146 | public static final byte linkID = -3; 147 | 148 | @Override 149 | public void write(ByteBuffer buffer, Object object) { 150 | if (object instanceof String link) { 151 | buffer.put(linkID); 152 | TypeIO.writeString(buffer, link); 153 | } else 154 | super.write(buffer, object); 155 | } 156 | 157 | @Override 158 | public Object read(ByteBuffer buffer) { 159 | if (buffer.get() == linkID) return TypeIO.readString(buffer); 160 | 161 | buffer.position(buffer.position() - 1); 162 | return super.read(buffer); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/java/scheme/Main.java: -------------------------------------------------------------------------------- 1 | package scheme; 2 | 3 | import arc.graphics.g2d.Draw; 4 | import arc.util.Log; 5 | import arc.util.Tmp; 6 | import mindustry.content.Blocks; 7 | import mindustry.game.Schematics; 8 | import mindustry.gen.Building; 9 | import mindustry.mod.Mod; 10 | import mindustry.type.Item; 11 | import mindustry.ui.CoreItemsDisplay; 12 | import mindustry.world.Tile; 13 | import mindustry.world.blocks.distribution.Router; 14 | import mindustry.world.blocks.logic.LogicDisplay; 15 | import scheme.moded.ModedGlyphLayout; 16 | import scheme.moded.ModedSchematics; 17 | import scheme.tools.MessageQueue; 18 | import scheme.tools.RainbowTeam; 19 | import scheme.ui.MapResizeFix; 20 | 21 | import static arc.Core.*; 22 | import static mindustry.Vars.*; 23 | import static scheme.SchemeVars.*; 24 | 25 | public class Main extends Mod { 26 | 27 | public Main() { 28 | // well, after the 136th build, it became much easier 29 | maxSchematicSize = 512; 30 | 31 | // mod reimported through mods dialog 32 | if (schematics.getClass().getSimpleName().startsWith("Moded")) return; 33 | 34 | assets.load(schematics = m_schematics = new ModedSchematics()); 35 | assets.unload(Schematics.class.getSimpleName()); // prevent dual loading 36 | } 37 | 38 | @Override 39 | public void init() { 40 | ServerIntegration.load(); 41 | ClajIntegration.load(); 42 | ModedGlyphLayout.load(); 43 | SchemeVars.load(); 44 | MapResizeFix.load(); 45 | MessageQueue.load(); 46 | RainbowTeam.load(); 47 | 48 | ui.schematics = schemas; // do it before build hudfrag 49 | ui.listfrag = listfrag; 50 | 51 | m_settings.apply(); // sometimes settings are not self-applying 52 | 53 | hudfrag.build(ui.hudGroup); 54 | listfrag.build(ui.hudGroup); 55 | shortfrag.build(ui.hudGroup); 56 | consolefrag.build(); 57 | corefrag.build(ui.hudGroup); 58 | 59 | control.setInput(m_input.asHandler()); 60 | renderer.addEnvRenderer(0, render::draw); 61 | 62 | if (m_schematics.requiresDialog) ui.showOkText("@rename.name", "@rename.text", () -> {}); 63 | if (settings.getBool("welcome")) ui.showOkText("@welcome.name", "@welcome.text", () -> {}); 64 | if (settings.getBool("check4update")); 65 | 66 | if (/*SchemeUpdater.installed("miner-tools")*/true) { // very sad but they are incompatible 67 | ui.showOkText("@incompatible.name", "@incompatible.text", () -> {}); 68 | ui.hudGroup.fill(cont -> { // crutch to prevent crash 69 | cont.visible = false; 70 | cont.add(new CoreItemsDisplay()); 71 | }); 72 | } 73 | 74 | Blocks.distributor.buildType = () -> ((Router) Blocks.distributor).new RouterBuild() { 75 | @Override 76 | public boolean canControl() { return true; } 77 | 78 | @Override 79 | public Building getTileTarget(Item item, Tile from, boolean set) { 80 | Building target = super.getTileTarget(item, from, set); 81 | 82 | if (unit != null && isControlled() && unit.isShooting()) { 83 | float angle = angleTo(unit.aimX(), unit.aimY()); 84 | Tmp.v1.set(block.size * tilesize, 0f).rotate(angle).add(this); 85 | 86 | Building other = world.buildWorld(Tmp.v1.x, Tmp.v1.y); 87 | if (other != null && other.acceptItem(this, item)) target = other; 88 | } 89 | 90 | return target; 91 | } 92 | }; 93 | 94 | content.blocks().each(block -> block instanceof LogicDisplay, block -> block.buildType = () -> ((LogicDisplay) block).new LogicDisplayBuild() { 95 | @Override 96 | public void draw() { 97 | super.draw(); 98 | if (render.borderless) Draw.draw(Draw.z(), () -> { 99 | Draw.rect(Draw.wrap(buffer.getTexture()), x, y, block.region.width * Draw.scl, -block.region.height * Draw.scl); 100 | }); 101 | } 102 | }); 103 | } 104 | 105 | public static void log(String info) { 106 | app.post(() -> Log.infoTag("Scheme", info)); 107 | } 108 | 109 | public static void error(Throwable info) { 110 | app.post(() -> Log.err("Scheme", info)); 111 | } 112 | 113 | public static void copy(String text) { 114 | if (text == null) return; 115 | 116 | app.setClipboardText(text); 117 | ui.showInfoFade("@copied"); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/java/scheme/SchemeVars.java: -------------------------------------------------------------------------------- 1 | package scheme; 2 | 3 | import arc.struct.Seq; 4 | import mindustry.core.UI; 5 | import mindustry.type.Item; 6 | import mindustry.type.StatusEffect; 7 | import mindustry.type.UnitType; 8 | import scheme.moded.*; 9 | import scheme.tools.*; 10 | import scheme.tools.admins.AdminsTools; 11 | import scheme.ui.*; 12 | import scheme.ui.dialogs.*; 13 | 14 | import static arc.Core.*; 15 | import static mindustry.Vars.*; 16 | 17 | public class SchemeVars { 18 | 19 | public static ModedSchematics m_schematics; 20 | public static ModedInputHandler m_input; 21 | 22 | public static AdminsTools admins; 23 | public static RendererTools render; 24 | public static BuildingTools build; 25 | 26 | public static AdminsConfigDialog adminscfg; 27 | public static RendererConfigDialog rendercfg; 28 | 29 | public static AISelectDialog ai; 30 | public static TeamSelectDialog team; 31 | public static TileSelectDialog tile; 32 | public static TagSelectDialog tag; 33 | 34 | public static ContentSelectDialog unit; 35 | public static ContentSelectDialog effect; 36 | public static ContentSelectDialog item; 37 | 38 | public static SettingsMenuDialog m_settings; 39 | public static SchemasDialog schemas; 40 | public static ImageParserDialog parser; 41 | public static WaveApproachingDialog approaching; 42 | public static JoinViaClajDialog joinViaClaj; 43 | public static ManageRoomsDialog manageRooms; 44 | 45 | public static HudFragment hudfrag; 46 | public static PlayerListFragment listfrag; 47 | public static ShortcutFragment shortfrag; 48 | public static ConsoleFragment consolefrag; 49 | public static CoreInfoFragment corefrag; 50 | 51 | public static Seq clajURLs = Seq.with( 52 | "n3.xpdustry.com:7025", 53 | "37.187.73.180:7025", 54 | "claj.phoenix-network.dev:4000", 55 | "167.235.159.121:4000", 56 | "new.xem8k5.top:1050", 57 | "123.149.153.233:1050" 58 | ); 59 | 60 | /** List of ip servers that block the mod. */ 61 | public static Seq antiModIPs = Seq.with( 62 | "play.thedimas.pp.ua", 63 | "91.209.226.11"); 64 | 65 | public static void load() { 66 | // m_schematics is created in Main to prevent dual loading 67 | m_input = mobile ? new ModedMobileInput() : new ModedDesktopInput(); 68 | 69 | admins = AdminsConfigDialog.getTools(); 70 | render = new RendererTools(); 71 | build = new BuildingTools(); 72 | 73 | adminscfg = new AdminsConfigDialog(); 74 | rendercfg = new RendererConfigDialog(); 75 | 76 | ai = new AISelectDialog(); 77 | team = new TeamSelectDialog(); 78 | tile = new TileSelectDialog(); 79 | tag = new TagSelectDialog(); 80 | 81 | unit = new ContentSelectDialog<>("@select.unit", content.units(), 0, 25, 1, value -> { 82 | return value == 0 ? "@select.unit.clear" : bundle.format("select.units", value); 83 | }); 84 | effect = new ContentSelectDialog<>("@select.effect", content.statusEffects(), 0, 5 * 3600, 600, value -> { 85 | return value == 0 ? "@select.effect.clear" : bundle.format("select.seconds", value / 60f); 86 | }); 87 | item = new ContentSelectDialog<>("@select.item", content.items(), -10000, 10000, 500, value -> { 88 | return value == 0 ? "@select.item.clear" : bundle.format("select.items", UI.formatAmount(value.longValue())); 89 | }); 90 | 91 | m_settings = new SettingsMenuDialog(); 92 | schemas = new SchemasDialog(); 93 | parser = new ImageParserDialog(); 94 | approaching = new WaveApproachingDialog(); 95 | joinViaClaj = new JoinViaClajDialog(); 96 | manageRooms = new ManageRoomsDialog(); 97 | 98 | hudfrag = new HudFragment(); 99 | listfrag = new PlayerListFragment(); 100 | shortfrag = new ShortcutFragment(); 101 | consolefrag = new ConsoleFragment(); 102 | corefrag = new CoreInfoFragment(); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/java/scheme/ServerIntegration.java: -------------------------------------------------------------------------------- 1 | package scheme; 2 | 3 | import arc.Events; 4 | import arc.struct.IntMap; 5 | import arc.util.Reflect; 6 | import arc.util.Strings; 7 | import mindustry.game.EventType.*; 8 | import mindustry.gen.Call; 9 | import mindustry.io.JsonIO; 10 | 11 | import static arc.Core.*; 12 | import static mindustry.Vars.*; 13 | import static scheme.SchemeVars.*; 14 | 15 | /** 16 | * Package manager for getting player data from the server. 17 | *

18 | * How it works: 19 | * When a player connects to a server it will send a SendMeSubtitle packet, 20 | * players with this mod will send a MySubtitle packet in response. 21 | * Then the server will send a Subtitles packet to all connections 22 | * containing a list of ids and subtitles of players who use this mod. 23 | *

24 | * Reference implementation: server region in the {@link ServerIntegration#load() load method} 25 | */ 26 | @SuppressWarnings("unchecked") 27 | public class ServerIntegration { 28 | 29 | /** List of user ids that use this mod. */ 30 | public static IntMap SSUsers = new IntMap<>(8); 31 | 32 | /** Host's player id. If you're joining a headless server it will be -1. */ 33 | public static int hostID = -1; 34 | 35 | /** Whether the player received subtitles from the server. */ 36 | public static boolean hasData; 37 | 38 | public static void load() { 39 | // region Server 40 | 41 | Events.on(PlayerJoin.class, event -> Call.clientPacketReliable(event.player.con, "SendMeSubtitle", player == null ? null : String.valueOf(player.id))); 42 | Events.on(PlayerLeave.class, event -> { 43 | if (event.player != null/* how? */) SSUsers.remove(event.player.id); 44 | }); 45 | 46 | netServer.addPacketHandler("MySubtitle", (target, args) -> { 47 | SSUsers.put(target.id, args); 48 | Call.clientPacketReliable("Subtitles", JsonIO.write(SSUsers)); 49 | }); 50 | 51 | // endregion 52 | // region Client 53 | 54 | Events.run(HostEvent.class, ServerIntegration::clear); 55 | Events.run(ClientPreConnectEvent.class, ServerIntegration::clear); 56 | 57 | netClient.addPacketHandler("SendMeSubtitle", args -> { 58 | if (antiModIPs.contains(Reflect.get(ui.join, "lastIp"))) return; 59 | 60 | Call.serverPacketReliable("MySubtitle", settings.getString("subtitle")); 61 | if (args != null) hostID = Strings.parseInt(args, -1); 62 | }); 63 | 64 | netClient.addPacketHandler("Subtitles", args -> { 65 | SSUsers = JsonIO.read(IntMap.class, args); 66 | hasData = true; 67 | }); 68 | 69 | // endregion 70 | } 71 | 72 | /** Clears all data about users. */ 73 | public static void clear() { 74 | SSUsers.clear(); 75 | hostID = -1; 76 | hasData = false; 77 | 78 | // put the host's subtitle so that you do not copy the int map later 79 | SSUsers.put(player.id, settings.getString("subtitle")); 80 | } 81 | 82 | /** Returns whether the user with the given id is using a mod. */ 83 | public static boolean isModded(int id) { 84 | return SSUsers.containsKey(id) || player.id == id; // of course you are a modded player 85 | } 86 | 87 | /** Returns the user type with the given id: host, no data, mod or vanilla. */ 88 | public static String type(int id) { 89 | if (hostID == id) return "trace.type.host"; 90 | return !hasData && net.client() ? "trace.type.nodata" : isModded(id) ? "trace.type.mod" : "trace.type.vanilla"; 91 | } 92 | 93 | /** Returns the user type with subtitle. */ 94 | public static String tooltip(int id) { 95 | if (player.id == id) return "@trace.type.self"; 96 | return bundle.get(type(id)) + (isModded(id) ? "\n" + SSUsers.get(id) : ""); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/java/scheme/ai/GammaAI.java: -------------------------------------------------------------------------------- 1 | package scheme.ai; 2 | 3 | import arc.func.Cons; 4 | import arc.math.geom.Position; 5 | import arc.math.geom.Vec2; 6 | import arc.scene.style.Drawable; 7 | import arc.struct.Seq; 8 | import mindustry.entities.units.AIController; 9 | import mindustry.entities.units.BuildPlan; 10 | import mindustry.game.Teams.BlockPlan; 11 | import mindustry.gen.Icon; 12 | import mindustry.gen.Player; 13 | import mindustry.world.Tile; 14 | import mindustry.world.blocks.ConstructBlock.ConstructBuild; 15 | 16 | import static mindustry.Vars.*; 17 | import static scheme.SchemeVars.*; 18 | 19 | public class GammaAI extends AIController { 20 | 21 | public static final String tooltip = null; 22 | 23 | public static Updater move = Updater.none; 24 | public static Updater build = Updater.none; 25 | public static float range = 80f; 26 | public static float speed = 1f; 27 | 28 | public Player target; 29 | public Position cache; 30 | public Position aim; 31 | 32 | @Override 33 | public void updateUnit() { 34 | if (target == null || !target.isAdded()) return; 35 | 36 | move.update.get(this); 37 | build.update.get(this); 38 | 39 | player.boosting = target.boosting; 40 | aim = new Vec2(target.mouseX, target.mouseY); 41 | } 42 | 43 | public void draw() { 44 | if (target != null && target != player) render.drawPlans(target.unit(), build != Updater.destroy); 45 | } 46 | 47 | public void block(Tile tile, boolean breaking) { 48 | var build = tile.build; 49 | unit.addBuild(breaking 50 | ? new BuildPlan(tile.x, tile.y, build.rotation, build instanceof ConstructBuild c ? c.previous : build.block) 51 | : new BuildPlan(tile.x, tile.y)); 52 | } 53 | 54 | public void cache() { 55 | target = ai.players.get(); 56 | cache = target == player ? player.tileOn() : target; 57 | } 58 | 59 | public float speed() { 60 | return unit.speed() * speed / 100f; 61 | } 62 | 63 | public enum Updater { 64 | none(Icon.line, ai -> {}), 65 | circle(Icon.commandRally, ai -> { 66 | ai.circle(ai.cache, range, ai.speed()); 67 | ai.faceMovement(); 68 | ai.stopShooting(); 69 | }), 70 | cursor(Icon.diagonal, ai -> moveTo(ai, ai.aim)), 71 | follow(Icon.resize, ai -> moveTo(ai, ai.cache)), 72 | help(Icon.add, ai -> { 73 | if (ai.target.unit().plans.isEmpty() || !ai.target.unit().updateBuilding) return; 74 | ai.unit.addBuild(ai.target.unit().buildPlan()); 75 | }), 76 | destroy(Icon.hammer, ai -> {}), // works through events 77 | repair(Icon.wrench, ai -> { 78 | if (ai.target.team().data().plans.isEmpty() || !ai.target.unit().updateBuilding) return; 79 | BlockPlan plan = Seq.with(ai.target.team().data().plans).min(p -> ai.unit.dst(p.x * tilesize, p.y * tilesize)); 80 | ai.unit.addBuild(new BuildPlan(plan.x, plan.y, plan.rotation, content.block(plan.block), plan.config)); 81 | }); 82 | 83 | public final Drawable icon; 84 | public final Cons update; 85 | 86 | private Updater(Drawable icon, Cons update) { 87 | this.icon = icon; 88 | this.update = update; 89 | } 90 | 91 | public String tooltip() { 92 | return "@gamma." + name(); 93 | } 94 | 95 | private static void moveTo(GammaAI ai, Position pos) { 96 | ai.moveTo(pos, range / 3f); 97 | ai.unit.vel(ai.unit.vel().limit(ai.speed())); 98 | if (ai.unit.moving()) ai.faceMovement(); 99 | else ai.unit.lookAt(ai.aim); 100 | ai.unit.aim(ai.aim); 101 | ai.unit.controlWeapons(true, player.shooting = ai.target.shooting); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/java/scheme/ai/NetMinerAI.java: -------------------------------------------------------------------------------- 1 | package scheme.ai; 2 | 3 | import mindustry.ai.types.MinerAI; 4 | import mindustry.gen.Building; 5 | import mindustry.gen.Call; 6 | import mindustry.type.Item; 7 | 8 | import static mindustry.Vars.*; 9 | 10 | /** Fix for working on servers. */ 11 | public class NetMinerAI extends MinerAI { 12 | 13 | public static Item priorityItem; 14 | 15 | @Override 16 | public void updateMovement() { 17 | Building core = unit.closestCore(); 18 | if (!unit.canMine() || core == null) return; 19 | 20 | if (mining) { 21 | if (priorityItem != null && indexer.hasOre(priorityItem) && unit.canMine(priorityItem) && core.acceptStack(priorityItem, 1, unit) != 0) 22 | targetItem = priorityItem; // player can select an item to dig 23 | 24 | super.updateMovement(); 25 | } else { 26 | unit.mineTile = null; 27 | 28 | if (unit.stack.amount == 0) { 29 | mining = true; 30 | return; 31 | } 32 | 33 | if (unit.within(core, unit.type.range)) { 34 | Call.transferInventory(player, core); 35 | mining = true; 36 | } 37 | 38 | circle(core, unit.type.range / 1.8f); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/java/scheme/moded/ModedInputHandler.java: -------------------------------------------------------------------------------- 1 | package scheme.moded; 2 | 3 | import arc.graphics.Color; 4 | import arc.graphics.g2d.Draw; 5 | import arc.graphics.g2d.Lines; 6 | import arc.math.Mathf; 7 | import arc.math.geom.Geometry; 8 | import arc.math.geom.Vec2; 9 | import arc.struct.Seq; 10 | import mindustry.content.Blocks; 11 | import mindustry.core.World; 12 | import mindustry.entities.units.BuildPlan; 13 | import mindustry.gen.Player; 14 | import mindustry.graphics.Pal; 15 | import mindustry.input.InputHandler; 16 | import mindustry.input.Placement; 17 | import mindustry.input.Placement.NormalizeDrawResult; 18 | import mindustry.world.Tile; 19 | import scheme.tools.BuildingTools.Mode; 20 | 21 | import static arc.Core.*; 22 | import static mindustry.Vars.*; 23 | import static scheme.SchemeVars.*; 24 | 25 | /** Last update - Jul 19, 2022 */ 26 | public interface ModedInputHandler { 27 | 28 | public int sizeX = mobile ? 0 : -16; 29 | public int sizeY = mobile ? 32 : -16; 30 | 31 | public default void modedInput() {} 32 | 33 | public default void buildInput() {} 34 | 35 | public boolean hasMoved(int x, int y); 36 | 37 | public void changePanSpeed(float value); 38 | 39 | public void lockMovement(); 40 | 41 | public void lockShooting(); 42 | 43 | public void observe(Player target); 44 | 45 | public void flush(Seq plans); 46 | 47 | public default void flushLastRemoved() { 48 | flush(build.removed); 49 | build.removed.clear(); 50 | } 51 | 52 | public default void flushBuildingTools() { 53 | if (build.mode != Mode.remove) flush(build.plan); 54 | else build.plan.each(player.unit()::addBuild); 55 | build.plan.clear(); 56 | } 57 | 58 | public InputHandler asHandler(); 59 | 60 | // methods that exist but, who knows why, not available 61 | public default Tile tileAt() { 62 | return world.tiles.getc(tileX(), tileY()); 63 | } 64 | 65 | public default int tileX() { 66 | return World.toTile(input.mouseWorldX()); 67 | } 68 | 69 | public default int tileY() { 70 | return World.toTile(input.mouseWorldY()); 71 | } 72 | 73 | // some drawing methods 74 | public default void drawSize(int x1, int y1, int x2, int y2, int maxLength) { 75 | String x = getSize(Math.abs(x1 - x2), maxLength); 76 | String y = getSize(Math.abs(y1 - y2), maxLength); 77 | ui.showLabel(x + ", " + y, 0.02f, x2 * tilesize + sizeX, y2 * tilesize + sizeY); 78 | } 79 | 80 | public default String getSize(int size, int maxLength) { 81 | return ++size >= maxLength ? "[accent]" + maxLength + "[]" : String.valueOf(size); 82 | } 83 | 84 | public default void drawEditSelection(int x1, int y1, int x2, int y2, int maxLength){ 85 | NormalizeDrawResult result = Placement.normalizeDrawArea(Blocks.air, x1, y1, x2, y2, false, maxLength, 1f); 86 | 87 | drawSize(x1, y1, x2, y2, maxLength); 88 | Lines.stroke(2f); 89 | 90 | Draw.color(Pal.darkerMetal); 91 | Lines.rect(result.x, result.y - 1, result.x2 - result.x, result.y2 - result.y); 92 | Draw.color(Pal.darkMetal); 93 | Lines.rect(result.x, result.y, result.x2 - result.x, result.y2 - result.y); 94 | } 95 | 96 | public default void drawEditSelection(int x, int y, int radius) { 97 | Vec2[] polygons = Geometry.pixelCircle(radius, (index, cx, cy) -> Mathf.dst(cx, cy, index, index) < index); 98 | Lines.stroke(2f); 99 | 100 | Draw.color(Pal.darkerMetal); 101 | Lines.poly(polygons, x * tilesize - 4, y * tilesize - 5, tilesize); 102 | Draw.color(Pal.darkMetal); 103 | Lines.poly(polygons, x * tilesize - 4, y * tilesize - 4, tilesize); 104 | } 105 | 106 | public default void drawLocked(float x, float y) { 107 | ui.showLabel(bundle.format( 108 | Mathf.absin(25f, 1f) < .5f ? "locked.info" : "locked.bind", 109 | Color.orange.cpy().lerp(Color.scarlet, Mathf.absin(3f, 1f))), 0.02f, x, y); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/java/scheme/moded/ModedMobileInput.java: -------------------------------------------------------------------------------- 1 | package scheme.moded; 2 | 3 | import arc.scene.ui.layout.Table; 4 | import arc.struct.Seq; 5 | import mindustry.content.Blocks; 6 | import mindustry.entities.units.BuildPlan; 7 | import mindustry.gen.Player; 8 | import mindustry.gen.Unit; 9 | import mindustry.input.*; 10 | import mindustry.input.Placement.NormalizeResult; 11 | import mindustry.world.blocks.power.PowerNode; 12 | import scheme.ai.GammaAI; 13 | import scheme.tools.BuildingTools.Mode; 14 | 15 | import static arc.Core.*; 16 | import static mindustry.Vars.*; 17 | import static mindustry.input.PlaceMode.*; 18 | import static scheme.SchemeVars.*; 19 | 20 | /** Last update - Feb 10, 2023 */ 21 | public class ModedMobileInput extends MobileInput implements ModedInputHandler { 22 | 23 | public boolean using, movementLocked, lastTouched, shootingLocked; 24 | public int buildX, buildY, lastX, lastY, lastSize = 8; 25 | 26 | public Player observed; 27 | 28 | private boolean isRelease() { 29 | return lastTouched && !input.isTouched(0); 30 | } 31 | 32 | private boolean isTap() { 33 | return !lastTouched && input.isTouched(0); 34 | } 35 | 36 | @Override 37 | protected void removeSelection(int x1, int y1, int x2, int y2, boolean flush) { 38 | build.save(x1, y1, x2, y2, maxSchematicSize); 39 | super.removeSelection(x1, y1, x2, y2, flush, maxSchematicSize); 40 | } 41 | 42 | @Override 43 | public void buildPlacementUI(Table table) { 44 | super.buildPlacementUI(table); 45 | 46 | var button = table.getChildren().get(table.getChildren().size - 1); 47 | button.clicked(() -> { 48 | if (m_schematics.isCursed(selectPlans)) admins.flush(selectPlans); 49 | }); 50 | 51 | int size = button.getListeners().size; 52 | button.getListeners().swap(size - 1, size - 2); 53 | } 54 | 55 | @Override 56 | public void drawTop() { 57 | if (mode == schematicSelect) { 58 | drawSelection(lineStartX, lineStartY, lastLineX, lastLineY, maxSchematicSize); 59 | drawSize(lineStartX, lineStartY, lastLineX, lastLineY, maxSchematicSize); 60 | } else if (mode == breaking && lineMode) 61 | drawSize(lineStartX, lineStartY, tileX(), tileY(), maxSchematicSize); 62 | else if (mode == rebuildSelect) 63 | drawRebuildSelection(lineStartX, lineStartY, lastLineX, lastLineY); 64 | 65 | if (using) { 66 | if (build.mode == Mode.edit) 67 | drawEditSelection(buildX, buildY, lastX, lastY, maxSchematicSize); 68 | 69 | if (build.mode == Mode.connect && isPlacing()) 70 | drawEditSelection(lastX - build.size + 1, lastY - build.size + 1, lastX + build.size - 1, lastY + build.size - 1, 256); 71 | } 72 | 73 | if (build.mode == Mode.brush) 74 | drawEditSelection(lastX, lastY, build.size); 75 | 76 | drawCommanded(); 77 | } 78 | 79 | @Override 80 | public void drawBottom() { 81 | if (!build.isPlacing()) super.drawBottom(); 82 | else build.plan.each(plan -> { 83 | plan.animScale = 1f; 84 | if (build.mode != Mode.remove) drawPlan(plan); 85 | else drawBreaking(plan); 86 | }); 87 | if (ai.ai instanceof GammaAI gamma) gamma.draw(); 88 | } 89 | 90 | @Override 91 | public void update() { 92 | super.update(); 93 | 94 | if (locked()) return; 95 | 96 | if (observed != null) { 97 | camera.position.set(observed.unit()); // idk why, but unit moves smoother 98 | if (input.isTouched(0) && !scene.hasMouse()) observed = null; 99 | } 100 | 101 | buildInput(); 102 | if (movementLocked) drawLocked(player.unit().x, player.unit().y); 103 | } 104 | 105 | @Override 106 | protected void updateMovement(Unit unit) { 107 | if (ai.ai != null && !input.isTouched()) { 108 | if (!movementLocked) camera.position.set(unit.x, unit.y); 109 | ai.update(); 110 | } else if (!movementLocked) super.updateMovement(unit); 111 | 112 | if (shootingLocked) { 113 | unit.aimLook(player.mouseX, player.mouseY); 114 | unit.controlWeapons(true, false); 115 | player.shooting = unit.isShooting = false; 116 | } 117 | } 118 | 119 | public void buildInput() { 120 | if (!hudfrag.building.fliped) build.setMode(Mode.none); 121 | if (build.mode == Mode.none) return; 122 | 123 | int cursorX = tileX(); 124 | int cursorY = tileY(); 125 | 126 | boolean has = hasMoved(cursorX, cursorY); 127 | if (has) build.plan.clear(); 128 | 129 | if (using) { 130 | if (build.mode == Mode.drop) build.drop(cursorX, cursorY); 131 | if (has) { 132 | if (build.mode == Mode.replace) build.replace(cursorX, cursorY); 133 | if (build.mode == Mode.remove) build.remove(cursorX, cursorY); 134 | if (build.mode == Mode.connect) { 135 | if (block instanceof PowerNode == false) block = Blocks.powerNode; 136 | build.connect(cursorX, cursorY, (x, y) -> { 137 | updateLine(x, y); 138 | build.plan.addAll(linePlans).remove(0); 139 | }); 140 | } 141 | 142 | if (build.mode == Mode.fill) build.fill(buildX, buildY, cursorX, cursorY, maxSchematicSize); 143 | if (build.mode == Mode.circle) build.circle(cursorX, cursorY); 144 | if (build.mode == Mode.square) build.square(cursorX, cursorY, (x1, y1, x2, y2) -> { 145 | updateLine(x1, y1, x2, y2); 146 | build.plan.addAll(linePlans); 147 | }); 148 | 149 | if (build.mode == Mode.brush) admins.brush(cursorX, cursorY, build.size); 150 | 151 | lastX = cursorX; 152 | lastY = cursorY; 153 | lastSize = build.size; 154 | linePlans.clear(); 155 | } 156 | 157 | if (isRelease()) { 158 | flushBuildingTools(); 159 | 160 | if (build.mode == Mode.pick) tile.select(cursorX, cursorY); 161 | if (build.mode == Mode.edit) { 162 | NormalizeResult result = Placement.normalizeArea(buildX, buildY, cursorX, cursorY, 0, false, maxSchematicSize); 163 | admins.fill(result.x, result.y, result.x2, result.y2); 164 | } 165 | } 166 | } 167 | 168 | if (isTap() && !scene.hasMouse()) { 169 | buildX = cursorX; 170 | buildY = cursorY; 171 | using = true; 172 | } 173 | 174 | if (isRelease()) using = false; 175 | 176 | lastTouched = input.isTouched(); 177 | } 178 | 179 | public boolean hasMoved(int x, int y) { 180 | return lastX != x || lastY != y || lastSize != build.size; 181 | } 182 | 183 | // there is nothing because, you know, it's mobile 184 | public void changePanSpeed(float value) {} 185 | 186 | public void lockMovement() { 187 | movementLocked = !movementLocked; 188 | } 189 | 190 | public void lockShooting() { 191 | shootingLocked = !shootingLocked; 192 | } 193 | 194 | public void observe(Player target) { 195 | observed = target; 196 | } 197 | 198 | public void flush(Seq plans) { 199 | flushPlans(plans); 200 | } 201 | 202 | public InputHandler asHandler() { 203 | return this; 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/java/scheme/tools/ImageParser.java: -------------------------------------------------------------------------------- 1 | package scheme.tools; 2 | 3 | import arc.files.Fi; 4 | import arc.graphics.Color; 5 | import arc.graphics.Pixmap; 6 | import arc.graphics.Pixmaps; 7 | import arc.math.geom.Point2; 8 | import arc.struct.Seq; 9 | import arc.struct.StringMap; 10 | import arc.util.Strings; 11 | import mindustry.content.Blocks; 12 | import mindustry.game.Schematic; 13 | import mindustry.game.Schematic.Stile; 14 | import mindustry.logic.LExecutor; 15 | import mindustry.world.blocks.logic.LogicBlock; 16 | import mindustry.world.blocks.logic.LogicDisplay; 17 | import mindustry.world.blocks.logic.LogicBlock.LogicLink; 18 | 19 | import static mindustry.Vars.*; 20 | 21 | public class ImageParser { 22 | 23 | public static final String processorSeparator = "#"; 24 | public static final String flush = "drawflush display1\n"; 25 | 26 | // region parse 27 | 28 | /** Reads an image from a file and converts it to schematic. */ 29 | public static Schematic parseSchematic(Fi file, Config cfg) { 30 | try { 31 | return parseSchematic(file.nameWithoutExtension(), new Pixmap(file), cfg); 32 | } catch (Throwable ingored) { // file is corrupted or idk what 33 | return null; 34 | } 35 | } 36 | 37 | /** Converts a pixmap to schematic with logical processors and displays. */ 38 | public static Schematic parseSchematic(String name, Pixmap image, Config cfg) { 39 | final int size = cfg.display.size, pixels = cfg.display.displaySize; 40 | final int width = cfg.columns * size, height = cfg.rows * size; 41 | 42 | Pixmaps.flip(image); 43 | if (cfg.filter) 44 | image = Pixmaps.scale(image, pixels * cfg.columns, pixels * cfg.rows, true); 45 | else 46 | image = Pixmaps.scale(image, (float) pixels * cfg.columns / image.width, (float) pixels * cfg.rows / image.height); 47 | 48 | // region creating tiles 49 | 50 | int layer = 0; // the current layer on which the processors are located 51 | Seq available = new Seq<>(); // sequence of all positions available to the processor 52 | 53 | Seq tiles = new Seq<>(); 54 | for (int row = 0; row < cfg.rows; row++) { 55 | for (int column = 0; column < cfg.columns; column++) { 56 | 57 | int x = column * size - cfg.offset(), y = row * size - cfg.offset(); 58 | tiles.add(new Stile(cfg.display, x, y, null, (byte) 0)); 59 | 60 | Display block = new Display(image, column * pixels, row * pixels, pixels); 61 | for (String code : parseCode(block).split(processorSeparator)) { 62 | 63 | var pos = next(available, x, y, cfg.range()); 64 | if (pos == null) refill(available, layer++, width, height); 65 | 66 | pos = next(available, x, y, cfg.range()); 67 | if (pos == null) return null; // processor range is too small 68 | 69 | byte[] compressed = LogicBlock.compress(code, Seq.with(new LogicLink(x - pos.x, y - pos.y, "display1", true))); 70 | tiles.add(new Stile(cfg.processor, pos.x, pos.y, compressed, (byte) 0)); 71 | available.remove(pos); // this position is now filled 72 | } 73 | } 74 | } 75 | 76 | // endregion 77 | 78 | int minx = tiles.min(st -> st.x).x; 79 | int miny = tiles.min(st -> st.y).y; 80 | 81 | tiles.each(st -> { 82 | st.x -= minx; 83 | st.y -= miny; 84 | }); 85 | 86 | return new Schematic(tiles, StringMap.of("name", name), width + layer * 2, height + layer * 2); 87 | } 88 | 89 | /** Converts a display into a sequence of instructions for a logical processor. */ 90 | public static String parseCode(Display display) { 91 | StringBuilder code = new StringBuilder(); 92 | 93 | Color last = null; 94 | int instructions = 0; 95 | for (Line rect : parseLines(display).sort(rect -> rect.color.abgr())) { 96 | if (!rect.color.equals(last)) { 97 | last = rect.color; 98 | 99 | code.append(rect.colorCode()); 100 | instructions++; 101 | } 102 | 103 | code.append(rect.rectCode()); 104 | instructions++; 105 | 106 | if (instructions + 2 >= LExecutor.maxInstructions) { 107 | code.append(flush).append(processorSeparator); 108 | instructions = 0; 109 | last = null; 110 | 111 | continue; 112 | } 113 | 114 | if (instructions % LExecutor.maxGraphicsBuffer <= 1) { 115 | code.append(flush).append(rect.colorCode()); 116 | instructions += 2; 117 | } 118 | } 119 | 120 | return code.append(flush).toString(); 121 | } 122 | 123 | /** Converts a display into a sequence of colored lines. */ 124 | public static Seq parseLines(Display display) { 125 | var result = new Seq(); 126 | 127 | for (int y = 0; y < display.size; y++) { 128 | for (int x = 0; x < display.size; x++) { 129 | int raw = display.get(x, y); 130 | 131 | int length = 1; 132 | for (int i = x + 1; i < display.size; i++) 133 | if (display.get(i, y) == raw) length++; 134 | else break; 135 | 136 | result.add(new Line(new Color(raw), x, y, length)); 137 | x += length - 1; // skip same pixels 138 | } 139 | } 140 | 141 | return result; 142 | } 143 | 144 | // endregion 145 | // region available positions 146 | 147 | private static void refill(Seq available, int layer, int width, int height) { 148 | int amount = 2 * (width + height) + 8 * layer + 4; 149 | Point2 pos = new Point2(-layer - 1, -layer), dir = new Point2(0, 1); 150 | 151 | for (int i = 0; i < amount; i++) { 152 | available.add(pos.cpy()); 153 | pos.add(dir); 154 | 155 | if (pos.equals(-layer - 1, height + layer)) dir.set(1, 0); 156 | if (pos.equals(width + layer, height + layer)) dir.set(0, -1); 157 | if (pos.equals(width + layer, -layer - 1)) dir.set(-1, 0); 158 | } 159 | } 160 | 161 | private static Point2 next(Seq available, int x, int y, float range) { 162 | var inRange = available.select(point -> point.dst(x, y) < range); 163 | return inRange.isEmpty() ? null : inRange.first(); 164 | } 165 | 166 | // endregion 167 | 168 | public record Display(Pixmap pixmap, int x, int y, int size) { 169 | 170 | public int get(int x, int y) { 171 | return pixmap.getRaw(this.x + x, this.y + y); 172 | } 173 | } 174 | 175 | public record Line(Color color, int x, int y, int length) { // height is always 1 pixel 176 | 177 | public String colorCode() { 178 | return Strings.format("draw color @ @ @ @\n", (int) (color.r * 255), (int) (color.g * 255), (int) (color.b * 255), (int) (color.a * 255)); 179 | } 180 | 181 | public String rectCode() { 182 | return Strings.format("draw rect @ @ @ 1\n", x, y, length); 183 | } 184 | } 185 | 186 | public static class Config { 187 | 188 | public LogicBlock processor = (LogicBlock) Blocks.microProcessor; 189 | public LogicDisplay display = (LogicDisplay) Blocks.logicDisplay; 190 | 191 | public int rows, columns; 192 | public boolean filter; 193 | 194 | public int offset() { 195 | return display.sizeOffset; 196 | } 197 | 198 | public float range() { 199 | return processor.range / tilesize + display.size / 2 - 1; 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/java/scheme/tools/MessageQueue.java: -------------------------------------------------------------------------------- 1 | package scheme.tools; 2 | 3 | import arc.Events; 4 | import arc.struct.Queue; 5 | import arc.util.Ratekeeper; 6 | import arc.util.Timer; 7 | import mindustry.game.EventType.*; 8 | import mindustry.gen.Call; 9 | 10 | /** Prevents ddos ban. */ 11 | public class MessageQueue { 12 | 13 | public static Queue messages = new Queue<>(); 14 | public static Ratekeeper messageRate = new Ratekeeper(), dropRate = new Ratekeeper(); 15 | 16 | public static void load() { 17 | Events.run(WorldLoadEvent.class, messages::clear); 18 | 19 | Timer.schedule(() -> { 20 | if (messages.isEmpty() || !allow()) return; 21 | Call.sendChatMessage(messages.removeFirst()); 22 | }, 0f, .1f); 23 | } 24 | 25 | public static void send(String message) { 26 | if (messages.isEmpty() || !messages.last().equals(message)) messages.add(message); 27 | } 28 | 29 | public static boolean allow() { 30 | return messageRate.allow(2000L, 15); 31 | } 32 | 33 | public static boolean drop() { 34 | return dropRate.allow(1000L, 30); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/java/scheme/tools/RainbowTeam.java: -------------------------------------------------------------------------------- 1 | package scheme.tools; 2 | 3 | import arc.Events; 4 | import arc.func.Cons; 5 | import arc.graphics.Color; 6 | import arc.math.Mathf; 7 | import arc.struct.IntMap; 8 | import arc.struct.Seq; 9 | import arc.util.Time; 10 | import arc.util.Timer; 11 | import mindustry.game.Team; 12 | import mindustry.game.EventType.*; 13 | import mindustry.gen.Groups; 14 | import mindustry.gen.Player; 15 | 16 | /** Just for fun :D */ 17 | public class RainbowTeam { 18 | 19 | public static IntMap> members = new IntMap<>(); 20 | public static Seq rainbow; 21 | 22 | public static void load() { 23 | Events.run(WorldLoadEvent.class, members::clear); 24 | Timer.schedule(() -> { 25 | if (members.isEmpty()) return; 26 | 27 | for (var entry : members) 28 | if (Groups.player.getByID(entry.key) == null) members.remove(entry.key); 29 | 30 | Team team = rainbow.get(Mathf.floor(Time.time / 6f % rainbow.size)); 31 | for (var entry : members) 32 | if (MessageQueue.allow()) entry.value.get(team); 33 | }, 0f, .3f); 34 | 35 | rainbow = Seq.with(Team.all); 36 | rainbow.removeAll(team -> { 37 | int[] hsv = Color.RGBtoHSV(team.color); 38 | return !(hsv[1] > 64 && hsv[2] > 84); 39 | }).sort(team -> { 40 | int[] hsv = Color.RGBtoHSV(team.color); 41 | return hsv[0] * 1000 + hsv[1]; 42 | }); 43 | } 44 | 45 | public static void add(Player target, Cons cons) { 46 | members.put(target.id, cons); 47 | } 48 | 49 | public static void remove(Player target) { 50 | members.remove(target.id); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/java/scheme/tools/RendererTools.java: -------------------------------------------------------------------------------- 1 | package scheme.tools; 2 | 3 | import arc.graphics.Color; 4 | import arc.graphics.g2d.Draw; 5 | import arc.graphics.g2d.Fill; 6 | import arc.graphics.g2d.Lines; 7 | import arc.math.Mathf; 8 | import arc.math.geom.Rect; 9 | import arc.struct.Seq; 10 | import arc.util.Tmp; 11 | import mindustry.gen.Groups; 12 | import mindustry.gen.Unit; 13 | import mindustry.graphics.Drawf; 14 | import mindustry.graphics.Layer; 15 | import mindustry.graphics.Pal; 16 | 17 | import static arc.Core.*; 18 | import static mindustry.Vars.*; 19 | 20 | public class RendererTools { 21 | 22 | public Rect bounds = new Rect(); 23 | public boolean xray, grid, ruler, unitInfo, borderless, unitRadius, turretRadius, reactorRadius, overdriveRadius; 24 | 25 | public void draw() { 26 | camera.bounds(bounds); // do NOT use Tmp.r1 27 | int xwidth = (int) (bounds.x + bounds.width), yheigth = (int) (bounds.y + bounds.height); 28 | 29 | // if (xray) builds.each(bounds, tile -> tile.floor().drawBase(tile)); 30 | 31 | if (grid) Draw.draw(Layer.blockUnder, () -> { 32 | Lines.stroke(1f, Pal.darkMetal); 33 | 34 | int sx = Mathf.round(bounds.x, tilesize) + 4; 35 | int sy = Mathf.round(bounds.y, tilesize) + 4; 36 | 37 | for (int x = sx; x < xwidth; x += tilesize) 38 | for (int y = sy - 2; y < yheigth; y += tilesize) 39 | Lines.line(x, y, x, y + 4); 40 | 41 | for (int y = sy; y < yheigth; y += tilesize) 42 | for (int x = sx - 2; x < xwidth; x += tilesize) 43 | Lines.line(x, y, x + 4, y); 44 | }); 45 | 46 | if (ruler) Draw.draw(Layer.legUnit, () -> { 47 | Lines.stroke(1f, Pal.accent); 48 | 49 | int x = Mathf.round(input.mouseWorldX() - 4, tilesize) + 4; 50 | int y = Mathf.round(input.mouseWorldY() - 4, tilesize) + 4; 51 | 52 | Lines.line(x, bounds.y, x, yheigth); 53 | Lines.line(x + tilesize, bounds.y, x + tilesize, yheigth); 54 | Lines.line(bounds.x, y, xwidth, y); 55 | Lines.line(bounds.x, y + tilesize, xwidth, y + tilesize); 56 | }); 57 | 58 | Seq units = unitInfo || unitRadius ? Groups.unit.intersect(bounds.x, bounds.y, bounds.width, bounds.height) : null; 59 | 60 | if (unitInfo) Draw.draw(Layer.overlayUI, () -> units.each(unit -> unit != player.unit(), unit -> { 61 | if (unit.isPlayer()) { 62 | Tmp.v1.set(unit.aimX, unit.aimY).sub(unit).setLength(unit.hitSize); 63 | Lines.stroke(2f, unit.team.color); 64 | Lines.line(unit.x + Tmp.v1.x, unit.y + Tmp.v1.y, unit.aimX, unit.aimY); 65 | } 66 | 67 | drawBar(unit, 3f, Pal.darkishGray, 1f); 68 | drawBar(unit, 3f, Pal.health, Mathf.clamp(unit.healthf())); 69 | 70 | if (!state.rules.unitAmmo) return; 71 | 72 | drawBar(unit, -3f, unit.type.ammoType.color(), 1f); 73 | drawBar(unit, -3f, Pal.darkishGray, 1f - Mathf.clamp(unit.ammof())); 74 | })); 75 | 76 | if (unitRadius) Draw.draw(Layer.overlayUI, () -> units.each(unit -> Drawf.circles(unit.x, unit.y, unit.range(), unit.team.color))); 77 | 78 | // asynchrony requires sacrifice 79 | Draw.draw(Layer.blockUnder, Draw::reset); 80 | Draw.draw(Layer.legUnit, Draw::reset); 81 | Draw.draw(Layer.overlayUI, Draw::reset); 82 | } 83 | 84 | public void showUnits(boolean hide) {} 85 | 86 | public void toggleCoreItems() { 87 | settings.put("coreitems", !settings.getBool("coreitems")); 88 | } 89 | 90 | public void drawPlans(Unit unit, boolean valid) { 91 | Draw.draw(Layer.plans, valid ? unit::drawBuildPlans : () -> unit.plans.each(plan -> { 92 | plan.animScale = 1f; 93 | plan.block.drawPlan(plan, unit.plans, valid); 94 | })); 95 | } 96 | 97 | private void drawBar(Unit unit, float size, Color color, float fract) { 98 | Draw.color(color); 99 | 100 | size = Mathf.sqrt(unit.hitSize) * size; 101 | float x = unit.x - size / 2f, y = unit.y - size; 102 | 103 | float height = size * 2f, stroke = size * -.35f, xs = x - size; 104 | float f1 = Math.min(fract * 2f, 1f), f2 = (fract - .5f) * 2f; 105 | 106 | float bo = -(1f - f1) * (-size - stroke); 107 | Fill.quad( 108 | x, y, 109 | x + stroke, y, 110 | xs + bo, y + size * f1, 111 | xs - stroke + bo, y + size * f1); 112 | 113 | if (f2 < 0) return; 114 | 115 | float bx = (1f - f2) * (-size - stroke) + x; 116 | Fill.quad( 117 | xs, y + size, 118 | xs - stroke, y + size, 119 | bx, y + height * fract, 120 | bx + stroke, y + height * fract); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/java/scheme/tools/admins/AdminsTools.java: -------------------------------------------------------------------------------- 1 | package scheme.tools.admins; 2 | 3 | import arc.math.geom.Position; 4 | import arc.struct.Seq; 5 | import mindustry.entities.Units; 6 | import mindustry.entities.units.BuildPlan; 7 | import mindustry.game.Team; 8 | import mindustry.gen.Player; 9 | import mindustry.type.Item; 10 | import mindustry.type.UnitType; 11 | 12 | import static arc.Core.*; 13 | import static mindustry.Vars.*; 14 | 15 | public interface AdminsTools { 16 | 17 | public String disabled = null; 18 | public String unabailable = null; 19 | 20 | public void manageUnit(); 21 | 22 | public void spawnUnits(); 23 | 24 | public void manageEffect(); 25 | 26 | public void manageItem(); 27 | 28 | public void manageTeam(); 29 | 30 | public void placeCore(); 31 | 32 | public void despawn(Player target); 33 | 34 | public default void despawn() { 35 | despawn(player); 36 | } 37 | 38 | public void teleport(Position pos); 39 | 40 | public default void teleport() { 41 | teleport(camera.position); 42 | } 43 | 44 | public default void look() { 45 | for (int i = 0; i < 10; i++) player.unit().lookAt(input.mouseWorld()); 46 | } 47 | 48 | public void fill(int sx, int sy, int ex, int ey); 49 | 50 | public void brush(int x, int y, int radius); 51 | 52 | public void flush(Seq plans); 53 | 54 | public boolean unusable(); 55 | 56 | public default int fixAmount(Item item, Float amount) { 57 | int items = player.core().items.get(item); 58 | return amount == 0f || items + amount < 0 ? -items : amount.intValue(); 59 | } 60 | 61 | public default boolean canCreate(Team team, UnitType type) { 62 | boolean can = Units.canCreate(team, type); 63 | if (!can) ui.showInfoFade("@admins.nounit"); 64 | return can; 65 | } 66 | 67 | public default boolean hasCore(Team team) { 68 | boolean has = team.core() != null; 69 | if (!has) ui.showInfoFade("@admins.nocore"); 70 | return has; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/java/scheme/tools/admins/Darkdustry.java: -------------------------------------------------------------------------------- 1 | package scheme.tools.admins; 2 | 3 | import arc.math.geom.Position; 4 | import arc.struct.Seq; 5 | import mindustry.entities.units.BuildPlan; 6 | import mindustry.gen.Call; 7 | import mindustry.gen.Player; 8 | import mindustry.world.Block; 9 | import mindustry.world.blocks.storage.CoreBlock.CoreBuild; 10 | import scheme.tools.MessageQueue; 11 | import scheme.tools.RainbowTeam; 12 | 13 | import static arc.Core.*; 14 | import static mindustry.Vars.*; 15 | import static scheme.SchemeVars.*; 16 | 17 | public class Darkdustry implements AdminsTools { 18 | 19 | public void manageUnit() { 20 | if (unusable()) return; 21 | unit.select(false, true, false, (target, team, unit, amount) -> { 22 | send("unit", unit.id, "#" + target.id); 23 | }); 24 | } 25 | 26 | public void spawnUnits() { 27 | if (unusable()) return; 28 | unit.select(true, false, true, (target, team, unit, amount) -> { 29 | if (amount == 0f) { 30 | send("despawn"); 31 | return; 32 | } 33 | 34 | send("spawn", unit.id, amount.intValue(), team.id); 35 | }); 36 | } 37 | 38 | public void manageEffect() { 39 | if (unusable()) return; 40 | effect.select(true, true, false, (target, team, effect, amount) -> send("effect", effect.id, amount.intValue() / 60, "#" + target.id)); 41 | } 42 | 43 | public void manageItem() { 44 | if (unusable()) return; 45 | item.select(true, false, true, (target, team, item, amount) -> send("give", item.id, amount.intValue(), team.id)); 46 | } 47 | 48 | public void manageTeam() { 49 | if (unusable()) return; 50 | team.select((target, team) -> { 51 | if (team != null) { 52 | RainbowTeam.remove(target); 53 | send("team", team.id, "#" + target.id); 54 | } else 55 | RainbowTeam.add(target, t -> send("team", t.id, "#" + target.id)); 56 | }); 57 | } 58 | 59 | public void placeCore() { 60 | if (unusable()) return; 61 | if (player.buildOn() instanceof CoreBuild) 62 | sendPacket("fill", "null 0 null", player.tileX(), player.tileY(), 1, 1); 63 | else send("core"); 64 | } 65 | 66 | public void despawn(Player target) { 67 | if (unusable()) return; 68 | send("despawn", "#" + target.id); 69 | } 70 | 71 | public void teleport(Position pos) { 72 | if (unusable()) return; 73 | send("tp", (int) pos.getX() / tilesize, (int) pos.getY() / tilesize); 74 | } 75 | 76 | public void fill(int sx, int sy, int ex, int ey) { 77 | if (unusable()) return; 78 | tile.select((floor, block, overlay) -> sendPacket("fill", id(floor), id(block), id(overlay), sx, sy, ex - sx + 1, ey - sy + 1)); 79 | } 80 | 81 | public void brush(int x, int y, int radius) { 82 | if (unusable()) return; 83 | tile.select((floor, block, overlay) -> sendPacket("brush", id(floor), id(block), id(overlay), x, y, radius)); 84 | } 85 | 86 | public void flush(Seq plans) { 87 | if (unusable()) return; 88 | ui.showInfoFade("@admins.notsupported"); 89 | } 90 | 91 | public boolean unusable() { 92 | boolean admin = !player.admin && !settings.getBool("adminsalways"); 93 | if (!settings.getBool("adminsenabled")) { 94 | ui.showInfoFade(disabled); 95 | return true; 96 | } else if (admin) ui.showInfoFade("@admins.notanadmin"); 97 | return admin; // darkness was be here 98 | } 99 | 100 | private static void send(String command, Object... args) { 101 | StringBuilder message = new StringBuilder(netServer.clientCommands.getPrefix()).append(command); 102 | for (var arg : args) message.append(" ").append(arg); 103 | MessageQueue.send(message.toString()); 104 | } 105 | 106 | private static void sendPacket(String command, Object... args) { 107 | StringBuilder message = new StringBuilder(); 108 | for (var arg : args) message.append(arg).append(" "); 109 | Call.serverPacketReliable(command, message.toString()); 110 | } 111 | 112 | private static String id(Block block) { 113 | return block == null ? "null" : String.valueOf(block.id); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/java/scheme/tools/admins/Internal.java: -------------------------------------------------------------------------------- 1 | package scheme.tools.admins; 2 | 3 | import arc.math.geom.Geometry; 4 | import arc.math.geom.Position; 5 | import arc.struct.Seq; 6 | import mindustry.content.Blocks; 7 | import mindustry.entities.units.BuildPlan; 8 | import mindustry.gen.Groups; 9 | import mindustry.gen.Player; 10 | import mindustry.world.Block; 11 | import mindustry.world.Tile; 12 | import mindustry.world.blocks.environment.Prop; 13 | import mindustry.world.blocks.storage.CoreBlock.CoreBuild; 14 | import scheme.tools.RainbowTeam; 15 | 16 | import static arc.Core.*; 17 | import static mindustry.Vars.*; 18 | import static scheme.SchemeVars.*; 19 | 20 | public class Internal implements AdminsTools { 21 | 22 | public void manageUnit() { 23 | if (unusable()) return; 24 | unit.select(false, true, false, (target, team, unit, amount) -> { 25 | if (!canCreate(team, unit)) return; 26 | target.unit().spawnedByCore(true); 27 | target.unit(unit.spawn(team, target)); 28 | }); 29 | } 30 | 31 | public void spawnUnits() { 32 | if (unusable()) return; 33 | unit.select(true, true, true, (target, team, unit, amount) -> { 34 | if (amount == 0f) { 35 | Groups.unit.each(u -> u.team == team && u.type == unit, u -> u.spawnedByCore(true)); 36 | return; 37 | } 38 | 39 | if (!canCreate(team, unit)) return; 40 | for (int i = 0; i < amount; i++) unit.spawn(team, target); 41 | }); 42 | } 43 | 44 | public void manageEffect() { 45 | if (unusable()) return; 46 | effect.select(true, true, false, (target, team, effect, amount) -> { 47 | if (amount == 0f) target.unit().unapply(effect); 48 | else target.unit().apply(effect, amount); 49 | }); 50 | } 51 | 52 | public void manageItem() { 53 | if (unusable()) return; 54 | item.select(true, false, true, (target, team, item, amount) -> { 55 | if (!hasCore(team)) return; 56 | team.core().items.add(item, fixAmount(item, amount)); 57 | }); 58 | } 59 | 60 | public void manageTeam() { 61 | if (unusable()) return; 62 | team.select((target, team) -> { 63 | if (team != null) { 64 | RainbowTeam.remove(target); 65 | target.team(team); 66 | } else 67 | RainbowTeam.add(target, target::team); 68 | }); 69 | } 70 | 71 | public void placeCore() { 72 | if (unusable()) return; 73 | Tile tile = player.tileOn(); 74 | if (tile != null) tile.setNet(tile.build instanceof CoreBuild ? Blocks.air : Blocks.coreShard, player.team(), 0); 75 | } 76 | 77 | public void despawn(Player target) { 78 | if (unusable()) return; 79 | target.unit().spawnedByCore(true); 80 | target.clearUnit(); 81 | } 82 | 83 | public void teleport(Position pos) { 84 | player.unit().set(pos); // it's always available 85 | } 86 | 87 | public void fill(int sx, int sy, int ex, int ey) { 88 | if (unusable()) return; 89 | tile.select((floor, block, overlay) -> { 90 | for (int x = sx; x <= ex; x++) 91 | for (int y = sy; y <= ey; y++) 92 | edit(floor, block, overlay, x, y); 93 | }); 94 | } 95 | 96 | public void brush(int x, int y, int radius) { 97 | if (unusable()) return; 98 | tile.select((floor, block, overlay) -> Geometry.circle(x, y, radius, (cx, cy) -> edit(floor, block, overlay, cx, cy))); 99 | } 100 | 101 | public void flush(Seq plans) { 102 | plans.each(plan -> { 103 | if (plan.block.isFloor() && !plan.block.isOverlay()) 104 | edit(plan.block, null, null, plan.x, plan.y); 105 | else if (plan.block instanceof Prop) 106 | edit(null, plan.block, null, plan.x, plan.y); 107 | else if (plan.block.isOverlay()) 108 | edit(null, null, plan.block, plan.x, plan.y); 109 | }); 110 | } 111 | 112 | public boolean unusable() { 113 | boolean admin = net.client() && !settings.getBool("adminsalways"); 114 | if (!settings.getBool("adminsenabled")) { 115 | ui.showInfoFade(disabled); 116 | return true; 117 | } else if (admin) ui.showInfoFade(unabailable); 118 | return admin; 119 | } 120 | 121 | private static void edit(Block floor, Block block, Block overlay, int x, int y) { 122 | Tile tile = world.tile(x, y); 123 | if (tile == null) return; 124 | 125 | if ((floor != null && tile.floor() != floor) || (overlay != null && tile.overlay() != overlay)) 126 | tile.setFloorNet(floor == null ? tile.floor() : floor, overlay == null ? tile.overlay() : overlay); 127 | 128 | if (block != null && tile.block() != block) tile.setNet(block); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/java/scheme/tools/admins/SlashJs.java: -------------------------------------------------------------------------------- 1 | package scheme.tools.admins; 2 | 3 | import arc.math.geom.Position; 4 | import arc.struct.Seq; 5 | import arc.util.Strings; 6 | import mindustry.entities.units.BuildPlan; 7 | import mindustry.gen.Player; 8 | import mindustry.type.Item; 9 | import mindustry.type.StatusEffect; 10 | import mindustry.type.UnitType; 11 | import mindustry.world.Block; 12 | import scheme.tools.MessageQueue; 13 | import scheme.tools.RainbowTeam; 14 | 15 | import static arc.Core.*; 16 | import static mindustry.Vars.*; 17 | import static scheme.SchemeVars.*; 18 | 19 | public class SlashJs implements AdminsTools { 20 | 21 | public void manageUnit() { 22 | if (unusable()) return; 23 | unit.select(false, true, false, (target, team, unit, amount) -> { 24 | if (!canCreate(team, unit)) return; 25 | getPlayer(target); 26 | send("player.unit().spawnedByCore = true"); 27 | send("player.unit(@.spawn(player.team(), player))", getUnit(unit)); 28 | }); 29 | } 30 | 31 | public void spawnUnits() { 32 | if (unusable()) return; 33 | unit.select(true, true, true, (target, team, unit, amount) -> { 34 | if (amount == 0f) { 35 | send("Groups.unit.each(u => u.team == Team.@ && u.type == @, u => u.spawnedByCore = true)", team, getUnit(unit)); 36 | return; 37 | } 38 | 39 | if (!canCreate(team, unit)) return; 40 | getPlayer(target); 41 | send("var unit = @", getUnit(unit)); 42 | send("for (var i = 0; i < @; i++) unit.spawn(Team.@, player)", amount, team); 43 | }); 44 | } 45 | 46 | public void manageEffect() { 47 | if (unusable()) return; 48 | effect.select(true, true, false, (target, team, effect, amount) -> { 49 | getPlayer(target); 50 | if (amount == 0f) send("player.unit().unapply(" + getEffect(effect) + ")"); 51 | else send("player.unit().apply(" + getEffect(effect) + ", " + amount + ")"); 52 | }); 53 | } 54 | 55 | public void manageItem() { 56 | if (unusable()) return; 57 | item.select(true, false, true, (target, team, item, amount) -> { 58 | if (!hasCore(team)) return; 59 | send("Team.@.core().items.add(@, @)", team, getItem(item), fixAmount(item, amount)); 60 | }); 61 | } 62 | 63 | public void manageTeam() { 64 | if (unusable()) return; 65 | team.select((target, team) -> { 66 | if (team != null) { 67 | RainbowTeam.remove(target); 68 | send("Groups.player.getByID(@).team(Team.@)", target.id, team); 69 | } else 70 | RainbowTeam.add(target, t -> send("Groups.player.getByID(@).team(Team.get(@))", target.id, t.id)); 71 | }); 72 | } 73 | 74 | public void placeCore() { 75 | if (unusable()) return; 76 | getPlayer(player); 77 | send("var tile = player.tileOn()"); 78 | send("if (tile != null) tile.setNet(tile.build instanceof CoreBlock.CoreBuild ? Blocks.air : Blocks.coreShard, player.team(), 0)"); 79 | } 80 | 81 | public void despawn(Player target) { 82 | if (unusable()) return; 83 | getPlayer(target); 84 | send("player.unit().spawnedByCore = true"); 85 | send("player.clearUnit()"); 86 | } 87 | 88 | public void teleport(Position pos) { 89 | if (unusable()) return; 90 | String conpos = "(player.con, " + pos.toString().replace("(", ""); // Vec2 and Point2 returns (x, y) 91 | getPlayer(player); 92 | send("var spawned = player.unit().spawnedByCore; var unit = player.unit(); unit.spawnedByCore = false; player.clearUnit()"); 93 | send("unit.set@; Call.setPosition@; Call.setCameraPosition@", pos, conpos, conpos); 94 | send("player.unit(unit); unit.spawnedByCore = spawned"); 95 | } 96 | 97 | public void fill(int sx, int sy, int ex, int ey) { 98 | if (unusable()) return; 99 | tile.select((floor, block, overlay) -> { 100 | edit(floor, block, overlay); 101 | send("for (var x = @; x <= @; x++) for (var y = @; y <= @; y++) todo(Vars.world.tile(x, y))", sx, ex, sy, ey); 102 | }); 103 | } 104 | 105 | public void brush(int x, int y, int radius) { 106 | if (unusable()) return; 107 | tile.select((floor, block, overlay) -> { 108 | edit(floor, block, overlay); 109 | send("Geometry.circle(@, @, @, (cx, cy) => todo(Vars.world.tile(cx, cy)))", x, y, radius); 110 | }); 111 | } 112 | 113 | public void flush(Seq plans) { 114 | if (unusable()) return; 115 | ui.showInfoFade("@admins.notsupported"); 116 | } 117 | 118 | public boolean unusable() { 119 | boolean admin = !player.admin && !settings.getBool("adminsalways"); 120 | if (!settings.getBool("adminsenabled")) { 121 | ui.showInfoFade(disabled); 122 | return true; 123 | } else if (admin) ui.showInfoFade("@admins.notanadmin"); 124 | return admin; 125 | } 126 | 127 | private static void send(String command, Object... args) { 128 | MessageQueue.send("/js " + Strings.format(command, args)); 129 | } 130 | 131 | private static void getPlayer(Player target) { 132 | send("var player = Groups.player.getByID(@)", target.id); 133 | } 134 | 135 | private static String getUnit(UnitType unit) { 136 | return "Vars.content.unit(" + unit.id + ")"; 137 | } 138 | 139 | private static String getEffect(StatusEffect effect) { 140 | return "Vars.content.statusEffects().get(" + effect.id + ")"; 141 | } 142 | 143 | private static String getItem(Item item) { 144 | return "Vars.content.item(" + item.id + ")"; 145 | } 146 | 147 | private static String getBlock(Block block) { 148 | return block == null ? "null" : "Vars.content.block(" + block.id + ")"; 149 | } 150 | 151 | private static void edit(Block floor, Block block, Block overlay) { 152 | boolean fo = floor != null || overlay != null; 153 | 154 | send("f = @; b = @; o = @", getBlock(floor), getBlock(block), getBlock(overlay)); 155 | send("todo = tile => { if(tile!=null){" + (fo ? "sflr(tile);" : "") + (block != null ? "if(tile.block()!=b)tile.setNet(b)" : "") + "} }"); 156 | if (fo) send("sflr = tile => { if(tile.floor()!=f||tile.overlay()!=o)tile.setFloorNet(f==null?tile.floor():f,o==null?tile.overlay():o) }"); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/java/scheme/ui/FlipButton.java: -------------------------------------------------------------------------------- 1 | package scheme.ui; 2 | 3 | import arc.scene.ui.ImageButton; 4 | import mindustry.gen.Icon; 5 | 6 | public class FlipButton extends ImageButton { 7 | 8 | public boolean fliped; 9 | 10 | public FlipButton() { 11 | super(Icon.downOpen, HudFragment.style); 12 | clicked(this::flip); 13 | resizeImage(Icon.downOpen.imageSize()); 14 | } 15 | 16 | public void flip() { 17 | setChecked(fliped = !fliped); 18 | getStyle().imageUp = fliped ? Icon.upOpen : Icon.downOpen; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/java/scheme/ui/HexBar.java: -------------------------------------------------------------------------------- 1 | package scheme.ui; 2 | 3 | import arc.func.Cons; 4 | import arc.func.Floatp; 5 | import arc.graphics.Color; 6 | import arc.graphics.g2d.Draw; 7 | import arc.graphics.g2d.Fill; 8 | import arc.math.Mathf; 9 | import arc.scene.ui.layout.Scl; 10 | import arc.scene.ui.layout.Table; 11 | import arc.util.Tmp; 12 | import mindustry.graphics.Drawf; 13 | import mindustry.graphics.Pal; 14 | 15 | /** Contains many magic numbers. */ 16 | public class HexBar extends Table { 17 | 18 | public final Floatp amount; 19 | public float last, blink, value; 20 | 21 | /** I don't even remember what it's for. */ 22 | public final float sw = Scl.scl(25.8f); 23 | 24 | public HexBar(Floatp amount, Cons cons) { 25 | super(cons); 26 | this.amount = amount; 27 | } 28 | 29 | @Override 30 | public void draw() { 31 | float next = amount.get(); 32 | 33 | if (next == 0f) return; 34 | if (next < last) blink = 1f; 35 | 36 | if (Float.isNaN(next) || Float.isInfinite(next)) next = 1f; 37 | if (Float.isNaN(value) || Float.isInfinite(value)) value = 1f; 38 | 39 | blink = Mathf.lerpDelta(blink, 0f, 0.2f); 40 | value = Mathf.lerpDelta(value, next, 0.15f); 41 | last = next; 42 | 43 | drawInner(Pal.darkerGray, 1f); // draw a gray background over the standard one 44 | if (value > 0) drawInner(Tmp.c1.set(Pal.accent).lerp(Color.white, blink), value); 45 | 46 | Drawf.shadow(x + width / 2f, y + height / 2f, height * 1.13f, parentAlpha); 47 | Draw.reset(); 48 | 49 | super.draw(); 50 | } 51 | 52 | public void drawInner(Color color, float fract) { 53 | Draw.color(color, parentAlpha); 54 | 55 | float f1 = Math.min(fract * 2f, 1f), f2 = (fract - 0.5f) * 2f; 56 | float bh = height / 2f, mw = width - sw; 57 | 58 | float dx = sw * f1; 59 | float dy = bh * f1 + y; 60 | Fill.quad( 61 | x + sw, y, 62 | x + mw, y, 63 | x + dx + mw, dy, 64 | x - dx + sw, dy); 65 | 66 | if (f2 < 0) return; 67 | 68 | dx = sw * f2; 69 | dy = height * fract + y; 70 | Fill.quad( 71 | x, y + bh, 72 | x + width, y + bh, 73 | x + width - dx, dy, 74 | x + dx, dy); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/java/scheme/ui/List.java: -------------------------------------------------------------------------------- 1 | package scheme.ui; 2 | 3 | import arc.func.*; 4 | import arc.graphics.Color; 5 | import arc.graphics.g2d.Draw; 6 | import arc.graphics.g2d.Lines; 7 | import arc.graphics.g2d.TextureRegion; 8 | import arc.scene.ui.Button; 9 | import arc.scene.ui.ScrollPane; 10 | import arc.scene.ui.layout.Cell; 11 | import arc.scene.ui.layout.Scl; 12 | import arc.scene.ui.layout.Table; 13 | import arc.util.Scaling; 14 | import mindustry.graphics.Pal; 15 | import mindustry.ui.Styles; 16 | 17 | import static mindustry.Vars.*; 18 | 19 | public class List { 20 | 21 | public T selected; 22 | 23 | public Cons> iterator; 24 | public Func title; 25 | public Func texture; 26 | public Func color; 27 | public Cons onChanged = value -> {}; 28 | 29 | public Cell pane; 30 | public Table list = new Table(); 31 | 32 | public List(Cons> iterator, Func title, Func texture, Func color) { 33 | this.iterator = iterator; 34 | this.title = title; 35 | this.texture = texture; 36 | this.color = color; 37 | } 38 | 39 | public void build(Table parent) { 40 | pane = parent.pane(list).height(mobile ? 540f : 630f).scrollX(false); 41 | } 42 | 43 | public void rebuild() { 44 | rebuild(false); 45 | } 46 | 47 | public void rebuild(boolean minimize) { 48 | list.clear(); 49 | list.defaults().size(minimize ? 144f : 264f, 74f).padBottom(16f); 50 | 51 | iterator.get(item -> { 52 | Button check = new Button(Styles.cleart); 53 | check.changed(() -> set(item)); 54 | 55 | if (!minimize) check.add(new IconTable(check::isChecked, texture.get(item))).size(74f); 56 | check.table(t -> { 57 | t.labelWrap(title.get(item)).growX().row(); 58 | t.image().height(4f).color(color.get(item)).growX().bottom().padTop(4f); 59 | }).size(minimize ? 120f : 170f, 74f).pad(10f); 60 | 61 | list.add(check).checked(button -> selected == item).row(); 62 | }); 63 | } 64 | 65 | public T get() { 66 | return selected; 67 | } 68 | 69 | public void set(T item) { 70 | selected = item; 71 | onChanged.get(item); 72 | } 73 | 74 | public static class IconTable extends Table { 75 | 76 | public Boolp active; 77 | 78 | public IconTable(Boolp active, TextureRegion region) { 79 | this.active = active; 80 | image(region).scaling(Scaling.bounded).pad(8f).grow(); 81 | } 82 | 83 | @Override 84 | public void draw() { 85 | super.draw(); 86 | Draw.color(active.get() ? Pal.accent : Pal.gray, parentAlpha); 87 | Lines.stroke(Scl.scl(4f)); 88 | Lines.rect(x, y, width, height); 89 | Draw.reset(); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/java/scheme/ui/MapResizeFix.java: -------------------------------------------------------------------------------- 1 | package scheme.ui; 2 | 3 | import arc.scene.ui.layout.Cell; 4 | import arc.scene.ui.layout.Table; 5 | import arc.struct.Seq; 6 | import arc.util.Reflect; 7 | import mindustry.editor.MapResizeDialog; 8 | 9 | import static mindustry.Vars.*; 10 | 11 | @SuppressWarnings("rawtypes") 12 | public class MapResizeFix { 13 | 14 | public static void load() { 15 | MapResizeDialog.maxSize = 5000; // hah, that was pretty easy 16 | MapResizeDialog dialog = Reflect.get(ui.editor, "resizeDialog"); 17 | 18 | dialog.shown(() -> { 19 | Seq cells = getCells(dialog); 20 | cells.each(cell -> cell.maxTextLength(4)); 21 | }); 22 | } 23 | 24 | private static Seq getCells(Table table) { 25 | return ((Table) ((Table) table.getChildren().get(1)).getChildren().get(0)).getCells(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/java/scheme/ui/PowerBars.java: -------------------------------------------------------------------------------- 1 | package scheme.ui; 2 | 3 | import mindustry.core.UI; 4 | import mindustry.gen.Building; 5 | import mindustry.graphics.Pal; 6 | import mindustry.ui.Bar; 7 | import mindustry.world.blocks.power.PowerGraph; 8 | import mindustry.world.blocks.power.PowerNode.PowerNodeBuild; 9 | 10 | import static arc.Core.*; 11 | import static mindustry.Vars.*; 12 | 13 | import arc.math.geom.Point2; 14 | 15 | public class PowerBars { 16 | 17 | public Building node; 18 | public Point2[] connections; 19 | public PowerGraph graph = new PowerGraph(); 20 | 21 | public boolean setNode(Building node) { 22 | if (node != null && node.power != null) { 23 | this.node = node; // caching the power node connection for case of its absence 24 | this.connections = node instanceof PowerNodeBuild build ? build.config() : new Point2[0]; 25 | this.graph = node.power.graph; 26 | return true; 27 | } else return false; 28 | } 29 | 30 | public void refreshNode() { 31 | if (node == null) return; // nothing to refresh 32 | if (setNode(world.build(node.pos()))) return; 33 | for (Point2 point : connections) // looking for a new power node among those to which the old one was connected 34 | if (setNode(world.build(point.add(node.tileX(), node.tileY()).pack()))) return; 35 | 36 | node = null; // correct power node not found 37 | graph = new PowerGraph(); 38 | } 39 | 40 | public Bar balance() { 41 | return new Bar( 42 | () -> bundle.format("bar.powerbalance", (graph.getPowerBalance() >= 0 ? "+" : "") + UI.formatAmount((long) graph.getPowerBalance() * 60L)), 43 | () -> Pal.powerBar, 44 | () -> graph.getSatisfaction()); 45 | } 46 | 47 | public Bar stored() { 48 | return new Bar( 49 | () -> bundle.format("bar.powerstored", UI.formatAmount((long) graph.getLastPowerStored()), UI.formatAmount((long) graph.getLastCapacity())), 50 | () -> Pal.powerBar, 51 | () -> graph.getLastPowerStored() / graph.getLastCapacity()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/java/scheme/ui/ShortcutFragment.java: -------------------------------------------------------------------------------- 1 | package scheme.ui; 2 | 3 | import arc.scene.Group; 4 | import arc.scene.ui.layout.Table; 5 | import arc.util.Align; 6 | import arc.util.Scaling; 7 | import mindustry.ui.Styles; 8 | import mindustry.ui.dialogs.SchematicsDialog.SchematicImage; 9 | 10 | import static mindustry.Vars.*; 11 | 12 | public class ShortcutFragment { 13 | 14 | public boolean visible; 15 | 16 | public int lastIndex; 17 | public Runnable rebuild; 18 | public Runnable onShown; 19 | 20 | public void build(Group parent) { 21 | parent.fill(cont -> { 22 | cont.name = "tagselection"; 23 | cont.add(/* selection */ "").visible(() -> visible).size(24f); // buttons size 24 | 25 | /* 26 | for (int i = 0; i < 6; i++) { 27 | String key = "shortcut-tag-" + i; 28 | selection.add(settings.getString(key, TagSelectDialog.none), button -> { 29 | tag.show(button.getText().toString(), tag -> { 30 | button.setText(tag); 31 | settings.put(key, tag); 32 | 33 | selection.updateAlignment(); 34 | }); 35 | hide(); 36 | }); 37 | } 38 | 39 | selection.updateAlignment(); 40 | */ 41 | }); 42 | 43 | parent.fill(cont -> { 44 | cont.name = "schematicselection"; 45 | cont.visible(() -> visible); 46 | 47 | cont.pane(list -> rebuild = () -> { 48 | String tag = /* selection.buttons[lastIndex].getText().toString() */ ""; 49 | schematics.all().each(schematic -> schematic.labels.contains(tag), schematic -> { 50 | list.button(button -> button.stack( 51 | new SchematicImage(schematic).setScaling(Scaling.fit), 52 | new Table(table -> table.top().table(Styles.black3, title -> { 53 | title.add(schematic.name(), Styles.outlineLabel, .60f).width(72f).labelAlign(Align.center).get().setEllipsis(true); 54 | }).pad(4f).width(72f)) 55 | ), () -> { 56 | control.input.useSchematic(schematic); 57 | hide(); 58 | }).margin(0f).pad(2f).size(80f); 59 | 60 | if (list.getChildren().size % 4 == 0) list.row(); 61 | }); 62 | }).size(362f, 336f).update(pane -> { 63 | /* 64 | Tmp.r1.setSize(pane.getWidth(), pane.getHeight()).setPosition(pane.translation.x + pane.x, pane.translation.y + pane.y).grow(8f); 65 | selection.updatable = lastIndex == -1 || !Tmp.r1.contains(input.mouse()); 66 | 67 | if (selection.selectedIndex == lastIndex) return; 68 | lastIndex = selection.selectedIndex; 69 | pane.getWidget().clear(); 70 | 71 | if (lastIndex == -1) return; 72 | rebuild.run(); // rebuild schematics list 73 | 74 | Vec2 offset = selection.vertices[lastIndex].cpy().setLength(HexSelection.size * 1.5f); 75 | offset.sub(offset.x > 0 ? 0 : pane.getWidth(), offset.y > 0 ? 0 : offset.y == 0 ? pane.getHeight() / 2 : pane.getHeight()); 76 | pane.setTranslation(selection.x + offset.x - pane.x, selection.y + offset.y - pane.y); 77 | */ 78 | }).with(pane -> onShown = pane.getWidget()::clear); 79 | }); 80 | } 81 | 82 | public void show(int x, int y) { 83 | // selection.setPosition(x, y); 84 | onShown.run(); // clear pane 85 | visible = true; 86 | } 87 | 88 | public void hide() { 89 | lastIndex = -1; 90 | visible = false; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/java/scheme/ui/TextSlider.java: -------------------------------------------------------------------------------- 1 | package scheme.ui; 2 | 3 | import arc.func.Cons; 4 | import arc.graphics.Color; 5 | import arc.scene.event.Touchable; 6 | import arc.scene.ui.Label; 7 | import arc.scene.ui.Slider; 8 | import arc.scene.ui.layout.Cell; 9 | import arc.scene.ui.layout.Stack; 10 | import arc.scene.ui.layout.Table; 11 | import arc.scene.utils.Disableable; 12 | import mindustry.ui.Styles; 13 | import mindustry.ui.dialogs.SettingsMenuDialog.StringProcessor; 14 | 15 | public class TextSlider extends Table implements Disableable{ 16 | 17 | public Label label; 18 | public Slider slider; 19 | 20 | public TextSlider(int min, int max, int step, int def, StringProcessor processor) { 21 | touchable = Touchable.disabled; 22 | 23 | label = labelWrap("").style(Styles.outlineLabel).padLeft(12f).growX().left().get(); 24 | slider = new Slider(min, max, step, false); 25 | 26 | slider.moved(value -> label.setText(processor.get((int) value))); 27 | slider.setValue(def); 28 | slider.change(); 29 | } 30 | 31 | public Cell build(Table table) { 32 | return table.stack(slider, this); 33 | } 34 | 35 | public TextSlider update(Cons cons) { 36 | update(() -> cons.get(this)); 37 | return this; 38 | } 39 | 40 | @Override 41 | public boolean isDisabled() { 42 | return slider.isDisabled(); 43 | } 44 | 45 | @Override 46 | public void setDisabled(boolean isDisabled) { 47 | slider.setDisabled(isDisabled); 48 | Color color = isDisabled ? Color.gray : Color.white; 49 | slider.setColor(color); 50 | label.setColor(color); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/java/scheme/ui/dialogs/AISelectDialog.java: -------------------------------------------------------------------------------- 1 | package scheme.ui.dialogs; 2 | 3 | import arc.Events; 4 | import arc.graphics.Color; 5 | import arc.graphics.g2d.TextureRegion; 6 | import arc.struct.Seq; 7 | import mindustry.ai.types.*; 8 | import mindustry.content.UnitTypes; 9 | import mindustry.entities.units.AIController; 10 | import mindustry.game.EventType.*; 11 | import mindustry.gen.Icon; 12 | import mindustry.gen.Player; 13 | import mindustry.graphics.Pal; 14 | import mindustry.type.UnitType; 15 | import scheme.ai.GammaAI; 16 | import scheme.ai.NetMinerAI; 17 | import scheme.ai.GammaAI.Updater; 18 | import scheme.ui.List; 19 | 20 | import static arc.Core.*; 21 | import static mindustry.Vars.*; 22 | 23 | public class AISelectDialog extends ListDialog { 24 | 25 | public AIController ai; 26 | public List list; 27 | 28 | public AISelectDialog() { 29 | super("@select.ai"); 30 | 31 | hidden(() -> { 32 | if (ai instanceof GammaAI gamma) gamma.cache(); 33 | if (ai instanceof MissileAI missile) missile.shooter = player.unit(); 34 | }); 35 | 36 | Events.run(WorldLoadEvent.class, this::deselect); 37 | Events.on(BlockBuildBeginEvent.class, event -> { 38 | if (ai instanceof GammaAI gamma && GammaAI.build == Updater.destroy && event.unit.getPlayer() == gamma.target) 39 | gamma.block(event.tile, event.breaking); // crutch but no way 40 | }); 41 | 42 | Seq units = Seq.with( 43 | new UnitAI(null, null), 44 | new UnitAI(UnitTypes.mono, new NetMinerAI()), 45 | new UnitAI(UnitTypes.poly, new BuilderAI()), 46 | new UnitAI(UnitTypes.mega, new RepairAI()), 47 | new UnitAI(UnitTypes.oct, new DefenderAI()), 48 | new UnitAI(UnitTypes.crawler, new SuicideAI()), 49 | new UnitAI(UnitTypes.dagger, new GroundAI()), 50 | new UnitAI(UnitTypes.flare, new FlyingAI()), 51 | new UnitAI(UnitTypes.renale, new HugAI()), 52 | new UnitAI(content.unit(64), new MissileAI()), 53 | new UnitAI(UnitTypes.gamma, new GammaAI())); 54 | 55 | list = new List<>(units::each, UnitAI::name, UnitAI::icon, unit -> Pal.accent); 56 | players.selected = player; // do it once 57 | 58 | cont.table(cont -> { 59 | players.build(cont); 60 | players.onChanged = player -> list.set(units.peek()); 61 | 62 | players.pane.padRight(16f); 63 | 64 | list.build(cont); 65 | list.onChanged = unit -> ai = unit.ai; 66 | }).row(); 67 | 68 | cont.labelWrap("@select.ai.tooltip").labelAlign(2, 8).padTop(16f).width(400f).get().getStyle().fontColor = Color.lightGray; 69 | } 70 | 71 | public void update() { 72 | ai.unit(player.unit()); 73 | ai.updateUnit(); 74 | player.shooting = player.unit().isShooting; 75 | } 76 | 77 | public void select() { 78 | players.rebuild(); 79 | list.rebuild(); 80 | 81 | show(scene); // call Dialog.show bypassing ListDialog.show 82 | } 83 | 84 | public void deselect() { 85 | ai = null; 86 | } 87 | 88 | public void gotoppl(Player player) { 89 | players.set(player); 90 | ((GammaAI) ai).cache(); 91 | } 92 | 93 | public record UnitAI(UnitType type, AIController ai) { 94 | 95 | public String name() { 96 | return type != null ? type.localizedName : "@keycomb.reset_ai"; 97 | } 98 | 99 | public TextureRegion icon() { 100 | return type != null ? type.uiIcon : Icon.none.getRegion(); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/java/scheme/ui/dialogs/AdminsConfigDialog.java: -------------------------------------------------------------------------------- 1 | package scheme.ui.dialogs; 2 | 3 | import arc.math.Mathf; 4 | import arc.scene.ui.layout.Table; 5 | import mindustry.gen.Call; 6 | import mindustry.gen.ClientSnapshotCallPacket; 7 | import mindustry.ui.dialogs.BaseDialog; 8 | import scheme.tools.admins.*; 9 | import scheme.ui.TextSlider; 10 | 11 | import static arc.Core.*; 12 | import static mindustry.Vars.*; 13 | import static scheme.SchemeVars.*; 14 | 15 | public class AdminsConfigDialog extends BaseDialog { 16 | 17 | public boolean enabled = settings.getBool("adminsenabled", false); 18 | public boolean always = settings.getBool("adminsalways", false); 19 | public boolean strict = settings.getBool("adminsstrict", false); 20 | public int way = settings.getInt("adminsway", 0); 21 | 22 | public AdminsConfigDialog() { 23 | super("@admins.name"); 24 | addCloseButton(); 25 | 26 | hidden(() -> { 27 | settings.put("adminsenabled", enabled); 28 | settings.put("adminsalways", always); 29 | settings.put("adminsstrict", strict); 30 | settings.put("adminsway", way); 31 | admins = getTools(); 32 | }); 33 | 34 | new TextSlider(0, 1, 1, enabled ? 1 : 0, value -> { 35 | return bundle.format("admins.lever", bundle.get((enabled = value == 1) ? "admins.enabled" : "admins.disabled")); 36 | }).build(cont).width(320f).row(); 37 | 38 | cont.labelWrap("@admins.way").padTop(16f).width(320f).row(); 39 | cont.table(table -> { 40 | addCheck(table, "@admins.way.internal", 0); 41 | addCheck(table, "@admins.way.slashjs", 1); 42 | addCheck(table, "@admins.way.darkdustry", 2); 43 | }).left().row(); 44 | 45 | cont.labelWrap("@admins.always").padTop(16f).width(320f).row(); 46 | new TextSlider(0, 1, 1, always ? 1 : 0, value -> { 47 | return (always = value == 1) ? "@yes" : "@no"; 48 | }).update(slider -> slider.setDisabled(!enabled)).build(cont).width(320f).row(); 49 | 50 | cont.labelWrap("@admins.strict").padTop(16f).width(320f).row(); 51 | new TextSlider(0, 1, 1, strict ? 1 : 0, value -> { 52 | return (strict = value == 1) ? "@yes" : "@no"; 53 | }).update(slider -> slider.setDisabled(net.client())).build(cont).width(320f).row(); 54 | 55 | net.handleServer(ClientSnapshotCallPacket.class, (con, snapshot) -> { 56 | if (strict && con.player != null && !con.player.dead() && !con.kicked) { 57 | var unit = con.player.unit(); 58 | 59 | if (!snapshot.dead && unit.id == snapshot.unitID && !Mathf.within(snapshot.x, snapshot.y, unit.x, unit.y, 112f)) { 60 | Call.setPosition(con, unit.x, unit.y); // teleport and correct position when necessary 61 | return; 62 | } 63 | } 64 | 65 | snapshot.handleServer(con); // built-in 66 | }); 67 | } 68 | 69 | private void addCheck(Table table, String text, int way) { 70 | table.check(text + ".name", value -> this.way = way).checked(t -> this.way == way).disabled(t -> !enabled).tooltip(text + ".desc").left().row(); 71 | } 72 | 73 | /** Made static so that it can be accessed before the dialog is created. */ 74 | public static AdminsTools getTools() { 75 | return new AdminsTools[] { 76 | new Internal(), new SlashJs(), new Darkdustry() 77 | }[settings.getInt("adminsway", 0)]; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/java/scheme/ui/dialogs/ContentSelectDialog.java: -------------------------------------------------------------------------------- 1 | package scheme.ui.dialogs; 2 | 3 | import arc.func.Cons4; 4 | import arc.func.Func; 5 | import arc.scene.style.TextureRegionDrawable; 6 | import arc.scene.ui.Label; 7 | import arc.scene.ui.Slider; 8 | import arc.scene.ui.layout.Scl; 9 | import arc.scene.ui.layout.Table; 10 | import arc.struct.Seq; 11 | import mindustry.content.StatusEffects; 12 | import mindustry.content.UnitTypes; 13 | import mindustry.ctype.UnlockableContent; 14 | import mindustry.game.Team; 15 | import mindustry.gen.Icon; 16 | import mindustry.gen.Player; 17 | import mindustry.ui.Styles; 18 | 19 | import static arc.Core.*; 20 | import static mindustry.Vars.*; 21 | 22 | public class ContentSelectDialog extends ListDialog { 23 | 24 | public static final int row = mobile ? 5 : 10; 25 | public static final float size = mobile ? 52f : 64f; 26 | public static final Seq specials = Seq.with( 27 | UnitTypes.latum, UnitTypes.renale, content.unit(53), content.unit(55), content.unit(64), 28 | StatusEffects.muddy, StatusEffects.shielded, StatusEffects.corroded, StatusEffects.disarmed, StatusEffects.invincible); 29 | 30 | public Cons4 callback; 31 | public Func format; 32 | 33 | public boolean showSlider; 34 | public int items; 35 | 36 | public ContentSelectDialog(String title, Seq content, int min, int max, int step, Func format) { 37 | super(title); 38 | this.format = format; 39 | 40 | Label label = new Label("", Styles.outlineLabel); 41 | Slider slider = new Slider(min, max, step, false); 42 | 43 | slider.moved(value -> label.setText(format.get(value))); 44 | slider.change(); // update label 45 | 46 | Table table = new Table(); 47 | table.pane(pane -> { 48 | pane.margin(0f, 24f, 0f, 24f); 49 | 50 | content.each(this::visible, item -> { 51 | pane.button(new TextureRegionDrawable(item.uiIcon), () -> { 52 | callback.get(players.get(), teams.get(), item, slider.getValue()); 53 | hide(); 54 | }).size(size).tooltip(item.localizedName); 55 | 56 | if (++items % row == 0) pane.row(); 57 | }); 58 | }); 59 | 60 | addPlayer(); 61 | 62 | cont.table(cont -> { 63 | cont.add(table).row(); 64 | cont.add(label).center().padTop(16f).visible(() -> showSlider).row(); 65 | cont.table(slide -> { 66 | slide.button(Icon.add, () -> { 67 | content.each(this::visible, item -> callback.get(players.get(), teams.get(), item, slider.getValue())); 68 | hide(); 69 | }).tooltip("@select.all"); 70 | slide.add(slider).padLeft(8f).growX(); 71 | }).fillX().visible(() -> showSlider); 72 | }).growX(); 73 | 74 | addTeam(); 75 | } 76 | 77 | public void select(boolean showSlider, boolean showPlayers, boolean showTeams, Cons4 callback) { 78 | // in portrait orientation, ui elements may not fit into the screen 79 | boolean minimize = graphics.getWidth() < Scl.scl(mobile ? 900f : 1250f); 80 | 81 | players.pane.visible(showPlayers); 82 | players.rebuild(minimize); 83 | 84 | teams.pane.visible(showTeams); 85 | teams.rebuild(minimize); 86 | 87 | this.showSlider = showSlider; 88 | this.callback = callback; 89 | show(); 90 | } 91 | 92 | public boolean visible(T item) { 93 | return item.logicVisible() || specials.contains(item); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/java/scheme/ui/dialogs/ImageParserDialog.java: -------------------------------------------------------------------------------- 1 | package scheme.ui.dialogs; 2 | 3 | import arc.files.Fi; 4 | import arc.scene.style.TextureRegionDrawable; 5 | import arc.scene.ui.ImageButton.ImageButtonStyle; 6 | import arc.scene.ui.layout.Table; 7 | import mindustry.game.Schematic; 8 | import mindustry.gen.Tex; 9 | import mindustry.ui.Styles; 10 | import mindustry.ui.dialogs.BaseDialog; 11 | import mindustry.ui.dialogs.SchematicsDialog.SchematicImage; 12 | import mindustry.world.blocks.logic.LogicDisplay; 13 | import scheme.SchemeVars; 14 | import scheme.tools.ImageParser; 15 | import scheme.tools.ImageParser.Config; 16 | import scheme.ui.TextSlider; 17 | 18 | import static arc.Core.*; 19 | import static mindustry.Vars.*; 20 | 21 | public class ImageParserDialog extends BaseDialog { 22 | 23 | public Fi file; 24 | public Schematic last; 25 | 26 | public Table image; 27 | public Config config = new Config(); 28 | 29 | public ImageParserDialog() { 30 | super("@parser.name"); 31 | addCloseButton(); 32 | 33 | cont.table(t -> image = t).row(); 34 | 35 | new TextSlider(1, 10, 1, 1, value -> { 36 | app.post(this::rebuild); 37 | return bundle.format("parser.row", config.rows = value); 38 | }).build(cont).width(450f).row(); 39 | 40 | new TextSlider(1, 10, 1, 1, value -> { 41 | app.post(this::rebuild); 42 | return bundle.format("parser.column", config.columns = value); 43 | }).build(cont).width(450f).row(); 44 | 45 | cont.check("@parser.filter", true, value -> { 46 | app.post(this::rebuild); 47 | config.filter = value; 48 | }).left().tooltip("@parser.filter.tooltip").row(); 49 | 50 | buttons.button("@parser.import", () -> { 51 | SchemeVars.schemas.imported(last); 52 | hide(); 53 | }).disabled(button -> last == null); 54 | 55 | cont.table(table -> { 56 | var style = new ImageButtonStyle(Styles.clearNoneTogglei) {{ 57 | up = Tex.pane; 58 | }}; 59 | 60 | content.blocks().each(block -> { 61 | if (block instanceof LogicDisplay display) 62 | table.button(new TextureRegionDrawable(display.uiIcon), style, 30f, () -> { 63 | app.post(this::rebuild); 64 | config.display = display; 65 | }).size(48f).padRight(4f).checked(button -> config.display == display); 66 | }); 67 | }).left().row(); 68 | } 69 | 70 | public void rebuild() { 71 | if (file == null) return; // don't do this 72 | 73 | last = ImageParser.parseSchematic(file, config); 74 | if (last == null) return; // an error occurred while parsing the schema 75 | 76 | image.clear(); 77 | image.add(new SchematicImage(last)).size(450f); 78 | } 79 | 80 | public void show(Fi file) { 81 | this.file = file; 82 | 83 | rebuild(); 84 | show(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/java/scheme/ui/dialogs/JoinViaClajDialog.java: -------------------------------------------------------------------------------- 1 | package scheme.ui.dialogs; 2 | 3 | import arc.scene.ui.layout.*; 4 | import mindustry.gen.Icon; 5 | import mindustry.ui.dialogs.BaseDialog; 6 | import scheme.ClajIntegration; 7 | 8 | import static mindustry.Vars.*; 9 | 10 | public class JoinViaClajDialog extends BaseDialog { 11 | 12 | public String lastLink = "CLaJLink#ip:port"; 13 | 14 | public boolean valid; 15 | public String output; 16 | 17 | public JoinViaClajDialog() { 18 | super("@join.name"); 19 | 20 | cont.table(table -> { 21 | table.add("@join.link").padRight(5f).left(); 22 | table.field(lastLink, this::setLink).size(550f, 54f).maxTextLength(100).valid(this::setLink); 23 | }).row(); 24 | 25 | cont.label(() -> output).width(550f).left(); 26 | 27 | buttons.defaults().size(140f, 60f).pad(4f); 28 | buttons.button("@cancel", this::hide); 29 | buttons.button("@ok", () -> { 30 | try { 31 | if (player.name.trim().isEmpty()) { 32 | ui.showInfo("@noname"); 33 | return; 34 | } 35 | 36 | var link = ClajIntegration.parseLink(lastLink); 37 | ClajIntegration.joinRoom(link.ip(), link.port(), link.key(), () -> { 38 | ui.join.hide(); 39 | hide(); 40 | }); 41 | 42 | ui.loadfrag.show("@connecting"); 43 | ui.loadfrag.setButton(() -> { 44 | ui.loadfrag.hide(); 45 | netClient.disconnectQuietly(); 46 | }); 47 | } catch (Throwable ignored) { 48 | ui.showErrorMessage(ignored.getMessage()); 49 | } 50 | }).disabled(button -> lastLink.isEmpty() || net.active()); 51 | 52 | fixJoinDialog(); 53 | } 54 | 55 | public boolean setLink(String link) { 56 | if (lastLink.equals(link)) return valid; 57 | 58 | try { 59 | ClajIntegration.parseLink(link); 60 | 61 | output = "@join.valid"; 62 | valid = true; 63 | } catch (Throwable ignored) { 64 | output = ignored.getMessage(); 65 | valid = false; 66 | } 67 | 68 | lastLink = link; 69 | return valid; 70 | } 71 | 72 | private void fixJoinDialog() { 73 | var stack = (Stack) ui.join.getChildren().get(1); 74 | var root = (Table) stack.getChildren().get(1); 75 | 76 | // poor mobile players =< 77 | boolean infoButton = !steam && !mobile; 78 | 79 | root.button("@join.name", Icon.play, this::show); 80 | 81 | if (infoButton) 82 | root.getCells().insert(4, root.getCells().remove(6)); 83 | else 84 | root.getCells().insert(3, root.getCells().remove(4)); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/java/scheme/ui/dialogs/ListDialog.java: -------------------------------------------------------------------------------- 1 | package scheme.ui.dialogs; 2 | 3 | import arc.graphics.g2d.TextureRegion; 4 | import arc.scene.ui.Dialog; 5 | import arc.util.Structs; 6 | import mindustry.game.Team; 7 | import mindustry.gen.Groups; 8 | import mindustry.gen.Player; 9 | import mindustry.ui.dialogs.BaseDialog; 10 | import scheme.ui.List; 11 | 12 | import static arc.Core.*; 13 | import static mindustry.Vars.*; 14 | 15 | public class ListDialog extends BaseDialog { 16 | 17 | public List players; 18 | public List teams; 19 | 20 | public ListDialog(String title) { 21 | super(title); 22 | addCloseButton(); 23 | 24 | players = new List<>(Groups.player::each, Player::coloredName, Player::icon, player -> player.team().color); 25 | teams = new List<>(cons -> Structs.each(cons, Team.baseTeams), Team::localized, ListDialog::texture, team -> team.color); 26 | } 27 | 28 | @Override 29 | public Dialog show() { 30 | players.set(player); 31 | return super.show(); 32 | } 33 | 34 | public void addPlayer() { 35 | players.build(cont); 36 | players.pane.left(); 37 | } 38 | 39 | public void addTeam() { 40 | teams.build(cont); 41 | teams.pane.right(); 42 | 43 | // not via ListFragment.set because some dialogs are closed after selecting a team 44 | players.onChanged = player -> teams.selected = player.team(); 45 | } 46 | 47 | public void addEmpty() { 48 | cont.table().width(288f); 49 | } 50 | 51 | public static TextureRegion texture(Team team) { 52 | return atlas.find(new String[] { 53 | "team-derelict", 54 | "team-sharded", 55 | "team-crux", 56 | "team-malis", 57 | "status-electrified-ui", 58 | "status-wet-ui" 59 | }[team.id]); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/java/scheme/ui/dialogs/ManageRoomsDialog.java: -------------------------------------------------------------------------------- 1 | package scheme.ui.dialogs; 2 | 3 | import arc.graphics.Color; 4 | import arc.net.Client; 5 | import arc.scene.ui.TextField; 6 | import arc.scene.ui.layout.Table; 7 | import arc.util.Strings; 8 | import mindustry.gen.Icon; 9 | import mindustry.gen.Tex; 10 | import mindustry.ui.Styles; 11 | import mindustry.ui.dialogs.BaseDialog; 12 | import scheme.ClajIntegration; 13 | import scheme.Main; 14 | import scheme.ui.FlipButton; 15 | 16 | import static mindustry.Vars.*; 17 | import static scheme.SchemeVars.*; 18 | 19 | import java.io.IOException; 20 | 21 | public class ManageRoomsDialog extends BaseDialog { 22 | 23 | public String serverIP; 24 | public int serverPort; 25 | 26 | public Table list; 27 | public TextField field; 28 | public FlipButton flip; 29 | public boolean valid; 30 | 31 | public ManageRoomsDialog() { 32 | super("@manage.name"); 33 | addCloseButton(); 34 | 35 | cont.defaults().width(mobile ? 550f : 750f); 36 | 37 | cont.table(list -> { 38 | list.defaults().growX().padBottom(8f); 39 | list.update(() -> list.getCells().removeAll(cell -> cell.get() == null)); // remove closed rooms 40 | 41 | this.list = list; 42 | }).row(); 43 | 44 | cont.table(url -> { 45 | url.field(clajURLs.first(), this::setURL).maxTextLength(100).valid(this::validURL).with(f -> field = f).growX(); 46 | url.add(flip = new FlipButton()).size(48f).padLeft(8f); 47 | }).row(); 48 | 49 | cont.collapser(list -> { 50 | clajURLs.each(url -> { 51 | list.button(url, Styles.cleart, () -> setURL(url)).height(32f).growX().row(); 52 | }); 53 | }, true, () -> flip.fliped).row(); 54 | 55 | cont.button("@manage.create", () -> { 56 | try { 57 | list.add(new Room()).row(); 58 | } catch (Exception ignored) { 59 | ui.showErrorMessage(ignored.getMessage()); 60 | } 61 | }).disabled(b -> list.getChildren().size >= 4 || !valid).row(); 62 | 63 | cont.labelWrap("@manage.tooltip").labelAlign(2, 8).padTop(16f).width(400f).get().getStyle().fontColor = Color.lightGray; 64 | 65 | setURL(clajURLs.first()); 66 | ui.paused.shown(this::fixPausedDialog); 67 | } 68 | 69 | private void fixPausedDialog() { 70 | var root = ui.paused.cont; 71 | 72 | if (mobile) { 73 | root.row().buttonRow("@manage.name", Icon.planet, this::show).colspan(3).disabled(button -> !net.server()); 74 | return; 75 | } 76 | 77 | root.row(); 78 | root.button("@manage.name", Icon.planet, this::show).colspan(2).width(450f).disabled(button -> !net.server()).row(); 79 | 80 | var buttons = root.getCells(); 81 | buttons.swap(buttons.size - 1, buttons.size - 2); // move the claj button above the quit button 82 | } 83 | 84 | // region URL 85 | 86 | public void setURL(String url) { 87 | field.setText(url); 88 | 89 | int semicolon = url.indexOf(':'); 90 | serverIP = url.substring(0, semicolon); 91 | serverPort = Strings.parseInt(url.substring(semicolon + 1)); 92 | } 93 | 94 | public boolean validURL(String url) { 95 | return valid = url.contains(":") && Strings.canParseInt(url.substring(url.indexOf(':') + 1)); 96 | } 97 | 98 | // endregion 99 | 100 | public class Room extends Table { 101 | 102 | public Client client; 103 | public String link; 104 | 105 | public Room() throws IOException { 106 | client = ClajIntegration.createRoom(serverIP, serverPort, link -> this.link = link, this::close); 107 | 108 | table(Tex.underline, cont -> { 109 | cont.label(() -> link).growX().left().fontScale(.7f).ellipsis(true); 110 | }).growX(); 111 | 112 | table(btns -> { 113 | btns.defaults().size(48f).padLeft(8f); 114 | 115 | btns.button(Icon.copy, Styles.clearNonei, () -> Main.copy(link)); 116 | btns.button(Icon.cancel, Styles.clearNonei, this::close); 117 | }); 118 | } 119 | 120 | public void close() { 121 | client.close(); 122 | remove(); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/java/scheme/ui/dialogs/RendererConfigDialog.java: -------------------------------------------------------------------------------- 1 | package scheme.ui.dialogs; 2 | 3 | import arc.func.Boolc; 4 | import arc.func.Boolp; 5 | import arc.func.Cons; 6 | import arc.graphics.Color; 7 | import arc.scene.ui.ScrollPane; 8 | import arc.scene.ui.TextButton; 9 | import arc.scene.ui.layout.Table; 10 | import mindustry.ui.dialogs.BaseDialog; 11 | 12 | import static arc.Core.*; 13 | import static mindustry.Vars.*; 14 | import static scheme.SchemeVars.*; 15 | 16 | public class RendererConfigDialog extends BaseDialog { 17 | 18 | public RendererConfigDialog() { 19 | super("@render.name"); 20 | addCloseButton(); 21 | 22 | partition("general.name", part -> { 23 | check(part, "power", this::togglePowerLines, () -> settings.getInt("lasersopacity") != 0); 24 | check(part, "status", value -> settings.put("blockstatus", value), () -> settings.getBool("blockstatus")); 25 | check(part, "light", value -> enableLight = value, () -> enableLight); 26 | check(part, "dark", value -> enableDarkness = value, () -> enableDarkness); 27 | check(part, "fog", value -> state.rules.fog = value, () -> state.rules.fog); 28 | }); 29 | 30 | cont.button("@keycomb.view_sets", () -> show(true)).width(320f).row(); 31 | 32 | partition("add.name", part -> { 33 | check(part, "xray", value -> render.xray = value); 34 | check(part, "hide", render::showUnits); 35 | check(part, "grid", value -> render.grid = value); 36 | check(part, "ruler", value -> render.ruler = value); 37 | check(part, "info", value -> render.unitInfo = value); 38 | check(part, "border", value -> render.borderless = value); 39 | }); 40 | 41 | partition("radius.name", part -> { 42 | check(part, "unit", value -> render.unitRadius = value); 43 | check(part, "turret", value -> render.turretRadius = value); 44 | check(part, "reactor",value -> render.reactorRadius = value); 45 | check(part, "drive", value -> render.overdriveRadius = value); 46 | }); 47 | 48 | cont.labelWrap("@render.desc").labelAlign(2, 8).padTop(16f).width(320f).get().getStyle().fontColor = Color.lightGray; 49 | } 50 | 51 | private void partition(String title, Cons
cons) { 52 | cont.add("@category." + title).padTop(16f).row(); 53 | cont.table(cons).left().row(); 54 | } 55 | 56 | private void check(Table table, String name, Boolc listener) { 57 | check(table, name, listener, null); 58 | } 59 | 60 | private void check(Table table, String name, Boolc listener, Boolp checked) { 61 | table.check("@render." + name, listener).left().with(check -> { 62 | if (checked != null) check.update(() -> check.setChecked(checked.get())); 63 | }).row(); 64 | } 65 | 66 | public void show(boolean graphics) { 67 | if (graphics) { 68 | ui.settings.show(); 69 | graphics().fireClick(); 70 | } else show(); 71 | } 72 | 73 | private void togglePowerLines(boolean on) { 74 | if (on) 75 | settings.put("lasersopacity", settings.getInt("preferredlaseropacity", 100)); 76 | else { 77 | settings.put("preferredlaseropacity", settings.getInt("lasersopacity")); 78 | settings.put("lasersopacity", 0); 79 | } 80 | } 81 | 82 | private TextButton graphics() { // oh no 83 | return (TextButton) ((Table) ((Table) ((ScrollPane) ui.settings.getChildren().get(1)).getChildren().get(0)).getChildren().get(0)).getChildren().get(1); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/java/scheme/ui/dialogs/SchemasDialog.java: -------------------------------------------------------------------------------- 1 | package scheme.ui.dialogs; 2 | 3 | import arc.scene.style.Drawable; 4 | import arc.scene.ui.TextButton; 5 | import arc.scene.ui.layout.Cell; 6 | import arc.scene.ui.layout.Table; 7 | import arc.util.Reflect; 8 | import mindustry.game.Schematic; 9 | import mindustry.gen.Icon; 10 | import mindustry.gen.Tex; 11 | import mindustry.ui.Styles; 12 | import mindustry.ui.dialogs.BaseDialog; 13 | import mindustry.ui.dialogs.SchematicsDialog; 14 | import scheme.moded.ModedSchematics; 15 | 16 | import static arc.Core.*; 17 | import static mindustry.Vars.*; 18 | import static scheme.SchemeVars.*; 19 | 20 | public class SchemasDialog extends SchematicsDialog { 21 | 22 | @Override 23 | public void showImport() { 24 | new ImportDialog().show(); 25 | } 26 | 27 | public void imported(Schematic schematic) { 28 | Reflect.invoke(SchematicsDialog.class, this, "setup", null); 29 | Reflect.invoke(SchematicsDialog.class, this, "checkTags", new Object[] { schematic }, Schematic.class); 30 | showInfo(schematic); 31 | 32 | schematic.removeSteamID(); 33 | schematics.add(schematic); 34 | } 35 | 36 | public class ImportDialog extends BaseDialog { 37 | 38 | public ImportDialog() { 39 | super("@editor.export"); 40 | addCloseButton(); 41 | 42 | cont.pane(pane -> { 43 | pane.margin(10f); 44 | pane.table(Tex.button, t -> { 45 | t.defaults().size(280f, 60f).left(); 46 | 47 | button(t, "copy.import", Icon.copy, () -> { 48 | try { 49 | imported(ModedSchematics.readBase64(app.getClipboardText())); 50 | ui.showInfoFade("@schematic.saved"); 51 | } catch (Throwable error) { 52 | ui.showException(error); 53 | } 54 | }).disabled(b -> app.getClipboardText() == null || !app.getClipboardText().startsWith(schematicBaseStart)).row(); 55 | 56 | button(t, "importfile", Icon.download, () -> platform.showFileChooser(true, schematicExtension, file -> { 57 | try { 58 | imported(ModedSchematics.read(file)); 59 | } catch (Throwable error) { 60 | ui.showException(error); 61 | } 62 | })).row(); 63 | 64 | button(t, "importimage", Icon.image, () -> { // do not replace with :: because null pointer 65 | platform.showFileChooser(true, "png", file -> parser.show(file)); 66 | }).row(); 67 | 68 | if (!steam) return; 69 | 70 | button("browseworkshop", Icon.book, () -> platform.openWorkshop()); 71 | }); 72 | }); 73 | } 74 | 75 | private Cell button(Table table, String text, Drawable image, Runnable listener) { 76 | return table.button("@schematic." + text, image, Styles.flatt, () -> { 77 | hide(); 78 | listener.run(); 79 | }).marginLeft(12f); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/java/scheme/ui/dialogs/SettingsMenuDialog.java: -------------------------------------------------------------------------------- 1 | package scheme.ui.dialogs; 2 | 3 | import mindustry.gen.Icon; 4 | 5 | import static arc.Core.*; 6 | import static mindustry.Vars.*; 7 | import static scheme.SchemeVars.*; 8 | 9 | public class SettingsMenuDialog { 10 | 11 | public SettingsMenuDialog() { 12 | ui.settings.hidden(this::apply); 13 | ui.settings.addCategory("@category.mod.name", Icon.book, table -> { 14 | if (!mobile) table.sliderPref("panspeedmul", 4, 4, 20, this::processor); 15 | 16 | table.sliderPref("maxzoommul", 4, 4, 20, this::processor); 17 | table.sliderPref("minzoommul", 4, 4, 20, this::processor); 18 | 19 | if (!mobile) table.checkPref("mobilebuttons", true); 20 | 21 | table.checkPref("hardscheme", false); 22 | table.checkPref("approachenabled", true); 23 | table.checkPref("welcome", true); 24 | table.checkPref("check4update", true); 25 | 26 | table.areaTextPref("subtitle", "I am using Scheme Size btw"); 27 | }); 28 | } 29 | 30 | public void apply() { 31 | m_input.changePanSpeed(settings.getInt("panspeedmul")); 32 | 33 | // 6f and 1.5f are default values in Renderer 34 | renderer.maxZoom = settings.getInt("maxzoommul") / 4f * 6f; 35 | renderer.minZoom = 1f / (settings.getInt("minzoommul") / 4f) * 1.5f; 36 | } 37 | 38 | private String processor(int value) { 39 | return value / 4f + "x"; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/java/scheme/ui/dialogs/TagSelectDialog.java: -------------------------------------------------------------------------------- 1 | package scheme.ui.dialogs; 2 | 3 | import arc.func.Cons; 4 | import arc.graphics.Color; 5 | import arc.struct.Seq; 6 | import arc.util.Align; 7 | import mindustry.ui.Styles; 8 | import mindustry.ui.dialogs.BaseDialog; 9 | 10 | import static arc.Core.*; 11 | 12 | @SuppressWarnings("unchecked") 13 | public class TagSelectDialog extends BaseDialog { 14 | 15 | public static final float tagh = 42f; 16 | public static final String none = "\uE868"; 17 | 18 | public Cons callback; 19 | public String current; 20 | 21 | public Runnable rebuild; 22 | 23 | public TagSelectDialog() { 24 | super("@select.tag"); 25 | addCloseButton(); 26 | 27 | cont.labelWrap(() -> bundle.format("select.tagdesc", current)).labelAlign(Align.center).width(700f).row(); 28 | cont.image().color(Color.darkGray).fillX().height(4f).pad(4f).row(); 29 | 30 | cont.pane(Styles.noBarPane, list -> rebuild = () -> { 31 | list.left().clear(); 32 | list.defaults().pad(2f).height(tagh); 33 | 34 | Seq tags = settings.getJson("schematic-tags", Seq.class, String.class, Seq::new); 35 | tags.insert(0, none); // add none to the beginning 36 | 37 | tags.each(tag -> { // creating a variable is needed to bring the tag to a string 38 | list.button(tag, Styles.togglet, () -> { 39 | callback.get(tag); 40 | hide(); 41 | }).checked(current.equals(tag)).with(button -> button.getLabel().setWrap(false)); 42 | }); 43 | }).fillX().height(tagh).scrollY(false); 44 | } 45 | 46 | public void show(String current, Cons callback) { 47 | this.callback = callback; 48 | this.current = current; 49 | 50 | rebuild.run(); 51 | show(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/java/scheme/ui/dialogs/TeamSelectDialog.java: -------------------------------------------------------------------------------- 1 | package scheme.ui.dialogs; 2 | 3 | import arc.func.Cons2; 4 | import arc.graphics.Color; 5 | import arc.scene.ui.Button; 6 | import arc.util.Time; 7 | import mindustry.game.Team; 8 | import mindustry.gen.Player; 9 | import mindustry.ui.Styles; 10 | import scheme.ui.List.IconTable; 11 | 12 | import static arc.Core.*; 13 | 14 | public class TeamSelectDialog extends ListDialog { 15 | 16 | public Cons2 callback; 17 | 18 | public TeamSelectDialog() { 19 | super("@select.team"); 20 | 21 | addPlayer(); 22 | addTeam(); 23 | 24 | teams.onChanged = team -> { 25 | callback.get(players.get(), team); 26 | hide(); 27 | }; 28 | } 29 | 30 | public void select(Cons2 callback) { 31 | players.rebuild(); 32 | teams.rebuild(); 33 | 34 | Button check = new Button(Styles.cleart); 35 | check.changed(() -> teams.set(null)); 36 | 37 | check.add(new IconTable(check::isChecked, atlas.find("status-overclock-ui"))).size(74f); 38 | check.table(t -> { 39 | t.labelWrap("@team.rainbow.name").growX().row(); 40 | t.image().height(4f).growX().bottom().padTop(4f).update(image -> image.setColor(Color.HSVtoRGB(Time.time % 360f, 100f, 100f))); 41 | }).size(170f, 74f).pad(10f); 42 | 43 | teams.list.add(check).row(); 44 | 45 | this.callback = callback; 46 | show(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/java/scheme/ui/dialogs/TileSelectDialog.java: -------------------------------------------------------------------------------- 1 | package scheme.ui.dialogs; 2 | 3 | import arc.func.Boolf; 4 | import arc.func.Cons; 5 | import arc.func.Cons3; 6 | import arc.func.Prov; 7 | import arc.graphics.g2d.TextureRegion; 8 | import arc.scene.style.TextureRegionDrawable; 9 | import arc.scene.ui.layout.Table; 10 | import arc.struct.Seq; 11 | import mindustry.content.Blocks; 12 | import mindustry.gen.Icon; 13 | import mindustry.graphics.Pal; 14 | import mindustry.ui.dialogs.BaseDialog; 15 | import mindustry.world.Block; 16 | import mindustry.world.Tile; 17 | import mindustry.world.blocks.environment.Floor; 18 | import mindustry.world.blocks.environment.OverlayFloor; 19 | import mindustry.world.blocks.environment.Prop; 20 | import scheme.ui.List; 21 | 22 | import static arc.Core.*; 23 | import static mindustry.Vars.*; 24 | import static scheme.SchemeVars.*; 25 | 26 | 27 | public class TileSelectDialog extends BaseDialog { 28 | 29 | public static final int row = mobile ? 8 : 10; 30 | public static final float size = mobile ? 54f : 64f; 31 | 32 | public Table blocks = new Table(); 33 | 34 | public Block floor; 35 | public Block block; 36 | public Block overlay; 37 | public List list; 38 | 39 | public TileSelectDialog() { 40 | super("@select.tile"); 41 | addCloseButton(); 42 | 43 | Seq folders = Seq.with( 44 | new Folder("select.floor", () -> floor, b -> b instanceof Floor && !(b instanceof OverlayFloor), b -> floor = b), 45 | new Folder("select.block", () -> block, b -> b instanceof Prop, b -> block = b), 46 | new Folder("select.overlay", () -> overlay, b -> b instanceof OverlayFloor, b -> overlay = b)); 47 | 48 | list = new List<>(folders::each, Folder::name, Folder::icon, folder -> Pal.accent); 49 | list.onChanged = this::rebuild; 50 | list.set(folders.first()); 51 | list.rebuild(); 52 | 53 | list.build(cont); 54 | cont.add(blocks).growX(); 55 | cont.table().width(288f); 56 | } 57 | 58 | public void rebuild(Folder folder) { 59 | blocks.clear(); 60 | blocks.table(table -> { 61 | table.defaults().size(size); 62 | 63 | table.button(Icon.none, () -> folder.callback(null)); 64 | table.button(Icon.line, () -> folder.callback(Blocks.air)); 65 | 66 | content.blocks().each(folder::pred, block -> { 67 | TextureRegionDrawable drawable = new TextureRegionDrawable(block.uiIcon); 68 | table.button(drawable, () -> folder.callback(block)); 69 | 70 | if (table.getChildren().count(i -> true) % row == 0) table.row(); 71 | }); 72 | }); 73 | } 74 | 75 | public void select(Cons3 callback) { 76 | callback.get(floor != null ? floor.asFloor() : null, block, overlay != null ? overlay.asFloor() : null); 77 | } 78 | 79 | public void select(int x, int y) { 80 | Tile tile = world.tile(x, y); 81 | if (tile == null) return; 82 | 83 | floor = tile.floor(); 84 | block = tile.build == null ? tile.block() : Blocks.air; 85 | overlay = tile.overlay(); 86 | list.rebuild(); 87 | } 88 | 89 | public record Folder(String name, Prov block, Boolf pred, Cons callback) { 90 | 91 | public String name() { 92 | Block selected = block.get(); 93 | return bundle.format(name, selected == null ? bundle.get("none") : selected.localizedName); 94 | } 95 | 96 | public TextureRegion icon() { 97 | Block selected = block.get(); 98 | return selected == null ? Icon.none.getRegion() : selected == Blocks.air ? Icon.line.getRegion() : selected.uiIcon; 99 | } 100 | 101 | public boolean pred(Block block) { 102 | return pred.get(block) && block.id > 1; 103 | } 104 | 105 | public void callback(Block block) { 106 | callback.get(block); 107 | tile.list.rebuild(); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/java/scheme/ui/dialogs/WaveApproachingDialog.java: -------------------------------------------------------------------------------- 1 | package scheme.ui.dialogs; 2 | 3 | import arc.scene.ui.Dialog; 4 | import arc.scene.ui.Image; 5 | import arc.scene.ui.Label; 6 | import arc.scene.ui.layout.Table; 7 | import arc.struct.ObjectIntMap; 8 | import arc.util.Scaling; 9 | import mindustry.gen.Icon; 10 | import mindustry.type.UnitType; 11 | import mindustry.ui.Styles; 12 | import mindustry.ui.dialogs.BaseDialog; 13 | import scheme.Main; 14 | 15 | import static arc.Core.*; 16 | import static mindustry.Vars.*; 17 | 18 | public class WaveApproachingDialog extends BaseDialog { 19 | 20 | public Label health; 21 | public Label shield; 22 | 23 | public Table enemies; 24 | public Table bosses; 25 | 26 | public WaveApproachingDialog() { 27 | super("@approaching.name"); 28 | addCloseButton(); 29 | 30 | setFillParent(false); // no sense in full screen dialog 31 | cont.add().width(350f).row(); // set min width 32 | 33 | cont.add("").with(l -> health = l).left().row(); 34 | cont.add("").with(l -> shield = l).left().row(); 35 | 36 | cont.add("@approaching.enemies").left(); 37 | cont.button(Icon.copySmall, Styles.clearNonei, () -> copyUnits(null /* units.waveUnits */)).row(); 38 | cont.table(t -> enemies = t).padLeft(16f).left().row(); 39 | 40 | cont.add("@approaching.bosses").left(); 41 | cont.button(Icon.copySmall, Styles.clearNonei, () -> copyUnits(null /* units.waveBosses */)).row(); 42 | cont.table(t -> bosses = t).padLeft(16f).left().row(); 43 | } 44 | 45 | @Override 46 | public Dialog show() { 47 | // units.refreshWaveInfo(); 48 | title.setText(bundle.format("approaching.name", String.valueOf(state.wave))); 49 | 50 | health.setText(bundle.format("approaching.health", 0f /* units.waveHealth */)); 51 | shield.setText(bundle.format("approaching.shield", 0f /* units.waveShield */)); 52 | 53 | addUnits(enemies, null /* units.waveUnits */); 54 | addUnits(bosses, null /* units.waveBosses */); 55 | 56 | return super.show(); 57 | } 58 | 59 | private void addUnits(Table table, ObjectIntMap units) { 60 | table.clear(); 61 | 62 | if (units.isEmpty()) table.add("@none"); 63 | else for (var entry : units) { 64 | table.stack( 65 | new Image(entry.key.uiIcon).setScaling(Scaling.fit), 66 | new Table(pad -> pad.bottom().left().add(String.valueOf(entry.value))) 67 | ).size(32f).padRight(8f); 68 | } 69 | } 70 | 71 | private void copyUnits(ObjectIntMap units) { 72 | StringBuilder builder = new StringBuilder(); 73 | 74 | if (units.isEmpty()) builder.append(bundle.get("none")); 75 | else for (var entry : units) 76 | builder.append(entry.value).append(entry.key.emoji()).append(" "); 77 | 78 | Main.copy(builder.toString()); 79 | hide(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/resources/bundles/bundle_ja.properties: -------------------------------------------------------------------------------- 1 | welcome.name = ようこそ! 2 | welcome.text = どうも、 このMODの唯一の開発者の[#0096FF]xzxADIxzx#7729[]です。\nこのMODについて知らない人がたくさんいます。なので、最初から使えるようになっているモバイルボタンと、この挨拶のメッセージを作りました。もし、このメッセージが 鬱陶しく感じたなら、「設定」から「MOD設定」へ行けばオフにできます。\n\nこのMODが好きなら、GitHubのほうで星をつけてください!\nでは、「Java神の御加護があらんことを」! 3 | 4 | rename.name = ファイル名が変更されました! 5 | rename.text = 縦または横の長さが128マス以上で、ゲームに内蔵された設計図ローダーでは読み込めない設計図が検知されました。今後、例外が発生しないように、 [accent]該当する設計図の拡張子を.mtlsに変更[]し、このMODの設計図ローダーで読み込みます。 6 | 7 | incompatible.name = 互換性がないMODが見つかりました! 8 | incompatible.text = Scheme Sizeはマイナーなツールとは[red]全くもって互換性がありません[]。一応、何とかすれば、使えるようには管理されます。 しかし、使うときは自己責任で...ね? 9 | 10 | locked.info = [#{0}]< [scarlet]\uE88D[] ユニットの動きを制限中 > 11 | locked.bind = [#{0}]<解除するには {1} を押してください。 > 12 | 13 | # just for fun 14 | team.rainbow.name = レ イ ン ボ ー 15 | 16 | join.name = CLaJリンクを通じて参加する 17 | join.link = リンク: 18 | join.missing-prefix = [red]無効なリンクです: CLaJのプレフィックスが見つかりませんでした。 19 | join.wrong-key-length = [red]I無効なリンクです: キーの長さが間違っています。 20 | join.semicolon-not-found = [red]無効なリンクです: セミコロンが見つかりませんでした。 21 | join.failed-to-parse = [red]無効なリンクです: ポート分析に失敗しました。 22 | join.valid = [lime]有効なリンクです。接続ができますよ。 23 | 24 | manage.name = CLaJ由来のルームを管理する 25 | manage.create = ルームを作成およびそのルームのリンクの生成 26 | manage.tooltip = ルームへは遠隔サーバーを通じた友人が、リンクを通じてのみ参加できます。 27 | 28 | approaching.name = ウェーブ {0} 29 | approaching.health = [lightgray]合計体力: []{0}\uE813 30 | approaching.shield = [lightgray]合計シールド: []{0}\uE84D 31 | approaching.enemies = [lightgray]敵の総数: 32 | approaching.bosses = [lightgray]ガーディアン: 33 | approaching.info = [ [scarlet]次のウェーブが接近中[] ]\n\uF129をクリックしてウェーブの詳細を確認する 34 | 35 | schematic.importimage = 画像を読み込む 36 | parser.name = 画像解析プログラム 37 | parser.row = ディスプレイの数(縦): {0} 38 | parser.column = ディスプレイの数(横): {0} 39 | parser.filter = 画像フィルターの適用 40 | parser.filter.tooltip = 大型ディスプレイを使った、画素数が高いものを生成します。その代わり、使用するプロセッサーの数は増えます。 41 | parser.import = 読み込む 42 | 43 | gamma.tooltip = 解除するには {0} をクリック 44 | gamma.range = 半径: {0} 45 | gamma.speed = 速度: {0}% 46 | gamma.none = なにもしない 47 | gamma.circle = 円を描く 48 | gamma.cursor = カーソルを追尾する 49 | gamma.follow = 追尾する 50 | gamma.help = 建築を手伝う 51 | gamma.destroy = 建築を邪魔する 52 | gamma.repair = 建物を建て直す 53 | 54 | setting.panspeedmul.name = カメラ自体の移動速度 55 | setting.maxzoommul.name = 最小縮小倍率 56 | setting.minzoommul.name = 最大拡大倍率 57 | setting.mobilebuttons.name = モバイルボタン 58 | setting.mobilebuttons.description = 設定を反映させるためにゲームを再起動します。 59 | setting.hardscheme.name = 設計図の常時許可 60 | setting.hardscheme.description = 入っているサーバーが設計図を禁止しているかどうかに関わらす、設計図を使えるようにします。\nその代わりサーバーからキックまたはバンされる恐れがあります。 61 | setting.approachenabled.name = 接近中のウェーブを知らせる 62 | setting.approachenabled.description = このお知らせからウェーブの詳細も知ることができます。 63 | setting.welcome.name = 挨拶のメッセージの表示 64 | setting.welcome.description = ...こんなの誰がいるんだ? 65 | setting.check4update.name = アップデートの自動確認 66 | setting.check4update.description = アップデートが来たかどうかをMODに確認させるようにできます。 67 | setting.subtitle.name = 自己紹介 68 | setting.subtitle.description = これはこのMODと結びつきを持ったサーバーで、他プレイヤーが見ることができるものです。\nここに書くことについてとやかく言うことはできません。なので、せめて常識のある文章を書いてください。 69 | category.mod.name = MOD設定 70 | category.bt.name = 建築ツール 71 | category.add.name = いろいろ表示 72 | category.radius.name = 範囲シリーズ 73 | 74 | layer = 設計図のレイヤー: {0} 75 | layer.building = \uF865建物 76 | layer.floor = \uF8EE地面 77 | layer.block = \uF7D3ブロック 78 | layer.overlay = \uF8C0トッピング 79 | layer.terrain = \uF8EA環境 80 | 81 | keybind.adminscfg.name = 管理者ツールの設定 82 | keybind.rendercfg.name = 便利機能の設定 83 | keybind.schematic_shortcut.name = 設計図のショートカットの表示 84 | keybind.toggle_core_items.name = コア資源数の表示 85 | keybind.toggle_ai.name = AI機能に関するあれこれの表示 86 | keybind.manage_unit.name = ユニットに変身 87 | keybind.manage_effect.name = ステータス効果の付与・削除 88 | keybind.manage_item.name = コア資源の追加・削除 89 | keybind.manage_team.name = チームの変更 90 | keybind.place_core.name = コアの設置 91 | keybind.alternative.name = 代替可能キー ([accent]Hold + Block Info[]) 92 | 93 | keycomb.name = 割り当てキーの組み合わせ一覧 94 | keycomb.main = 代替可能キー 95 | keycomb.view_comb = この一覧を見る 96 | keycomb.view_sets = グラフィック設定を見る 97 | keycomb.reset_ai = AIをリセット 98 | keycomb.spawn_unit = ユニットを召喚 99 | keycomb.despawn = 自らをデスポーンする 100 | keycomb.teleport = テレポート 101 | keycomb.lock_move = ユニットの動きを制限 102 | keycomb.schematic = 設計図のレイヤーを変える 103 | keycomb.toggle_bt = 建築ツールの表示を切り替える 104 | keycomb.return = さっき解体した建物の建て直し 105 | keycomb.drop = 建物にあるアイテムを捨てる 106 | 107 | tooltip.drop = カーソルに重なっている建物からアイテムを捨てる 108 | tooltip.replace =隣接した建物の一括置換 109 | tooltip.remove = 隣接した建物の一括削除 110 | tooltip.connect = 最寄りの電力使用ブロックからノードをつなげる 111 | tooltip.fill = ブロックの範囲設置 112 | tooltip.square = 四角く建築 113 | tooltip.circle = 丸く建築 114 | tooltip.pick = カーソルに重なっている地形をコピー 115 | tooltip.edit = 一括タイル設置 116 | tooltip.brush = タイルブラシ 117 | 118 | select.ai = AIを選択 119 | select.ai.tooltip = ガンマAIを選択してプレイヤーに対して行動をとることができます。 120 | select.team = チームを選択 121 | select.tile = タイルを選択 122 | select.tag = タグを設定 123 | select.unit = ユニットを選択 124 | select.unit.clear = ユニットをデスポーンさせる 125 | select.effect = ステータス効果を選択 126 | select.effect.clear = ステータス効果を消去する 127 | select.item = 資源を選択 128 | select.item.clear = 資源を消去する 129 | 130 | select.all = 全ユニットに適応 131 | select.units = {0} 機 132 | select.seconds = {0} 秒 133 | select.items = {0} つ 134 | select.floor = 地面: {0} 135 | select.block = ブロック: {0} 136 | select.overlay = トッピング: {0} 137 | select.tagdesc = 設計図のショートカットはタグを用いて使用する設計図を表示します。\n簡単に言えば設計図一覧のタグ選択みたいな感じです。\n今割り当てられているタグは {0}[white] ですが、別のタグも選択できます。 138 | 139 | trace.type.show = Scheme Sizeを使っている仲間を強調 140 | trace.type.nodata = データがサーバーにありません。\nサーバー主にこのMODとサーバーを結びつけるよう頼んでください。 141 | trace.type.mod = 多分Scheme Size使ってるな。 142 | trace.type.vanilla = 多分バニラのプレイヤーだな。 143 | trace.type.self = 多分あんただな。 144 | trace.type.host = 多分ホストだな。 145 | 146 | render.name = 便利機能の設定 147 | render.desc = この項目をオンにするとラグくなる恐れがあります!\nその時はごめんなさい<(_ _)>。 148 | render.power = 電線 149 | render.status = 建物の状態 150 | render.light = 霧 151 | render.dark = 暗いところ 152 | render.fog = 戦場の霧 153 | render.xray = Xレイ 154 | render.hide = ユニットを非表示 155 | render.grid = マップグリッド 156 | render.ruler = カーソルものさし 157 | render.info = ユニット情報 158 | render.border = ボーダーレスなディスプレイ 159 | render.unit = ユニットの射程距離 160 | render.turret = タレットの射程距離 161 | render.reactor = リアクターの爆破範囲 162 | render.drive = 加速プロジェクターの適応範囲 163 | 164 | admins.name = 管理者の設定 165 | admins.lever = 管理者の機能を {0} 166 | admins.enabled = オン 167 | admins.disabled = オフ 168 | admins.way = どのパスでこのMODの管理者限定機能を使いますか? 169 | admins.way.internal.name = Internal 170 | admins.way.internal.desc = ローカル環境で遊んでたり、君がサーバーのホストだったりするならこれを使おう。 171 | admins.way.slashjs.name = Slash Js 172 | admins.way.slashjs.desc = JSEvalプラグインを使ったサーバーで遊んでいるならこれを使おう。\nスパム判定されかねないから、乱用は控えよう。 173 | admins.way.darkdustry.name = Darkdustry 174 | admins.way.darkdustry.desc = [accent]darkdustry.tk[]サーバーで遊んでいるならこれを使おう。 175 | admins.always = 管理者またはサーバーのチェックをスキップします。あまり推奨しませんが、アナーキーサーバーだったら使えるかもしれません。 176 | admins.strict = プレイヤーがテレポートできないようにします。 普通のサーバーならデフォでオンになって今いますが、ローカルサーバーでは基本的にオンになっていません。 177 | 178 | admins.notenabled = [scarlet]\u26A0[]この機能は機能しません!\n使いたいなら、管理者の設定 ({0}) でこの機能をオンにしてください。 179 | admins.notanadmin = [scarlet]\u26A0[]管理者ではないので、この機能は使えません! 180 | admins.notavailable = [scarlet]\u26A0[]設定に対応したサーバーではないので、この機能は使えません!\n管理者なのにこの機能が使えないならなら、管理者の設定 ({0}) でこの機能を使えるようにして下さい。 181 | admins.notsupported = [scarlet]\u26A0[]この機能はDarkdustryのプラグインではサポートされていません! 182 | admins.nocore = [scarlet]\u26A0[]選択したチームにはコアがありません! 183 | admins.nounit = [scarlet]\u26A0[]選択したチームではもうそのユニットは作れません! 184 | 185 | console.classic = クラシック 186 | console.multiline = マルチライン 187 | console.multiline.send = 送信済 188 | console.schedule = スケジュール 189 | console.schedule.new = 新しいタスク 190 | console.schedule.interval = インターバル/秒 191 | console.schedule.tooltip = 最小値は0.01秒 192 | console.schedule.output = 出力:{0}{1} 193 | console.schedule.cancel = タスクをキャンセル 194 | 195 | updater.name = [scarlet]\u26A0[]最新のMODではありません! 196 | updater.info = [accent]Scheme Size[]の新しいバージョンが出ました。\nこんなウザいメッセージを見たくないなら、設定でオフにできます。\n\n[gray]現在のバージョンは v{0} ですが、最新のバージョンは v{1} です。[] 197 | updater.load = 今すぐインストール 198 | -------------------------------------------------------------------------------- /src/resources/bundles/bundle_zh_CN.properties: -------------------------------------------------------------------------------- 1 | welcome.name = 欢迎! 2 | welcome.text = 您好!我是[#0096FF]xzxADIxzx#7729[],这个模组的开发者\n很多人不了解这个模组的大部分功能,所以我默认启用了移动按钮,您可以在设置-改动中关闭这条信息\n\n如果您喜欢这个模组,请在github页面上标星\n祝你好运,愿java之神与你同在! 3 | 4 | rename.name = 文件已重命名! 5 | rename.text = 模组发现你的蓝图宽或高大于128格,不能通过游戏内置加载器加载。为防止未来出现异常[accent]已将它们的后缀更改为mtls[],将使用模组内置加载器加载 6 | 7 | incompatible.name = 模组不兼容! 8 | incompatible.text = 参数值与Miner Tools完全[red]不兼容[],在一根拐杖的帮助下,它们勉强跑了起来,但使用他们要自担风险 9 | 10 | locked.info = [#{0}]< [scarlet]\uE88D[] 单位移动锁定 > 11 | locked.bind = [#{0}]< 按 {1} 以解锁> 12 | 13 | # just for fun 仅仅是有趣 14 | team.rainbow.name = 彩虹 15 | 16 | join.name = 通过CLaJ链接进入 17 | join.link = 链接: 18 | join.missing-prefix = [red无效链接: 缺少CLaJ前缀 19 | join.wrong-key-length = [red]无效链接: 密钥长度错误 20 | join.semicolon-not-found = [red]无效链接: 没有找到分号 21 | join.failed-to-parse = [red]无效链接: 解析端口失败 22 | join.valid = [lime]有效链接, 可以尝试连接 23 | 24 | manage.name = 管理CLaJ房间 25 | manage.create = 创建房间并生成链接 26 | manage.tooltip = CLaJ房间允许您的朋友通过远程服务器连接到您 27 | 28 | approaching.name = 波次 {0} 29 | approaching.health = [lightgray]总血量: []{0}\uE813 30 | approaching.shield = [lightgray]总护盾: []{0}\uE84D 31 | approaching.enemies = [lightgray]全部敌军: 32 | approaching.bosses = [lightgray]BOSS: 33 | approaching.info = [ [scarlet]即将下一波[] ]\n点击 \uF129 查看波次信息 34 | 35 | schematic.importimage = 导入图片 36 | parser.name = 图像分析器 37 | parser.row = 显示每一行: {0} 38 | parser.column = 显示每列: {0} 39 | parser.filter = 启用图像过滤器 40 | parser.filter.tooltip = 使图像在大型显示器上更流畅,但可能会增加处理器的数量 41 | parser.import = 导入 42 | 43 | gamma.tooltip = 禁用单击 {0} 44 | gamma.range = 范围: {0} 45 | gamma.speed = 速度: {0}% 46 | gamma.none = 无操作 47 | gamma.circle = 圆 48 | gamma.cursor = 跟随鼠标 49 | gamma.follow = 跟随 50 | gamma.help = 辅助建造 51 | gamma.destroy = 建造方块 52 | gamma.repair = 修复建筑 53 | 54 | setting.panspeedmul.name = 视角移动速度 55 | setting.maxzoommul.name = 放大极限 56 | setting.minzoommul.name = 缩小极限 57 | setting.mobilebuttons.name = 移动按钮 58 | setting.mobilebuttons.description = 重启以应用更改 59 | setting.hardscheme.name = 总是允许使用蓝图 60 | setting.hardscheme.description = 允许在当前禁用蓝图游戏中使用蓝图\n您可能在某些服务器被踢出/封禁 61 | setting.approachenabled.name = 通知即将下一波 62 | setting.approachenabled.description = 您可以在此通知中得知下一波的某些信息 63 | setting.welcome.name = 显示欢迎消息 64 | setting.welcome.description = 谁会需要这些烦人的消息呢? 65 | setting.check4update.name = 检查更新 66 | setting.check4update.description = 选择模组是否应该检查更新 67 | setting.subtitle.name = 关于我 68 | setting.subtitle.description = 这个信息将对装本模组的其他玩家和集成服务器可见\n我无法控制您在这里写的东西,所以我只求您是个有教养的人 69 | 70 | category.mod.name = 改动 71 | category.bt.name = 建造工具 72 | category.add.name = 添加 73 | category.radius.name = 半径 74 | 75 | layer = 蓝图图层: {0} 76 | layer.building = \uF865建筑 77 | layer.floor = \uF8EE地板 78 | layer.block = \uF7D3方块 79 | layer.overlay = \uF8C0覆盖 80 | layer.terrain = \uF8EA地形 81 | 82 | keybind.adminscfg.name = 配置管理工具 83 | keybind.rendercfg.name = 配置渲染工具 84 | keybind.schematic_shortcut.name = 显示蓝图快捷键 85 | keybind.toggle_core_items.name = 调整核心资源 86 | keybind.toggle_ai.name = AI切换窗口 87 | keybind.manage_unit.name = 调整附身单位 88 | keybind.manage_effect.name = 调整状态效果 89 | keybind.manage_item.name = 调整核心资源 90 | keybind.manage_team.name = 调整队伍 91 | keybind.place_core.name = 放置核心 92 | keybind.alternative.name = 可选功能 ([accent]按住 + 方块信息[]) 93 | # for tooltip 关于功能提示 94 | keybind.lock_shoot.name = 开火锁定 95 | 96 | keycomb.name = 绑定键组合 97 | keycomb.main = 可替换组合 98 | keycomb.view_comb = 显示绑定键组合 99 | keycomb.view_sets = 查看图形设置 100 | keycomb.reset_ai = 重置AI 101 | keycomb.spawn_unit = 刷单位 102 | keycomb.despawn = 自杀 103 | keycomb.teleport = 瞬移 104 | keycomb.lock_move = 移动锁定 105 | keycomb.schematic = 改变蓝图图层 106 | keycomb.toggle_bt = 切换建筑工具 107 | keycomb.return = 撤销最后拆除建筑 108 | keycomb.drop = 清空建筑中的材料 109 | 110 | tooltip.drop = 清空鼠标下建筑中的材料 111 | tooltip.replace = 将建物筑物及其附近同类建筑覆盖为所选建筑 112 | tooltip.remove = 拆除建筑物以及附近同类建筑 113 | tooltip.connect = 从最近的电源连接到节点 114 | tooltip.fill = 用所选建筑填充区域 115 | tooltip.square = 建造正方形 116 | tooltip.circle = 建造圆环 117 | tooltip.pick = 复制鼠标下的地板 118 | tooltip.edit = 地板填充 119 | tooltip.brush = 地板画笔 120 | 121 | select.ai = 选择AI 122 | select.ai.tooltip = 你可以替换一个玩家的伽马ai 123 | select.team = 选择队伍 124 | select.tile = 选择地板 125 | select.tag = 选择标签 126 | select.unit = 选择单位 127 | select.unit.clear = 清除单位 128 | select.effect = 选择状态效果 129 | select.effect.clear = 清除状态效果 130 | select.item = 选择材料 131 | select.item.clear = 清除材料 132 | 133 | select.all = 选择全部 134 | select.units = {0} 单位 135 | select.seconds = {0} 二级 136 | select.items = {0} 材料 137 | select.floor = 地板: {0} 138 | select.block = 建筑: {0} 139 | select.overlay = 覆盖: {0} 140 | select.tagdesc = 蓝图快捷方式,显示所选标签内蓝图\n就像蓝图库中的标签原理\n您所选标签是{0}[white],您也可以任意选择其他您喜欢的标签 141 | 142 | trace.type.show = 显示参数值玩家 143 | trace.type.nodata = 此服务器没有提供数据\n请要求服主集成参数值 144 | trace.type.mod = 看起来这个玩家正在使用参数值 145 | trace.type.vanilla = 看起来这个玩家使用的原版 146 | trace.type.self = 看起来这是您 147 | trace.type.host = 看起来这是服主 148 | 149 | render.name = 渲染设置 150 | render.desc = 这些功能可能导致延迟!\n如果发生这种事,我很抱歉 151 | render.power = 显示电力激光 152 | render.status = 显示建筑状态 153 | render.light = 显示光照 154 | render.dark = 显示黑暗 155 | render.fog = 显示战争迷雾 156 | render.xray = 显示X光 157 | render.hide = 显示隐藏单位 158 | render.grid = 显示建筑网格 159 | render.ruler = 显示鼠标量尺 160 | render.info = 显示单位血量信息 161 | render.border = 不显示显示屏边界 162 | render.unit = 显示单位攻击半径 163 | render.turret = 显示炮台攻击半径 164 | render.reactor = 显示反应堆爆炸半径 165 | render.drive = 显示超速投影半径 166 | 167 | admins.name = 管理员配置 168 | admins.lever = 管理员功能 {0} 169 | admins.enabled = 授权 170 | admins.disabled = 禁用 171 | admins.way = 您想使用哪个方式进行游戏? 172 | admins.way.internal.name = 内置 173 | admins.way.internal.desc = 如果您是主机或本地游戏,请选择它 174 | admins.way.slashjs.name = 脚本 175 | admins.way.slashjs.desc = 如果你在一个有JSEval的服务器上游玩,请选择它\n请小心滥用,因为它会制造大量垃圾信息 176 | admins.way.darkdustry.name = Darkdustry 177 | admins.way.darkdustry.desc = 如果您游玩的是[accent]Darkdustry.tk[]服务器,请选择它 178 | admins.always = 跳过管理员/服务器检测,不推荐使用。但在一些无政府状态服务器中可能有用 179 | admins.strict = 防止玩家传送。 这在服务器上默认启用,但在本地主机上不启用 180 | 181 | admins.notenabled = [scarlet]\u26A0[]此功能已被禁用!\n要使用它,请启用管理员配置中的相关功能({0}) 182 | admins.notanadmin = [scarlet]\u26A0[]此功能不可用,因为您不是管理员! 183 | admins.notavailable = [scarlet]\u26A0[]此功能不可用,因为您不是服务端!\n如果您是管理员,请去管理员配置({0}) 184 | admins.notsupported = [scarlet]\u26A0[]此功能不被darkdustry支持! 185 | admins.nocore = [scarlet]\u26A0[]所选队伍没有核心! 186 | admins.nounit = [scarlet]\u26A0[]所选队伍不能创建更多单位! 187 | 188 | console.classic = 原版 189 | console.multiline = 多线程 190 | console.multiline.send = 发送 191 | console.schedule = 列表 192 | console.schedule.new = 新任务 193 | console.schedule.interval = 秒间隔 194 | console.schedule.tooltip = 最小为0.01秒 195 | console.schedule.output = 输出:{0}{1} 196 | console.schedule.cancel = 取消任务 197 | 198 | updater.name = [scarlet]\u26A0[]旧版模组! 199 | updater.info = 您的[accent]参数值[]为旧版.\n这个烦人的对话框可以在设置中关闭\n\n[gray]当前版本为v{0}最新版本为v{1}[] 200 | updater.load = 安装 201 | -------------------------------------------------------------------------------- /src/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzxADIxzx/Scheme-Size/f6c50f484ad690bef73c26a8300001b4e75b2ab2/src/resources/icon.png -------------------------------------------------------------------------------- /src/resources/scripts/main.js: -------------------------------------------------------------------------------- 1 | // this script is mainly used to help with development of the mod 2 | // as it loads the main classes into the dev console 3 | 4 | var mod = Vars.mods.getMod("schema") 5 | var get = (pkg) => mod.loader.loadClass(pkg).newInstance() 6 | 7 | // loader is null on mobile devices, because dex is used instead of java byte code 8 | if (Vars.mobile) get = (pkg) => null; 9 | 10 | const Main = mod.main 11 | const Updater = get("schema.Updater") 12 | const ServerIntegration = get("scheme.ServerIntegration") 13 | const ClajIntegration = get("scheme.ClajIntegration") 14 | const Stl = get("schema.ui.Style") 15 | 16 | // for some unknown reason, this works only here, in the script 17 | // basically, a new atlas region is created to be overridden then by the sprite from the override directory 18 | Core.atlas.addRegion("status-invincible", Core.atlas.white()); 19 | -------------------------------------------------------------------------------- /src/resources/sprites-override/status-invincible.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzxADIxzx/Scheme-Size/f6c50f484ad690bef73c26a8300001b4e75b2ab2/src/resources/sprites-override/status-invincible.png -------------------------------------------------------------------------------- /src/resources/sprites/button-disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzxADIxzx/Scheme-Size/f6c50f484ad690bef73c26a8300001b4e75b2ab2/src/resources/sprites/button-disabled.png -------------------------------------------------------------------------------- /src/resources/sprites/button-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzxADIxzx/Scheme-Size/f6c50f484ad690bef73c26a8300001b4e75b2ab2/src/resources/sprites/button-down.png -------------------------------------------------------------------------------- /src/resources/sprites/button-over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzxADIxzx/Scheme-Size/f6c50f484ad690bef73c26a8300001b4e75b2ab2/src/resources/sprites/button-over.png -------------------------------------------------------------------------------- /src/resources/sprites/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzxADIxzx/Scheme-Size/f6c50f484ad690bef73c26a8300001b4e75b2ab2/src/resources/sprites/button.png -------------------------------------------------------------------------------- /src/resources/sprites/panel-bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzxADIxzx/Scheme-Size/f6c50f484ad690bef73c26a8300001b4e75b2ab2/src/resources/sprites/panel-bottom.png -------------------------------------------------------------------------------- /src/resources/sprites/panel-clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzxADIxzx/Scheme-Size/f6c50f484ad690bef73c26a8300001b4e75b2ab2/src/resources/sprites/panel-clear.png -------------------------------------------------------------------------------- /src/resources/sprites/panel-top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzxADIxzx/Scheme-Size/f6c50f484ad690bef73c26a8300001b4e75b2ab2/src/resources/sprites/panel-top.png -------------------------------------------------------------------------------- /src/resources/sprites/panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzxADIxzx/Scheme-Size/f6c50f484ad690bef73c26a8300001b4e75b2ab2/src/resources/sprites/panel.png -------------------------------------------------------------------------------- /src/resources/sprites/scroll-knob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzxADIxzx/Scheme-Size/f6c50f484ad690bef73c26a8300001b4e75b2ab2/src/resources/sprites/scroll-knob.png --------------------------------------------------------------------------------