├── .idea └── .gitignore ├── src ├── resources │ ├── icon.png │ ├── sprites │ │ └── status-invincible.png │ ├── scripts │ │ └── main.js │ └── bundles │ │ ├── bundle_ja.properties │ │ └── bundle_zh_CN.properties └── java │ └── scheme │ ├── ui │ ├── FlipButton.java │ ├── MapResizeFix.java │ ├── dialogs │ │ ├── TeamSelectDialog.java │ │ ├── SettingsMenuDialog.java │ │ ├── TagSelectDialog.java │ │ ├── ListDialog.java │ │ ├── WaveApproachingDialog.java │ │ ├── ImageParserDialog.java │ │ ├── SchemasDialog.java │ │ ├── AdminsConfigDialog.java │ │ ├── RendererConfigDialog.java │ │ ├── ContentSelectDialog.java │ │ ├── AISelectDialog.java │ │ ├── RuleSetterDialog.java │ │ └── TileSelectDialog.java │ ├── TextSlider.java │ ├── PowerBars.java │ ├── HexBar.java │ ├── List.java │ ├── HexSelection.java │ ├── ShortcutFragment.java │ ├── CoreInfoFragment.java │ └── PlayerListFragment.java │ ├── tools │ ├── MessageQueue.java │ ├── RainbowTeam.java │ ├── admins │ │ ├── AdminsTools.java │ │ ├── Darkdustry.java │ │ ├── Internal.java │ │ └── SlashJs.java │ ├── UnitsCache.java │ ├── BuildsCache.java │ ├── RendererTools.java │ ├── BuildingTools.java │ └── ImageParser.java │ ├── ai │ ├── NetMinerAI.java │ └── GammaAI.java │ ├── claj │ └── client │ │ ├── ClajServers.java │ │ ├── dialogs │ │ └── JoinViaClajDialog.java │ │ ├── ClajLink.java │ │ ├── Claj.java │ │ └── ClajPackets.java │ ├── SchemeUpdater.java │ ├── ServerIntegration.java │ ├── moded │ ├── ModedInputHandler.java │ ├── ModedMobileInput.java │ └── ModedDesktopInput.java │ ├── Main.java │ └── SchemeVars.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── .github └── workflows │ └── android.yml ├── mod.hjson └── gradlew /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /src/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/00SunRay00/scheme-size-port/HEAD/src/resources/icon.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/00SunRay00/scheme-size-port/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/resources/sprites/status-invincible.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/00SunRay00/scheme-size-port/HEAD/src/resources/sprites/status-invincible.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/* 2 | .settings/* 3 | .vscode/* 4 | 5 | bin/* 6 | lib/* 7 | build/* 8 | 9 | *.classpath 10 | *.project 11 | *.bat 12 | *.xml 13 | *.directory 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /src/resources/scripts/main.js: -------------------------------------------------------------------------------- 1 | // it is really useful for development 2 | var mod = Vars.mods.getMod("scheme-size") 3 | var get = (pkg) => mod.loader.loadClass(pkg).newInstance() 4 | 5 | // mod.loader is null on mobile devices 6 | if (Vars.mobile) get = (pkg) => null; 7 | 8 | const SchemeMain = mod.main 9 | const SchemeVars = get("scheme.SchemeVars") 10 | const SchemeUpdater = get("scheme.SchemeUpdater") 11 | const ServerIntegration = get("scheme.ServerIntegration") 12 | const ModedSchematics = get("scheme.moded.ModedSchematics") 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Set up JDK 17 17 | uses: actions/setup-java@v4 18 | with: 19 | java-version: '17' 20 | distribution: 'temurin' 21 | cache: gradle 22 | 23 | - name: Set up Android SDK 24 | uses: android-actions/setup-android@v3 25 | 26 | - name: Grant execute permission for gradlew 27 | run: chmod +x gradlew 28 | 29 | - name: Build with Gradle 30 | run: ./gradlew deploy 31 | 32 | - name: Upload build artifact 33 | uses: actions/upload-artifact@v4 34 | with: 35 | name: scheme-size-port-build 36 | path: build/libs/*.jar 37 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /mod.hjson: -------------------------------------------------------------------------------- 1 | name: scheme-size, 2 | displayName: Scheme Size Port, 3 | subtitle: No longer just increases the schema size, 4 | description: " 5 | A mod that increases the schematic size limit to 512 blocks! Adds admins, renderer and building tools. Has an updater, keybinds and AI support. 6 | [red]This mod is not the original port of Scheme Size — it is a custom port. The developer of the original mod is not involved in this in any way. 7 | [red]!!!ATTENTION: VERSIONS BELOW v154 ARE NOT SUPPORTED!!! 8 | [red]Vanilla Only![orange] 9 | 10 | - Release! 11 | - Settings 12 | - Java 8 13 | - Controls 14 | - Updater 15 | - Mobile Support 16 | - Admins Tools 17 | 18 | - Building Tools 19 | - AI Power 20 | - Renderer Tools 21 | - Deep Cleaning 22 | - Schematic Shortcuts 23 | - Cursed Schematics 24 | - Advanced Dev Console 25 | - Copy Link and Join 26 | - Rule setter 27 | ", 28 | author: "[#0096FF]xzxADIxzx\nPort: 00SunRay00", 29 | minGameVersion: 154, 30 | version: 1.0.4.1, 31 | hidden: true, 32 | main: scheme.Main 33 | -------------------------------------------------------------------------------- /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/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/claj/client/ClajServers.java: -------------------------------------------------------------------------------- 1 | package scheme.claj.client; 2 | 3 | import arc.Core; 4 | import arc.struct.ArrayMap; 5 | import arc.struct.ObjectMap; 6 | import arc.util.Http; 7 | import arc.util.serialization.Jval; 8 | 9 | 10 | public class ClajServers { 11 | public static final String publicServersLink = 12 | "https://github.com/xpdustry/claj/blob/main/public-servers.hjson?raw=true"; 13 | public static final ArrayMap online = new ArrayMap<>(), 14 | custom = new ArrayMap<>(); 15 | 16 | public static synchronized void refreshOnline(Runnable done, arc.func.Cons failed) { 17 | // Public list 18 | Http.get(publicServersLink, result -> { 19 | Jval.JsonMap list = Jval.read(result.getResultAsString()).asObject(); 20 | online.clear(); 21 | for (ObjectMap.Entry e : list) 22 | online.put(e.key, e.value.asString()); 23 | done.run(); 24 | }, failed); 25 | //online.put("Chaotic Neutral", "n3.xpdustry.com:7025"); 26 | } 27 | 28 | @SuppressWarnings("unchecked") 29 | public static void loadCustom() { 30 | custom.clear(); 31 | custom.putAll(Core.settings.getJson("claj-custom-servers", ArrayMap.class, String.class, ArrayMap::new)); 32 | } 33 | 34 | public static void saveCustom() { 35 | Core.settings.putJson("claj-custom-servers", String.class, custom); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /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/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("crashreports", true); in dev 22 | table.checkPref("hardscheme", false); 23 | table.checkPref("approachenabled", true); 24 | table.checkPref("welcome", true); 25 | table.checkPref("check4update", true); 26 | 27 | table.areaTextPref("subtitle", "I am using Scheme Size btw"); 28 | }); 29 | } 30 | 31 | public void apply() { 32 | m_input.changePanSpeed(settings.getInt("panspeedmul")); 33 | 34 | // 6f and 1.5f are default values in Renderer 35 | renderer.maxZoom = settings.getInt("maxzoommul") / 4f * 6f; 36 | renderer.minZoom = 1f / (settings.getInt("minzoommul") / 4f) * 1.5f; 37 | } 38 | 39 | private String processor(int value) { 40 | return value / 4f + "x"; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /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/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/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/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/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/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 = bundle.format("admins.notenabled"); 18 | public String unabailable = bundle.format("admins.notavailable"); 19 | 20 | public void manageRuleBool(boolean value, String name); 21 | 22 | public void manageRuleStr(String value, String name); 23 | 24 | public void manageUnit(); 25 | 26 | public void spawnUnits(); 27 | 28 | public void manageEffect(); 29 | 30 | public void manageItem(); 31 | 32 | public void manageTeam(); 33 | 34 | public void placeCore(); 35 | 36 | public void despawn(Player target); 37 | 38 | public default void despawn() { 39 | despawn(player); 40 | } 41 | 42 | public void teleport(Position pos); 43 | 44 | public default void teleport() { 45 | teleport(camera.position); 46 | } 47 | 48 | public default void look() { 49 | for (int i = 0; i < 10; i++) player.unit().lookAt(input.mouseWorld()); 50 | } 51 | 52 | public void fill(int sx, int sy, int ex, int ey); 53 | 54 | public void brush(int x, int y, int radius); 55 | 56 | public void flush(Seq plans); 57 | 58 | public boolean unusable(); 59 | 60 | public default int fixAmount(Item item, Float amount) { 61 | int items = player.core().items.get(item); 62 | return amount == 0f || items + amount < 0 ? -items : amount.intValue(); 63 | } 64 | 65 | public default boolean canCreate(Team team, UnitType type) { 66 | boolean can = Units.canCreate(team, type); 67 | if (!can) ui.showInfoFade("@admins.nounit"); 68 | return can; 69 | } 70 | 71 | public default boolean hasCore(Team team) { 72 | boolean has = team.core() != null; 73 | if (!has) ui.showInfoFade("@admins.nocore"); 74 | return has; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /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/claj/client/dialogs/JoinViaClajDialog.java: -------------------------------------------------------------------------------- 1 | package scheme.claj.client.dialogs; 2 | 3 | import scheme.claj.client.Claj; 4 | import scheme.claj.client.ClajLink; 5 | 6 | import mindustry.Vars; 7 | 8 | 9 | public class JoinViaClajDialog extends mindustry.ui.dialogs.BaseDialog { 10 | String lastLink = "claj://"; 11 | boolean valid; 12 | String output; 13 | 14 | public JoinViaClajDialog() { 15 | super("@claj.join.name"); 16 | 17 | cont.defaults().width(Vars.mobile ? 350f : 550f); 18 | 19 | 20 | cont.labelWrap("@claj.join.note").padBottom(10f).left().row(); 21 | cont.table(table -> { 22 | table.add("@claj.join.link").padRight(5f).left(); 23 | table.field(lastLink, this::setLink).maxTextLength(100).valid(this::setLink).height(54f).growX().row(); 24 | table.add(); 25 | table.labelWrap(() -> output).left().growX().row(); 26 | }).row(); 27 | 28 | buttons.defaults().size(140f, 60f).pad(4f); 29 | buttons.button("@cancel", this::hide); 30 | buttons.button("@ok", this::joinRoom).disabled(button -> !valid || lastLink.isEmpty() || Vars.net.active()); 31 | 32 | //Adds the 'Join via CLaJ' button 33 | if (!Vars.steam && !Vars.mobile) { 34 | Vars.ui.join.buttons.button("@claj.join.name", mindustry.gen.Icon.play, this::show).row(); 35 | Vars.ui.join.buttons.getCells().swap(Vars.ui.join.buttons.getCells().size-1/*6*/, 4); 36 | } else { 37 | // adds in a new line for mobile players 38 | Vars.ui.join.buttons.row().add().growX().width(-1); 39 | Vars.ui.join.buttons.button("@claj.join.name", mindustry.gen.Icon.play, this::show).row(); 40 | } 41 | } 42 | 43 | public void joinRoom() { 44 | if (Vars.player.name.trim().isEmpty()) { 45 | Vars.ui.showInfo("@noname"); 46 | return; 47 | } 48 | 49 | ClajLink link; 50 | try { link = ClajLink.fromString(lastLink); } 51 | catch (Exception e) { 52 | valid = false; 53 | Vars.ui.showErrorMessage(arc.Core.bundle.get("claj.join.invalid") + ' ' + e.getLocalizedMessage()); 54 | return; 55 | } 56 | 57 | Vars.ui.loadfrag.show("@connecting"); 58 | Vars.ui.loadfrag.setButton(() -> { 59 | Vars.ui.loadfrag.hide(); 60 | Vars.netClient.disconnectQuietly(); 61 | }); 62 | 63 | arc.util.Time.runTask(2f, () -> 64 | Claj.joinRoom(link, () -> { 65 | Vars.ui.join.hide(); 66 | hide(); 67 | }) 68 | ); 69 | } 70 | 71 | public boolean setLink(String link) { 72 | if (lastLink.equals(link)) return valid; 73 | 74 | lastLink = link; 75 | try { 76 | ClajLink.fromString(lastLink); 77 | output = "@claj.join.valid"; 78 | return valid = true; 79 | 80 | } catch (Exception e) { 81 | output = arc.Core.bundle.get("claj.join.invalid") + ' ' + e.getLocalizedMessage(); 82 | return valid = false; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /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/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 | import static scheme.SchemeVars.*; 18 | 19 | public class WaveApproachingDialog extends BaseDialog { 20 | 21 | public Label health; 22 | public Label shield; 23 | 24 | public Table enemies; 25 | public Table bosses; 26 | 27 | public WaveApproachingDialog() { 28 | super("@approaching.name"); 29 | addCloseButton(); 30 | 31 | setFillParent(false); // no sense in full screen dialog 32 | cont.add().width(350f).row(); // set min width 33 | 34 | cont.add("").with(l -> health = l).left().row(); 35 | cont.add("").with(l -> shield = l).left().row(); 36 | 37 | cont.add("@approaching.enemies").left(); 38 | cont.button(Icon.copySmall, Styles.clearNonei, () -> copyUnits(units.waveUnits)).row(); 39 | cont.table(t -> enemies = t).padLeft(16f).left().row(); 40 | 41 | cont.add("@approaching.bosses").left(); 42 | cont.button(Icon.copySmall, Styles.clearNonei, () -> copyUnits(units.waveBosses)).row(); 43 | cont.table(t -> bosses = t).padLeft(16f).left().row(); 44 | } 45 | 46 | @Override 47 | public Dialog show() { 48 | units.refreshWaveInfo(); 49 | title.setText(bundle.format("approaching.name", String.valueOf(state.wave))); 50 | 51 | health.setText(bundle.format("approaching.health", units.waveHealth)); 52 | shield.setText(bundle.format("approaching.shield", units.waveShield)); 53 | 54 | addUnits(enemies, units.waveUnits); 55 | addUnits(bosses, units.waveBosses); 56 | 57 | return super.show(); 58 | } 59 | 60 | private void addUnits(Table table, ObjectIntMap units) { 61 | table.clear(); 62 | 63 | if (units.isEmpty()) table.add("@none"); 64 | else for (var entry : units) { 65 | table.stack( 66 | new Image(entry.key.uiIcon).setScaling(Scaling.fit), 67 | new Table(pad -> pad.bottom().left().add(String.valueOf(entry.value))) 68 | ).size(32f).padRight(8f); 69 | } 70 | } 71 | 72 | private void copyUnits(ObjectIntMap units) { 73 | StringBuilder builder = new StringBuilder(); 74 | 75 | if (units.isEmpty()) builder.append(bundle.get("none")); 76 | else for (var entry : units) 77 | builder.append(entry.value).append(entry.key.emoji()).append(" "); 78 | 79 | Main.copy(builder.toString()); 80 | hide(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/java/scheme/SchemeUpdater.java: -------------------------------------------------------------------------------- 1 | package scheme; 2 | 3 | import arc.util.Http; 4 | import arc.util.Http.HttpResponse; 5 | import arc.util.io.Streams; 6 | import arc.util.serialization.Jval; 7 | import arc.files.Fi; 8 | import mindustry.mod.Mods.LoadedMod; 9 | 10 | import static arc.Core.*; 11 | import static mindustry.Vars.*; 12 | 13 | import java.net.*; 14 | 15 | public class SchemeUpdater { 16 | 17 | public static final String repo = "00SunRay00/scheme-size-port"; 18 | 19 | public static LoadedMod mod; 20 | public static String url; 21 | 22 | public static float progress; 23 | public static String download; 24 | 25 | public static void load() { 26 | mod = mods.getMod(Main.class); 27 | url = ghApi + "/repos/" + repo + "/releases/latest"; 28 | 29 | Jval meta = Jval.read(mod.root.child("mod.hjson").readString()); 30 | mod.meta.author = meta.getString("author"); // restore colors in mod's meta 31 | mod.meta.description = meta.getString("description"); 32 | } 33 | 34 | public static void check() { 35 | Main.log("Checking for updates."); 36 | Http.get(url, res -> { 37 | Jval json = Jval.read(res.getResultAsString()); 38 | String latest = json.getString("tag_name").substring(1); 39 | download = json.get("assets").asArray().get(0).getString("browser_download_url"); 40 | 41 | if (!latest.equals(mod.meta.version)) ui.showCustomConfirm( 42 | "@updater.name", bundle.format("updater.info", mod.meta.version, latest), 43 | "@updater.load", "@ok", SchemeUpdater::update, () -> {}); 44 | }, Main::error); 45 | } 46 | 47 | public static void update() { 48 | try { // dancing with tambourines, just to remove the old mod 49 | if (mod.loader instanceof URLClassLoader cl) cl.close(); 50 | mod.loader = null; 51 | } catch (Throwable e) { Main.error(e); } // this has never happened before, but everything can be 52 | 53 | ui.loadfrag.show("@downloading"); 54 | ui.loadfrag.setProgress(() -> progress); 55 | 56 | Http.get(download, SchemeUpdater::handle, Main::error); 57 | } 58 | 59 | public static void handle(HttpResponse res) { 60 | try { 61 | Fi file = tmpDirectory.child(repo.replace("/", "") + ".zip"); 62 | Streams.copyProgress(res.getResultAsStream(), file.write(false), res.getContentLength(), 4096, p -> progress = p); 63 | 64 | mods.importMod(file).setRepo(repo); 65 | file.delete(); 66 | 67 | app.post(ui.loadfrag::hide); 68 | ui.showInfoOnHidden("@mods.reloadexit", app::exit); 69 | } catch (Throwable e) { Main.error(e); } 70 | } 71 | 72 | public static Fi script() { 73 | return mod.root.child("scripts").child("main.js"); 74 | } 75 | 76 | public static boolean installed(String mod) { 77 | return mods.getMod(mod) != null && mods.getMod(mod).enabled(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /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/HexSelection.java: -------------------------------------------------------------------------------- 1 | package scheme.ui; 2 | 3 | import arc.func.Cons; 4 | import arc.graphics.Color; 5 | import arc.graphics.g2d.Draw; 6 | import arc.graphics.g2d.Lines; 7 | import arc.math.Mathf; 8 | import arc.math.geom.Vec2; 9 | import arc.scene.ui.TextButton; 10 | import arc.scene.ui.layout.Scl; 11 | import arc.scene.ui.layout.Stack; 12 | import arc.scene.ui.layout.Table; 13 | import arc.util.Align; 14 | import mindustry.graphics.Pal; 15 | import mindustry.ui.Styles; 16 | 17 | import static arc.Core.*; 18 | 19 | public class HexSelection extends Stack { 20 | 21 | public static final float size = Scl.scl(60f), stroke = Scl.scl(24f), half = stroke / 2f; 22 | public static final Color background = Color.valueOf("00000099"); 23 | 24 | public TextButton[] buttons = new TextButton[6]; 25 | public Vec2[] vertices = new Vec2[6]; 26 | 27 | public int selectedIndex = -1; 28 | public boolean updatable = true; 29 | 30 | public HexSelection() { 31 | for (int deg = 0; deg < 360; deg += 60) 32 | vertices[deg / 60] = new Vec2(Mathf.cosDeg(deg) * size, Mathf.sinDeg(deg) * size); 33 | } 34 | 35 | public void add(String icon, Cons listener) { 36 | int i = children.size; 37 | add(new Table(table -> { 38 | table.button(icon, Styles.cleart, () -> listener.get(buttons[i])).with(button -> { 39 | buttons[i] = button; 40 | 41 | button.getLabel().setWrap(false); // someone can use non-single-character tags 42 | button.update(() -> { 43 | Vec2 offset = selectedIndex == i ? vertices[i].cpy().limit(stroke / 3f) : Vec2.ZERO; 44 | button.setTranslation(vertices[i].x + offset.x - half, vertices[i].y + offset.y - half); 45 | }); 46 | }).minSize(24f); // unscaled stroke; 47 | })); 48 | } 49 | 50 | public void updateAlignment() { 51 | for (int i = 0; i < buttons.length; i++) { 52 | int align = buttons[i].getText().length() == 1 ? Align.center : (i <= 1 || i == 5 ? Align.left : Align.right); 53 | buttons[i].getLabel().setAlignment(Align.center, align); 54 | } 55 | } 56 | 57 | @Override 58 | public void draw() { 59 | Lines.stroke(stroke, background); 60 | Lines.poly(x, y, 6, size); 61 | 62 | if (Mathf.within(x, y, input.mouseX(), input.mouseY(), stroke)) selectedIndex = -1; 63 | else { 64 | float min = Float.POSITIVE_INFINITY; 65 | if (updatable) for (int i = 0; i < vertices.length; i++) { 66 | float dst = Mathf.dst(x + vertices[i].x, y + vertices[i].y, input.mouseX(), input.mouseY()); 67 | if (dst < min) { 68 | min = dst; 69 | selectedIndex = i; 70 | } // yeah, it is 71 | } 72 | 73 | if (selectedIndex == -1) return; // please, try to avoid it 74 | Vec2 offset = vertices[selectedIndex].cpy().limit(half); 75 | 76 | Draw.color(Pal.accent); 77 | Lines.stroke(half); 78 | Lines.arc(x + offset.x, y + offset.y, size, 1f / 3f, (selectedIndex - 1) * 60f, 6); 79 | } 80 | 81 | Draw.reset(); 82 | super.draw(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/java/scheme/ui/dialogs/SchemasDialog.java: -------------------------------------------------------------------------------- 1 | package scheme.ui.dialogs; 2 | 3 | import arc.scene.style.Drawable; 4 | import arc.scene.ui.Dialog; 5 | import arc.scene.ui.TextButton; 6 | import arc.scene.ui.layout.Cell; 7 | import arc.scene.ui.layout.Table; 8 | import arc.util.Reflect; 9 | import mindustry.game.Schematic; 10 | import mindustry.gen.Icon; 11 | import mindustry.gen.Tex; 12 | import mindustry.ui.Styles; 13 | import mindustry.ui.dialogs.BaseDialog; 14 | import mindustry.ui.dialogs.SchematicsDialog; 15 | import scheme.moded.ModedSchematics; 16 | 17 | import static arc.Core.*; 18 | import static mindustry.Vars.*; 19 | import static scheme.SchemeVars.*; 20 | 21 | public class SchemasDialog extends SchematicsDialog { 22 | 23 | @Override 24 | public Dialog show() { 25 | return super.show(); // do not show SchematicsDialog if the keybind combination is pressed 26 | } 27 | 28 | @Override 29 | public void showImport() { 30 | new ImportDialog().show(); 31 | } 32 | 33 | public void imported(Schematic schematic) { 34 | Reflect.invoke(SchematicsDialog.class, this, "setup", null); 35 | Reflect.invoke(SchematicsDialog.class, this, "checkTags", new Object[] { schematic }, Schematic.class); 36 | showInfo(schematic); 37 | 38 | schematic.removeSteamID(); 39 | schematics.add(schematic); 40 | } 41 | 42 | public class ImportDialog extends BaseDialog { 43 | 44 | public ImportDialog() { 45 | super("@editor.export"); 46 | addCloseButton(); 47 | 48 | cont.pane(pane -> { 49 | pane.margin(10f); 50 | pane.table(Tex.button, t -> { 51 | t.defaults().size(280f, 60f).left(); 52 | 53 | button(t, "copy.import", Icon.copy, () -> { 54 | try { 55 | imported(ModedSchematics.readBase64(app.getClipboardText())); 56 | ui.showInfoFade("@schematic.saved"); 57 | } catch (Throwable error) { 58 | ui.showException(error); 59 | } 60 | }).disabled(b -> app.getClipboardText() == null || !app.getClipboardText().startsWith(schematicBaseStart)).row(); 61 | 62 | button(t, "importfile", Icon.download, () -> platform.showFileChooser(true, schematicExtension, file -> { 63 | try { 64 | imported(ModedSchematics.read(file)); 65 | } catch (Throwable error) { 66 | ui.showException(error); 67 | } 68 | })).row(); 69 | 70 | button(t, "importimage", Icon.image, () -> { // do not replace with :: because null pointer 71 | platform.showFileChooser(true, "png", file -> parser.show(file)); 72 | }).row(); 73 | 74 | if (!steam) return; 75 | 76 | button("browseworkshop", Icon.book, () -> platform.openWorkshop()); 77 | }); 78 | }); 79 | } 80 | 81 | private Cell button(Table table, String text, Drawable image, Runnable listener) { 82 | return table.button("@schematic." + text, image, Styles.flatt, () -> { 83 | hide(); 84 | listener.run(); 85 | }).marginLeft(12f); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /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/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/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/tools/UnitsCache.java: -------------------------------------------------------------------------------- 1 | package scheme.tools; 2 | 3 | import arc.Events; 4 | import arc.struct.ObjectIntMap; 5 | import arc.struct.Seq; 6 | import mindustry.content.StatusEffects; 7 | import mindustry.entities.abilities.Ability; 8 | import mindustry.entities.abilities.ForceFieldAbility; 9 | import mindustry.entities.abilities.ShieldArcAbility; 10 | import mindustry.game.EventType.*; 11 | import mindustry.gen.Groups; 12 | import mindustry.gen.Unit; 13 | import mindustry.type.UnitType; 14 | 15 | import static arc.Core.*; 16 | import static mindustry.Vars.*; 17 | 18 | public class UnitsCache { 19 | 20 | public float maxShield; 21 | public int maxAccepted; 22 | 23 | public ShieldArcAbility ability; 24 | public Seq cache = new Seq<>(); 25 | 26 | public float waveHealth; 27 | public float waveShield; 28 | 29 | public ObjectIntMap waveUnits = new ObjectIntMap<>(); 30 | public ObjectIntMap waveBosses = new ObjectIntMap<>(); 31 | 32 | public void load() { 33 | Events.run(WorldLoadEvent.class, () -> app.post(this::refresh)); 34 | Events.run(UnitSpawnEvent.class, this::refresh); 35 | Events.run(UnitCreateEvent.class, this::refresh); 36 | Events.run(UnitUnloadEvent.class, this::refresh); 37 | Events.run(UnitChangeEvent.class, this::refresh); 38 | 39 | Events.on(UnitChangeEvent.class, event -> { 40 | if (event.player != player) return; 41 | 42 | Unit unit = player.unit(); 43 | if (unit == null) { 44 | maxAccepted = 0; // или другое значение по умолчанию 45 | maxShield = -1; 46 | ability = null; 47 | return; 48 | } 49 | 50 | maxAccepted = unit.type.itemCapacity; 51 | 52 | maxShield = -1; 53 | ability = null; 54 | 55 | for (Ability ability : unit.abilities) { 56 | if (ability instanceof ForceFieldAbility field) { 57 | maxShield = field.max; 58 | break; 59 | } else if (ability instanceof ShieldArcAbility shield) { 60 | maxShield = shield.max; 61 | this.ability = shield; 62 | break; 63 | } 64 | } 65 | }); 66 | } 67 | 68 | public float shield() { 69 | if (ability != null) { 70 | return ability.data; 71 | } 72 | 73 | Unit unit = player.unit(); 74 | return unit != null ? unit.shield : 0f; 75 | } 76 | 77 | 78 | public void refresh() { 79 | if (!cache.isEmpty()) cache(); 80 | } 81 | 82 | public void refreshWaveInfo() { 83 | waveHealth = waveShield = 0; 84 | waveUnits.clear(); 85 | waveBosses.clear(); 86 | 87 | state.rules.spawns.each(group -> group.type != null, group -> { 88 | int amount = group.getSpawned(state.wave - 1); 89 | if (amount == 0) return; 90 | 91 | waveHealth += group.type.health * amount; 92 | waveShield += group.getShield(state.wave - 1); 93 | waveUnits.put(group.type, amount); 94 | if (group.effect == StatusEffects.boss) waveBosses.put(group.type, amount); 95 | }); 96 | } 97 | 98 | public void cache() { 99 | Groups.draw.each(drawc -> drawc instanceof Unit, drawc -> { 100 | Groups.draw.remove(drawc); 101 | cache.add((Unit) drawc); 102 | }); 103 | } 104 | 105 | public void uncache() { 106 | cache.each(Unit::isAdded, Groups.draw::add); 107 | cache.clear(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /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 {@link ServerIntegration#load()} 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 | Call.serverPacketReliable("MySubtitle", settings.getString("subtitle")); 59 | if (args != null) hostID = Strings.parseInt(args, -1); 60 | }); 61 | 62 | netClient.addPacketHandler("Subtitles", args -> { 63 | SSUsers = JsonIO.read(IntMap.class, args); 64 | hasData = true; 65 | }); 66 | 67 | // endregion 68 | } 69 | 70 | /** Clears all data about users. */ 71 | public static void clear() { 72 | SSUsers.clear(); 73 | hostID = -1; 74 | hasData = false; 75 | 76 | // put the host's subtitle so that you do not copy the int map later 77 | SSUsers.put(player.id, settings.getString("subtitle")); 78 | } 79 | 80 | /** Returns whether the user with the given id is using a mod. */ 81 | public static boolean isModded(int id) { 82 | return SSUsers.containsKey(id) || player.id == id; // of course you are a modded player 83 | } 84 | 85 | /** Returns the user type with the given id: host, no data, mod or vanilla. */ 86 | public static String type(int id) { 87 | if (hostID == id) return "trace.type.host"; 88 | return !hasData && net.client() ? "trace.type.nodata" : isModded(id) ? "trace.type.mod" : "trace.type.vanilla"; 89 | } 90 | 91 | /** Returns the user type with subtitle. */ 92 | public static String tooltip(int id) { 93 | if (player.id == id) return "@trace.type.self"; 94 | return bundle.get(type(id)) + (isModded(id) ? "\n" + SSUsers.get(id) : ""); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/java/scheme/tools/BuildsCache.java: -------------------------------------------------------------------------------- 1 | package scheme.tools; 2 | 3 | import arc.Events; 4 | import arc.func.Cons; 5 | import arc.math.geom.QuadTree; 6 | import arc.math.geom.Rect; 7 | import arc.struct.Seq; 8 | import mindustry.game.EventType.*; 9 | import mindustry.gen.Building; 10 | import mindustry.gen.Groups; 11 | import mindustry.world.Tile; 12 | import mindustry.world.blocks.defense.OverdriveProjector.OverdriveBuild; 13 | import mindustry.world.blocks.defense.turrets.BaseTurret.BaseTurretBuild; 14 | import mindustry.world.blocks.power.ImpactReactor.ImpactReactorBuild; 15 | import mindustry.world.blocks.power.NuclearReactor.NuclearReactorBuild; 16 | 17 | import static mindustry.Vars.*; 18 | 19 | public class BuildsCache { 20 | 21 | public TilesQuadtree tiles; 22 | public Building[] builds; 23 | 24 | public Seq turrets = new Seq<>(); 25 | public Seq nuclears = new Seq<>(); 26 | public Seq impacts = new Seq<>(); 27 | public Seq overdrives = new Seq<>(); 28 | 29 | public void load() { 30 | Events.run(WorldLoadEvent.class, this::refresh); 31 | Events.on(BlockDestroyEvent.class, event -> uncache(event.tile)); 32 | Events.on(BlockBuildBeginEvent.class, event -> { 33 | if (!event.breaking) put(event.tile.build); 34 | }); 35 | Events.on(BlockBuildEndEvent.class, event -> { 36 | if (event.breaking) uncache(event.tile); 37 | else cache(event.tile.build); 38 | }); 39 | } 40 | 41 | public void refresh() { 42 | tiles = new TilesQuadtree(new Rect(0, 0, world.unitWidth(), world.unitHeight())); 43 | builds = new Building[world.width() * world.height()]; 44 | world.tiles.eachTile(tile -> { 45 | tiles.insert(tile); 46 | if (tile.build != null) put(tile.build); // cache all buildings on world load 47 | }); 48 | 49 | turrets.clear(); 50 | nuclears.clear(); 51 | impacts.clear(); 52 | overdrives.clear(); 53 | Groups.build.each(this::cache); 54 | } 55 | 56 | public void cache(Building build) { 57 | if (build instanceof BaseTurretBuild turret) turrets.add(turret); 58 | if (build instanceof NuclearReactorBuild nuclear) nuclears.add(nuclear); 59 | if (build instanceof ImpactReactorBuild impact) impacts.add(impact); 60 | if (build instanceof OverdriveBuild overdrive) overdrives.add(overdrive); 61 | } 62 | 63 | public void uncache(Tile tile) { 64 | turrets.removeAll(turret -> turret.tile == tile); 65 | nuclears.removeAll(nuclear -> nuclear.tile == tile); 66 | impacts.removeAll(impact -> impact.tile == tile); 67 | overdrives.removeAll(overdrive -> overdrive.tile == tile); 68 | } 69 | 70 | public void each(Rect bounds, Cons cons) { 71 | tiles.intersect(bounds, tile -> { 72 | if (tile.build != null) cons.get(tile); 73 | }); 74 | } 75 | 76 | public void put(Building build) { 77 | if (build == null) return; // idk how is it possible 78 | builds[build.tileY() * world.width() + build.tileX()] = build; 79 | } 80 | 81 | public Building get(Tile tile) { 82 | return builds[tile.y * world.width() + tile.x]; 83 | } 84 | 85 | public static class TilesQuadtree extends QuadTree { 86 | 87 | public TilesQuadtree(Rect bounds) { 88 | super(bounds); 89 | } 90 | 91 | @Override 92 | public void hitbox(Tile tile) { 93 | var floor = tile.floor(); 94 | tmp.setCentered(tile.worldx(), tile.worldy(), floor.clipSize, floor.clipSize); 95 | } 96 | 97 | @Override 98 | protected QuadTree newChild(Rect rect) { 99 | return new TilesQuadtree(rect); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/java/scheme/ui/ShortcutFragment.java: -------------------------------------------------------------------------------- 1 | package scheme.ui; 2 | 3 | import arc.math.geom.Vec2; 4 | import arc.scene.Group; 5 | import arc.scene.ui.layout.Table; 6 | import arc.util.Align; 7 | import arc.util.Scaling; 8 | import arc.util.Tmp; 9 | import mindustry.ui.Styles; 10 | import mindustry.ui.dialogs.SchematicsDialog.SchematicImage; 11 | import scheme.ui.dialogs.TagSelectDialog; 12 | 13 | import static arc.Core.*; 14 | import static mindustry.Vars.*; 15 | import static scheme.SchemeVars.*; 16 | 17 | public class ShortcutFragment { 18 | 19 | public HexSelection selection = new HexSelection(); 20 | public boolean visible; 21 | 22 | public int lastIndex; 23 | public Runnable rebuild; 24 | public Runnable onShown; 25 | 26 | public void build(Group parent) { 27 | parent.fill(cont -> { 28 | cont.name = "tagselection"; 29 | cont.add(selection).visible(() -> visible).size(24f); // buttons size 30 | 31 | for (int i = 0; i < 6; i++) { 32 | String key = "shortcut-tag-" + i; 33 | selection.add(settings.getString(key, TagSelectDialog.none), button -> { 34 | tag.show(button.getText().toString(), tag -> { 35 | button.setText(tag); 36 | settings.put(key, tag); 37 | 38 | selection.updateAlignment(); 39 | }); 40 | hide(); 41 | }); 42 | } 43 | 44 | selection.updateAlignment(); 45 | }); 46 | 47 | parent.fill(cont -> { 48 | cont.name = "schematicselection"; 49 | cont.visible(() -> visible); 50 | 51 | cont.pane(list -> rebuild = () -> { 52 | String tag = selection.buttons[lastIndex].getText().toString(); 53 | schematics.all().each(schematic -> schematic.labels.contains(tag), schematic -> { 54 | list.button(button -> button.stack( 55 | new SchematicImage(schematic).setScaling(Scaling.fit), 56 | new Table(table -> table.top().table(Styles.black3, title -> { 57 | title.add(schematic.name(), Styles.outlineLabel, .60f).width(72f).labelAlign(Align.center).get().setEllipsis(true); 58 | }).pad(4f).width(72f)) 59 | ), () -> { 60 | control.input.useSchematic(schematic); 61 | hide(); 62 | }).margin(0f).pad(2f).size(80f); 63 | 64 | if (list.getChildren().size % 4 == 0) list.row(); 65 | }); 66 | }).size(362f, 336f).update(pane -> { 67 | Tmp.r1.setSize(pane.getWidth(), pane.getHeight()).setPosition(pane.translation.x + pane.x, pane.translation.y + pane.y).grow(8f); 68 | selection.updatable = lastIndex == -1 || !Tmp.r1.contains(input.mouse()); 69 | 70 | if (selection.selectedIndex == lastIndex) return; 71 | lastIndex = selection.selectedIndex; 72 | pane.getWidget().clear(); 73 | 74 | if (lastIndex == -1) return; 75 | rebuild.run(); // rebuild schematics list 76 | 77 | Vec2 offset = selection.vertices[lastIndex].cpy().setLength(HexSelection.size * 1.5f); 78 | offset.sub(offset.x > 0 ? 0 : pane.getWidth(), offset.y > 0 ? 0 : offset.y == 0 ? pane.getHeight() / 2 : pane.getHeight()); 79 | pane.setTranslation(selection.x + offset.x - pane.x, selection.y + offset.y - pane.y); 80 | }).with(pane -> onShown = pane.getWidget()::clear); 81 | }); 82 | } 83 | 84 | public void show(int x, int y) { 85 | selection.setPosition(x, y); 86 | onShown.run(); // clear pane 87 | visible = true; 88 | } 89 | 90 | public void hide() { 91 | lastIndex = -1; 92 | visible = false; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /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.Building; 12 | import mindustry.gen.Icon; 13 | import mindustry.gen.Player; 14 | import mindustry.world.Tile; 15 | import mindustry.world.blocks.ConstructBlock.ConstructBuild; 16 | 17 | import static arc.Core.*; 18 | import static mindustry.Vars.*; 19 | import static scheme.SchemeVars.*; 20 | 21 | public class GammaAI extends AIController { 22 | 23 | public static final String tooltip = bundle.format("gamma.tooltip"); 24 | 25 | public static Updater move = Updater.none; 26 | public static Updater build = Updater.none; 27 | public static float range = 80f; 28 | public static float speed = 1f; 29 | 30 | public Player target; 31 | public Position cache; 32 | public Position aim; 33 | 34 | @Override 35 | public void updateUnit() { 36 | if (target == null || !target.isAdded()) return; 37 | 38 | move.update.get(this); 39 | build.update.get(this); 40 | 41 | player.boosting = target.boosting; 42 | aim = new Vec2(target.mouseX, target.mouseY); 43 | } 44 | 45 | public void draw() { 46 | if (target != null && target != player) render.drawPlans(target.unit(), build != Updater.destroy); 47 | } 48 | 49 | public void block(Tile tile, boolean breaking) { 50 | Building build = builds.get(tile); 51 | unit.addBuild(breaking 52 | ? new BuildPlan(tile.x, tile.y, build.rotation, build instanceof ConstructBuild c ? c.previous : build.block) 53 | : new BuildPlan(tile.x, tile.y)); 54 | } 55 | 56 | public void cache() { 57 | target = ai.players.get(); 58 | cache = target == player ? player.tileOn() : target; 59 | } 60 | 61 | public float speed() { 62 | return unit.speed() * speed / 100f; 63 | } 64 | 65 | public enum Updater { 66 | none(Icon.line, ai -> {}), 67 | circle(Icon.commandRally, ai -> { 68 | ai.circle(ai.cache, range, ai.speed()); 69 | ai.faceMovement(); 70 | ai.stopShooting(); 71 | }), 72 | cursor(Icon.diagonal, ai -> moveTo(ai, ai.aim)), 73 | follow(Icon.resize, ai -> moveTo(ai, ai.cache)), 74 | help(Icon.add, ai -> { 75 | if (ai.target.unit().plans.isEmpty() || !ai.target.unit().updateBuilding) return; 76 | ai.unit.addBuild(ai.target.unit().buildPlan()); 77 | }), 78 | destroy(Icon.hammer, ai -> {}), // works through events 79 | repair(Icon.wrench, ai -> { 80 | if (ai.target.team().data().plans.isEmpty() || !ai.target.unit().updateBuilding) return; 81 | BlockPlan plan = Seq.with(ai.target.team().data().plans).min(p -> ai.unit.dst(p.x * tilesize, p.y * tilesize)); 82 | ai.unit.addBuild(new BuildPlan(plan.x, plan.y, plan.rotation, plan.block, plan.config)); 83 | }); 84 | 85 | public final Drawable icon; 86 | public final Cons update; 87 | 88 | private Updater(Drawable icon, Cons update) { 89 | this.icon = icon; 90 | this.update = update; 91 | } 92 | 93 | public String tooltip() { 94 | return "@gamma." + name(); 95 | } 96 | 97 | private static void moveTo(GammaAI ai, Position pos) { 98 | ai.moveTo(pos, range / 3f); 99 | ai.unit.vel(ai.unit.vel().limit(ai.speed())); 100 | if (ai.unit.moving()) ai.faceMovement(); 101 | else ai.unit.lookAt(ai.aim); 102 | ai.unit.aim(ai.aim); 103 | ai.unit.controlWeapons(true, player.shooting = ai.target.shooting); 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /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 | import com.github.bsideup.jabel.Desugar; 24 | 25 | public class AISelectDialog extends ListDialog { 26 | 27 | public AIController ai; 28 | public List list; 29 | 30 | public AISelectDialog() { 31 | super("@select.ai"); 32 | 33 | hidden(() -> { 34 | if (ai instanceof GammaAI gamma) gamma.cache(); 35 | if (ai instanceof MissileAI missile) missile.shooter = player.unit(); 36 | }); 37 | 38 | Events.run(WorldLoadEvent.class, this::deselect); 39 | Events.on(BlockBuildBeginEvent.class, event -> { 40 | if (ai instanceof GammaAI gamma && GammaAI.build == Updater.destroy && event.unit.getPlayer() == gamma.target) 41 | gamma.block(event.tile, event.breaking); // crutch but no way 42 | }); 43 | 44 | Seq units = Seq.with( 45 | new UnitAI(null, null), 46 | new UnitAI(UnitTypes.mono, new NetMinerAI()), 47 | new UnitAI(UnitTypes.poly, new BuilderAI()), 48 | new UnitAI(UnitTypes.mega, new RepairAI()), 49 | new UnitAI(UnitTypes.oct, new DefenderAI()), 50 | new UnitAI(UnitTypes.crawler, new SuicideAI()), 51 | new UnitAI(UnitTypes.dagger, new GroundAI()), 52 | new UnitAI(UnitTypes.flare, new FlyingAI()), 53 | new UnitAI(UnitTypes.renale, new HugAI()), 54 | new UnitAI(content.unit(64), new MissileAI()), 55 | new UnitAI(UnitTypes.gamma, new GammaAI())); 56 | 57 | list = new List<>(units::each, UnitAI::name, UnitAI::icon, unit -> Pal.accent); 58 | players.selected = player; // do it once 59 | 60 | cont.table(cont -> { 61 | players.build(cont); 62 | players.onChanged = player -> list.set(units.peek()); 63 | 64 | players.pane.padRight(16f); 65 | 66 | list.build(cont); 67 | list.onChanged = unit -> ai = unit.ai; 68 | }).row(); 69 | 70 | cont.labelWrap("@select.ai.tooltip").labelAlign(2, 8).padTop(16f).width(400f).get().getStyle().fontColor = Color.lightGray; 71 | } 72 | 73 | public void update() { 74 | ai.unit(player.unit()); 75 | ai.updateUnit(); 76 | player.shooting = player.unit().isShooting; 77 | } 78 | 79 | public void select() { 80 | players.rebuild(); 81 | list.rebuild(); 82 | 83 | show(scene); // call Dialog.show bypassing ListDialog.show 84 | } 85 | 86 | public void deselect() { 87 | ai = null; 88 | } 89 | 90 | public void gotoppl(Player player) { 91 | players.set(player); 92 | ((GammaAI) ai).cache(); 93 | } 94 | 95 | public class UnitAI { 96 | public final UnitType type; 97 | public final AIController ai; 98 | 99 | public UnitAI(UnitType type, AIController ai) { 100 | this.type = type; 101 | this.ai = ai; 102 | } 103 | 104 | public String name() { 105 | return type != null ? type.localizedName : "@keycomb.reset_ai"; 106 | } 107 | 108 | public TextureRegion icon() { 109 | return type != null ? type.uiIcon : Icon.none.getRegion(); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /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)) 110 | ), 0.02f, x, y); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/java/scheme/ui/dialogs/RuleSetterDialog.java: -------------------------------------------------------------------------------- 1 | package scheme.ui.dialogs; 2 | 3 | import arc.scene.ui.*; 4 | import arc.scene.ui.layout.Table; 5 | import mindustry.Vars; 6 | import mindustry.game.Rules; 7 | import mindustry.gen.Call; 8 | import mindustry.ui.Styles; 9 | import mindustry.ui.dialogs.BaseDialog; 10 | import scheme.SchemeVars; 11 | 12 | import java.lang.reflect.Field; 13 | 14 | import static arc.Core.settings; 15 | import static scheme.SchemeVars.admins; 16 | import static scheme.tools.MessageQueue.send; 17 | import static scheme.ui.dialogs.AdminsConfigDialog.getTools; 18 | 19 | public class RuleSetterDialog extends BaseDialog { 20 | 21 | public static Table createRulesTable() { 22 | Table table = new Table(); 23 | table.top().defaults().pad(4); 24 | 25 | Rules rules = Vars.state.rules; 26 | 27 | table.add(new Label("Rule", Styles.outlineLabel)).left().padRight(20); 28 | table.add(new Label("Boolean", Styles.outlineLabel)).left().row(); 29 | 30 | for (Field field : Rules.class.getDeclaredFields()) { 31 | if (field.getType() != boolean.class) continue; 32 | field.setAccessible(true); 33 | 34 | String name = field.getName(); 35 | boolean value; 36 | try { 37 | value = field.getBoolean(rules); 38 | } catch (IllegalAccessException e) { 39 | continue; 40 | } 41 | table.add(new Label(name, Styles.outlineLabel)).left().padRight(20); 42 | CheckBox cb = new CheckBox(""); 43 | cb.setChecked(value); 44 | cb.changed(() -> { 45 | boolean val = cb.isChecked(); 46 | 47 | admins.manageRuleBool(val, name); 48 | }); 49 | 50 | 51 | table.add(cb).left().row(); 52 | } 53 | 54 | return table; 55 | } 56 | 57 | public static Table createRulesTableInt() { 58 | Table table = new Table(); 59 | table.top().defaults().pad(4); 60 | 61 | Rules rules = Vars.state.rules; 62 | 63 | table.add(new Label("Rule", Styles.outlineLabel)).left().padRight(20); 64 | table.add(new Label("Int/Float", Styles.outlineLabel)).left().row(); 65 | 66 | for (Field field : Rules.class.getDeclaredFields()) { 67 | if (field.getType() != float.class && field.getType() != int.class) continue; 68 | field.setAccessible(true); 69 | 70 | String name = field.getName(); 71 | float value; 72 | try { 73 | if(field.getType() == float.class) { 74 | value = field.getFloat(rules); 75 | } else { 76 | // int -> float 77 | value = (float) field.getInt(rules); 78 | } 79 | } catch(IllegalAccessException e) { 80 | continue; 81 | } 82 | table.add(new Label(name, Styles.outlineLabel)).left().padRight(20); 83 | TextField cb = new TextField(); 84 | cb.setText(String.valueOf(value)); 85 | cb.changed(() -> { 86 | String val = cb.getText(); 87 | 88 | admins.manageRuleStr(val, name); 89 | }); 90 | 91 | table.add(cb).left().row(); 92 | } 93 | 94 | return table; 95 | } 96 | 97 | public RuleSetterDialog(){ 98 | super("Rule Setter"); 99 | 100 | addCloseButton(); 101 | } 102 | 103 | @Override 104 | public Dialog show() { 105 | super.show(); 106 | cont.clear(); 107 | Table inner = new Table(); 108 | inner.add(createRulesTable()).growX().row(); 109 | ScrollPane pane = new ScrollPane(inner, Styles.defaultPane); 110 | cont.add(pane).left().grow(); 111 | 112 | Table inner2 = new Table(); 113 | inner2.add(createRulesTableInt()).growX().row(); 114 | ScrollPane pane2 = new ScrollPane(inner2, Styles.defaultPane); 115 | cont.add(pane2).right().grow(); 116 | return null; 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /src/java/scheme/claj/client/ClajLink.java: -------------------------------------------------------------------------------- 1 | package scheme.claj.client; 2 | 3 | import java.net.URI; 4 | 5 | import arc.util.serialization.Base64Coder; 6 | 7 | 8 | /** 9 | * CLaJ links (for this version) are URI, and formatted like that: {@code claj://host:port/room-id} 10 | * or {@code host:port/room-id}.
11 | * - {@code 'claj://'} is the protocol name (this part is optional).
12 | * - {@code 'host'} is the host server.
13 | * - {@code 'port'} is the server port to connect to.
14 | * - {@code 'room-id'} is a base64 (url safe) encoded {@code long}, generated by the server and 15 | * given to the client that created the room. 16 | * 17 | * @implNote parsing without protocol scheme doesn't works. 18 | */ 19 | public class ClajLink { 20 | public static final String UriScheme = "claj"; 21 | 22 | public final URI uri; 23 | public final String host; 24 | public final int port; 25 | public final long roomId; 26 | public final String encodedRoomId; 27 | 28 | public ClajLink(String host, int port, String roomId) { 29 | if (host == null || host.isEmpty()) throw new IllegalArgumentException("Missing host"); 30 | if (port == -1) throw new IllegalArgumentException("Missing port"); 31 | if (roomId != null && roomId.startsWith("/")) roomId = roomId.substring(1); 32 | if (roomId == null || roomId.isEmpty()) throw new IllegalArgumentException("Missing room id"); 33 | 34 | this.host = host; 35 | this.port = port; 36 | try { this.roomId = bytesToLong(Base64Coder.decode(roomId, Base64Coder.urlsafeMap)); } 37 | catch (Exception e) { throw /*e;*/new IllegalArgumentException("Invalid room id"); } 38 | this.encodedRoomId = roomId; 39 | 40 | try { uri = new URI(UriScheme, null, host, port, '/'+encodedRoomId, null, null); } 41 | // This error can only happen when the host is invalid 42 | catch (java.net.URISyntaxException e) { throw new IllegalArgumentException("Invalid host"); } 43 | } 44 | 45 | public ClajLink(String host, int port, long roomId) { 46 | if (host == null || host.isEmpty()) throw new IllegalArgumentException("Missing host"); 47 | if (port == -1) throw new IllegalArgumentException("Missing port"); 48 | 49 | this.host = host; 50 | this.port = port; 51 | this.roomId = roomId; 52 | this.encodedRoomId = new String(Base64Coder.encode(longToBytes(roomId), Base64Coder.urlsafeMap)); 53 | 54 | try { uri = new URI(UriScheme, null, host, port, '/'+encodedRoomId, null, null); } 55 | // This error can only happen when the host is invalid 56 | catch (java.net.URISyntaxException e) { throw new IllegalArgumentException("Invalid host"); } 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return uri.toString(); 62 | } 63 | 64 | /** @throws IllegalArgumentException if the link is invalid */ 65 | public static ClajLink fromString(String link) { 66 | // if (!link.contains("://") && !link.startsWith(UriScheme+"://")) link = UriScheme+"://"+link; 67 | if (link.startsWith(UriScheme) && 68 | (!link.startsWith(UriScheme+"://") || link.length() == (UriScheme+"://").length())) 69 | throw new IllegalArgumentException("Missing host"); 70 | 71 | URI uri; 72 | try { uri = URI.create(link); } 73 | catch (IllegalArgumentException e) { 74 | String cause = e.getLocalizedMessage(); 75 | int semicolon = cause.indexOf(':'); 76 | if (semicolon == -1) throw e; 77 | else throw new IllegalArgumentException(cause.substring(0, semicolon), e); 78 | }; 79 | 80 | if (uri.isAbsolute() && !uri.getScheme().equals(UriScheme)) 81 | throw new IllegalArgumentException("Not a CLaJ link"); 82 | 83 | return new ClajLink(uri.getHost(), uri.getPort(), uri.getPath()); 84 | } 85 | 86 | 87 | /** Damn, why there are no native way to convert a long to a byte array? */ 88 | private static byte[] longToBytes(long l) { 89 | byte[] result = new byte[Long.BYTES]; 90 | for (int i=Long.BYTES-1; i>=0; i--) { 91 | result[i] = (byte)(l & 0xFF); 92 | l >>= 8; 93 | } 94 | return result; 95 | } 96 | 97 | /** Damn, why there are no native way to convert a bytes array to a long? */ 98 | private static long bytesToLong(final byte[] b) { 99 | if (b.length != Long.BYTES) throw new IndexOutOfBoundsException("must be " + Long.BYTES + " bytes"); 100 | long result = 0; 101 | for (int i=0; i { 34 | send("unit", unit.id, "#" + target.id); 35 | units.refresh(); 36 | }); 37 | } 38 | 39 | public void spawnUnits() { 40 | if (unusable()) return; 41 | unit.select(true, false, true, (target, team, unit, amount) -> { 42 | if (amount == 0f) { 43 | send("despawn"); 44 | return; 45 | } 46 | 47 | send("spawn", unit.id, amount.intValue(), team.id); 48 | units.refresh(); 49 | }); 50 | } 51 | 52 | public void manageEffect() { 53 | if (unusable()) return; 54 | effect.select(true, true, false, (target, team, effect, amount) -> send("effect", effect.id, amount.intValue() / 60, "#" + target.id)); 55 | } 56 | 57 | public void manageItem() { 58 | if (unusable()) return; 59 | item.select(true, false, true, (target, team, item, amount) -> send("give", item.id, amount.intValue(), team.id)); 60 | } 61 | 62 | public void manageTeam() { 63 | if (unusable()) return; 64 | team.select((target, team) -> { 65 | if (team != null) { 66 | RainbowTeam.remove(target); 67 | send("team", team.id, "#" + target.id); 68 | } else 69 | RainbowTeam.add(target, t -> send("team", t.id, "#" + target.id)); 70 | }); 71 | } 72 | 73 | public void placeCore() { 74 | if (unusable()) return; 75 | if (player.buildOn() instanceof CoreBuild) 76 | sendPacket("fill", "null 0 null", player.tileX(), player.tileY(), 1, 1); 77 | else send("core"); 78 | } 79 | 80 | public void despawn(Player target) { 81 | if (unusable()) return; 82 | send("despawn", "#" + target.id); 83 | } 84 | 85 | public void teleport(Position pos) { 86 | if (unusable()) return; 87 | send("tp", (int) pos.getX() / tilesize, (int) pos.getY() / tilesize); 88 | } 89 | 90 | public void fill(int sx, int sy, int ex, int ey) { 91 | if (unusable()) return; 92 | //Darkdustry is going fuck itself 93 | } 94 | 95 | public void brush(int x, int y, int radius) { 96 | if (unusable()) return; 97 | //Darkdustry is going fuck itself 98 | } 99 | 100 | public void flush(Seq plans) { 101 | if (unusable()) return; 102 | ui.showInfoFade("@admins.notsupported"); 103 | } 104 | 105 | public boolean unusable() { 106 | boolean admin = !player.admin && !settings.getBool("adminsalways"); 107 | if (!settings.getBool("adminsenabled")) { 108 | ui.showInfoFade(disabled); 109 | return true; 110 | } else if (admin) ui.showInfoFade("@admins.notanadmin"); 111 | return admin; // darkness was be here 112 | } 113 | 114 | private static void send(String command, Object... args) { 115 | StringBuilder message = new StringBuilder(netServer.clientCommands.getPrefix()).append(command); 116 | for (var arg : args) message.append(" ").append(arg); 117 | MessageQueue.send(message.toString()); 118 | } 119 | 120 | private static void sendPacket(String command, Object... args) { 121 | StringBuilder message = new StringBuilder(); 122 | for (var arg : args) message.append(arg).append(" "); 123 | Call.serverPacketReliable(command, message.toString()); 124 | } 125 | 126 | private static String id(Block block) { 127 | return block == null ? "null" : String.valueOf(block.id); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/java/scheme/claj/client/Claj.java: -------------------------------------------------------------------------------- 1 | package scheme.claj.client; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.util.concurrent.ExecutorService; 5 | 6 | import arc.Events; 7 | import arc.func.Cons; 8 | import arc.net.Client; 9 | import arc.util.Threads; 10 | import arc.util.Time; 11 | 12 | import mindustry.Vars; 13 | import mindustry.game.EventType; 14 | 15 | 16 | public class Claj { 17 | static { 18 | // Pretty difficult to know when the player quits the game, there is no event... 19 | Vars.ui.paused.hidden(() -> { 20 | arc.util.Timer.schedule(() -> { 21 | if (!Vars.net.active() || Vars.state.isMenu()) closeRoom(); 22 | }, 1f); 23 | }); 24 | Events.run(EventType.HostEvent.class, Claj::closeRoom); 25 | Events.run(EventType.ClientPreConnectEvent.class, Claj::closeRoom); 26 | Events.run(EventType.DisposeEvent.class, () -> { 27 | disposeRoom(); 28 | disposePinger(); 29 | }); 30 | } 31 | 32 | private static ClajProxy room; 33 | private static Client pinger; 34 | private static ExecutorService worker = Threads.unboundedExecutor("CLaJ Worker", 1); 35 | private static arc.net.NetSerializer tmpSerializer; 36 | private static ByteBuffer tmpBuffer = ByteBuffer.allocate(16);// we only need 10 bytes for the room join packet 37 | private static Thread roomThread, pingerThread; 38 | 39 | public static boolean isRoomClosed() { 40 | return room == null || !room.isConnected(); 41 | } 42 | 43 | /** @apiNote async operation */ 44 | public static void createRoom(String ip, int port, Cons done, Cons failed, 45 | Runnable disconnected) { 46 | if (room == null || roomThread == null || !roomThread.isAlive()) 47 | roomThread = Threads.daemon("CLaJ Proxy", room = new ClajProxy()); 48 | 49 | worker.submit(() -> { 50 | try { 51 | if (room.isConnected()) throw new IllegalStateException("Room is already created, please close it before."); 52 | room.connect(ip, port, id -> done.get(new ClajLink(ip, port, id)), disconnected); 53 | } catch (Throwable e) { failed.get(e); } 54 | }); 55 | } 56 | 57 | /** Just close the room connection, doesn't delete it */ 58 | public static void closeRoom() { 59 | if (room != null) room.closeRoom(); 60 | } 61 | 62 | /** Delete properly the room */ 63 | public static void disposeRoom() { 64 | if (room != null) { 65 | room.stop(); 66 | try { roomThread.join(1000); } 67 | catch (Exception ignored) {} 68 | try { room.dispose(); } 69 | catch (Exception ignored) {} 70 | roomThread = null; 71 | room = null; 72 | } 73 | } 74 | 75 | public static void joinRoom(ClajLink link, Runnable success) { 76 | if (link == null) return; 77 | 78 | Vars.logic.reset(); 79 | Vars.net.reset(); 80 | 81 | Vars.netClient.beginConnecting(); 82 | Vars.net.connect(link.host, link.port, () -> { 83 | if (!Vars.net.client()) return; 84 | if (tmpSerializer == null) tmpSerializer = new ClajProxy.Serializer(); 85 | 86 | // We need to serialize the packet manually 87 | tmpBuffer.clear(); 88 | ClajPackets.RoomJoinPacket p = new ClajPackets.RoomJoinPacket(); 89 | p.roomId = link.roomId; 90 | tmpSerializer.write(tmpBuffer, p); 91 | tmpBuffer.limit(tmpBuffer.position()).position(0); 92 | Vars.net.send(tmpBuffer, true); 93 | 94 | success.run(); 95 | }); 96 | } 97 | 98 | /** @apiNote async operation but blocking new tasks if a ping is already in progress */ 99 | public static void pingHost(String ip, int port, Cons success, Cons failed) { 100 | if (tmpSerializer == null) tmpSerializer = new ClajProxy.Serializer(); 101 | if (pinger == null || pingerThread == null || !pingerThread.isAlive()) 102 | pingerThread = Threads.daemon("CLaJ Pinger", pinger = new Client(8192, 8192, tmpSerializer)); 103 | 104 | worker.submit(() -> { 105 | synchronized (pingerThread) { 106 | long time = Time.millis(); 107 | try { 108 | // Connect successfully is enough. 109 | pinger.connect(2000, ip, port); 110 | time = Time.timeSinceMillis(time); 111 | pinger.close(); 112 | success.get(time); 113 | } catch (Exception e) { failed.get(e); } 114 | } 115 | }); 116 | } 117 | 118 | public static void disposePinger() { 119 | if (pinger != null) { 120 | pinger.stop(); 121 | try { pingerThread.join(1000); } 122 | catch (Exception ignored) {} 123 | try { pinger.dispose(); } 124 | catch (Exception ignored) {} 125 | pingerThread = null; 126 | pinger = null; 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/java/scheme/ui/dialogs/TileSelectDialog.java: -------------------------------------------------------------------------------- 1 | package scheme.ui.dialogs; 2 | 3 | import arc.func.*; 4 | import arc.graphics.g2d.TextureRegion; 5 | import arc.scene.style.TextureRegionDrawable; 6 | import arc.scene.ui.ScrollPane; 7 | import arc.scene.ui.layout.Table; 8 | import arc.struct.Seq; 9 | import mindustry.content.Blocks; 10 | import mindustry.gen.Building; 11 | import mindustry.gen.Icon; 12 | import mindustry.graphics.Pal; 13 | import mindustry.ui.Styles; 14 | import mindustry.ui.dialogs.BaseDialog; 15 | import mindustry.world.Block; 16 | import mindustry.world.Build; 17 | import mindustry.world.Tile; 18 | import mindustry.world.blocks.defense.Wall; 19 | import mindustry.world.blocks.environment.Floor; 20 | import mindustry.world.blocks.environment.OverlayFloor; 21 | import mindustry.world.blocks.environment.Prop; 22 | import mindustry.world.blocks.power.Battery; 23 | import scheme.ui.List; 24 | 25 | import static arc.Core.*; 26 | import static mindustry.Vars.*; 27 | import static scheme.SchemeVars.*; 28 | 29 | import com.github.bsideup.jabel.Desugar; 30 | 31 | public class TileSelectDialog extends BaseDialog { 32 | 33 | public static final int row = mobile ? 8 : 15; 34 | public static final float size = mobile ? 54f : 64f; 35 | 36 | public Table blocks = new Table(); 37 | 38 | public Block floor; 39 | public Block block; 40 | public Block overlay; 41 | public Block building; 42 | public List list; 43 | 44 | public TileSelectDialog() { 45 | super("@select.tile"); 46 | addCloseButton(); 47 | 48 | Seq folders = Seq.with( 49 | new Folder("select.floor", () -> floor, b -> b instanceof Floor && !(b instanceof OverlayFloor), b -> floor = b), 50 | new Folder("select.block", () -> block, b -> b instanceof Prop, b -> block = b), 51 | new Folder("select.overlay", () -> overlay, b -> b instanceof OverlayFloor, b -> overlay = b), 52 | new Folder("select.building", () -> building, b -> b instanceof Block, b -> building = b)); 53 | 54 | list = new List<>(folders::each, Folder::name, Folder::icon, folder -> Pal.accent); 55 | list.onChanged = this::rebuild; 56 | list.set(folders.first()); 57 | list.rebuild(); 58 | 59 | list.build(cont); 60 | cont.add(blocks).growX(); 61 | cont.table().width(288f); 62 | } 63 | 64 | public void rebuild(Folder folder) { 65 | blocks.clear(); 66 | Table inner = new Table(); 67 | inner.defaults().size(size); 68 | 69 | inner.button(Icon.none, () -> folder.callback(null)); 70 | inner.button(Icon.line, () -> folder.callback(Blocks.air)); 71 | 72 | content.blocks().each(folder::pred, block -> { 73 | TextureRegionDrawable drawable = new TextureRegionDrawable(block.uiIcon); 74 | inner.button(drawable, () -> folder.callback(block)); 75 | 76 | if (inner.getChildren().size % row == 0) inner.row(); 77 | }); 78 | ScrollPane pane = new ScrollPane(inner, Styles.defaultPane); 79 | blocks.add(pane).grow(); 80 | } 81 | 82 | public void select(Cons4 callback) { 83 | callback.get(floor != null ? floor.asFloor() : null, block, overlay != null ? overlay.asFloor() : null, building); 84 | } 85 | 86 | public void select(int x, int y) { 87 | Tile tile = world.tile(x, y); 88 | if (tile == null) return; 89 | 90 | floor = tile.floor(); 91 | block = tile.build == null ? tile.block() : Blocks.air; 92 | overlay = tile.overlay(); 93 | building = tile.build == null ? tile.block() : Blocks.air; 94 | list.rebuild(); 95 | } 96 | 97 | public class Folder { 98 | public final String name; 99 | public final Prov block; 100 | public final Boolf pred; 101 | public final Cons callback; 102 | 103 | public Folder(String name, Prov block, Boolf pred, Cons callback) { 104 | this.name = name; 105 | this.block = block; 106 | this.pred = pred; 107 | this.callback = callback; 108 | } 109 | 110 | public String name() { 111 | Block selected = block.get(); 112 | return bundle.format(name, selected == null ? bundle.get("none") : selected.localizedName); 113 | } 114 | 115 | public TextureRegion icon() { 116 | Block selected = block.get(); 117 | if (selected == null) return Icon.none.getRegion(); 118 | if (selected == Blocks.air) return Icon.line.getRegion(); 119 | return selected.uiIcon; 120 | } 121 | 122 | public boolean pred(Block block) { 123 | return pred.get(block) && block.id > 1; 124 | } 125 | 126 | public void callback(Block block) { 127 | callback.get(block); 128 | tile.list.rebuild(); 129 | } 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /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.mod.Scripts; 11 | import mindustry.type.Item; 12 | import mindustry.ui.CoreItemsDisplay; 13 | import mindustry.world.Tile; 14 | import mindustry.world.blocks.distribution.Router; 15 | import mindustry.world.blocks.logic.LogicDisplay; 16 | import scheme.claj.client.dialogs.*; 17 | import scheme.moded.ModedGlyphLayout; 18 | import scheme.moded.ModedSchematics; 19 | import scheme.tools.MessageQueue; 20 | import scheme.tools.RainbowTeam; 21 | import scheme.ui.MapResizeFix; 22 | 23 | import static arc.Core.*; 24 | import static mindustry.Vars.*; 25 | import static scheme.SchemeVars.*; 26 | 27 | public class Main extends Mod { 28 | 29 | private static String version; 30 | 31 | public Main() { 32 | // well, after the 136th build, it became much easier 33 | maxSchematicSize = 512; 34 | 35 | // mod reimported through mods dialog 36 | if (schematics.getClass().getSimpleName().startsWith("Moded")) return; 37 | 38 | assets.load(schematics = m_schematics = new ModedSchematics()); 39 | assets.unload(Schematics.class.getSimpleName()); // prevent dual loading 40 | } 41 | 42 | @Override 43 | public void init() { 44 | // Инициализация Scheme 45 | ServerIntegration.load(); 46 | ModedGlyphLayout.load(); 47 | SchemeVars.load(); 48 | SchemeUpdater.load(); 49 | MapResizeFix.load(); 50 | MessageQueue.load(); 51 | RainbowTeam.load(); 52 | 53 | ui.schematics = schemas; 54 | ui.listfrag = listfrag; 55 | 56 | units.load(); 57 | builds.load(); 58 | 59 | m_settings.apply(); 60 | 61 | hudfrag.build(ui.hudGroup); 62 | listfrag.build(ui.hudGroup); 63 | shortfrag.build(ui.hudGroup); 64 | corefrag.build(ui.hudGroup); 65 | 66 | control.setInput(m_input.asHandler()); 67 | renderer.addEnvRenderer(0, render::draw); 68 | 69 | if (m_schematics.requiresDialog) ui.showOkText("@rename.name", "@rename.text", () -> {}); 70 | if (settings.getBool("welcome")) ui.showOkText("@welcome.name", "@welcome.text", () -> {}); 71 | if (settings.getBool("check4update")) SchemeUpdater.check(); 72 | 73 | if (SchemeUpdater.installed("miner-tools")) { 74 | ui.showOkText("@incompatible.name", "@incompatible.text", () -> {}); 75 | ui.hudGroup.fill(cont -> { 76 | cont.visible = false; 77 | cont.add(new CoreItemsDisplay()); 78 | }); 79 | } 80 | 81 | try { 82 | Scripts scripts = mods.getScripts(); 83 | scripts.context.evaluateReader(scripts.scope, SchemeUpdater.script().reader(), "main.js", 0); 84 | log("Added constant variables to developer console."); 85 | } catch (Throwable e) { 86 | error(e); 87 | } 88 | 89 | Blocks.distributor.buildType = () -> ((Router) Blocks.distributor).new RouterBuild() { 90 | @Override 91 | public boolean canControl() { return true; } 92 | 93 | @Override 94 | public Building getTileTarget(Item item, Tile from, boolean set) { 95 | Building target = super.getTileTarget(item, from, set); 96 | 97 | if (unit != null && isControlled() && unit.isShooting()) { 98 | float angle = angleTo(unit.aimX(), unit.aimY()); 99 | Tmp.v1.set(block.size * tilesize, 0f).rotate(angle).add(this); 100 | 101 | Building other = world.buildWorld(Tmp.v1.x, Tmp.v1.y); 102 | if (other != null && other.acceptItem(this, item)) target = other; 103 | } 104 | 105 | return target; 106 | } 107 | }; 108 | 109 | content.blocks().each(block -> block instanceof LogicDisplay, block -> block.buildType = () -> ((LogicDisplay) block).new LogicDisplayBuild() { 110 | @Override 111 | public void draw() { 112 | super.draw(); 113 | if (render.borderless) Draw.draw(Draw.z(), () -> { 114 | Draw.rect(Draw.wrap(buffer.getTexture()), x, y, block.region.width * Draw.scl, -block.region.height * Draw.scl); 115 | }); 116 | } 117 | }); 118 | 119 | try { 120 | new JoinViaClajDialog(); 121 | new CreateClajRoomDialog(); 122 | log("Claj dialogs initialized."); 123 | } catch (Throwable e) { 124 | error(new RuntimeException("Failed to initialize Claj dialogs", e)); 125 | } 126 | } 127 | 128 | public static void log(String info) { 129 | app.post(() -> Log.infoTag("Scheme", info)); 130 | } 131 | 132 | public static void error(Throwable info) { 133 | app.post(() -> Log.err("Scheme", info)); 134 | } 135 | 136 | public static void copy(String text) { 137 | if (text == null) return; 138 | app.setClipboardText(text); 139 | ui.showInfoFade("@copied"); 140 | } 141 | /** @return the mod version, using this class, or {@code null} if mod is not loaded yet. */ 142 | public static String getVersion() {return "2.3.3";} 143 | } 144 | -------------------------------------------------------------------------------- /src/java/scheme/claj/client/ClajPackets.java: -------------------------------------------------------------------------------- 1 | package scheme.claj.client; 2 | 3 | import arc.func.Prov; 4 | import arc.net.DcReason; 5 | import arc.struct.ArrayMap; 6 | import arc.util.ArcRuntimeException; 7 | import arc.util.io.ByteBufferInput; 8 | import arc.util.io.ByteBufferOutput; 9 | 10 | 11 | /** @implNote This class must be the same for client and server. */ 12 | public class ClajPackets { 13 | /** Identifier for CLaJ packets */ 14 | public static final byte id = -4; /*doesn't uses other claj packet identifier to avoid problems*/ 15 | 16 | protected static final ArrayMap, Prov> packets = new ArrayMap<>(); 17 | 18 | static { 19 | register(ConnectionPacketWrapPacket::new); 20 | register(ConnectionClosedPacket::new); 21 | register(ConnectionJoinPacket::new); 22 | register(ConnectionIdlingPacket::new); 23 | register(RoomCreateRequestPacket::new); 24 | register(RoomCloseRequestPacket::new); 25 | register(RoomLinkPacket::new); 26 | register(RoomJoinPacket::new); 27 | register(ClajMessagePacket::new); 28 | } 29 | 30 | 31 | public static void register(Prov cons) { 32 | packets.put(cons.get().getClass(), cons); 33 | } 34 | 35 | public static byte getId(Packet packet) { 36 | int id = packets.indexOfKey(packet.getClass()); 37 | if(id == -1) throw new ArcRuntimeException("Unknown packet type: " + packet.getClass()); 38 | return (byte)id; 39 | } 40 | 41 | @SuppressWarnings("unchecked") 42 | public static T newPacket(byte id) { 43 | if (id < 0 || id >= packets.size) throw new ArcRuntimeException("Unknown packet id: " + id); 44 | return ((Prov)packets.getValueAt(id)).get(); 45 | } 46 | 47 | /****************************/ 48 | 49 | public static abstract class Packet { 50 | public void read(ByteBufferInput read) {}; 51 | public void write(ByteBufferOutput write) {}; 52 | } 53 | 54 | public static abstract class ConnectionWrapperPacket extends Packet { 55 | public int conID = -1; 56 | 57 | public void read(ByteBufferInput read) { 58 | conID = read.readInt(); 59 | read0(read); 60 | } 61 | 62 | public void write(ByteBufferOutput write) { 63 | write.writeInt(conID); 64 | write0(write); 65 | } 66 | 67 | protected void read0(ByteBufferInput read) {}; 68 | protected void write0(ByteBufferOutput write) {}; 69 | } 70 | 71 | /** Special packet for connection packet wrapping. */ 72 | public static class ConnectionPacketWrapPacket extends ConnectionWrapperPacket { 73 | /** serialization will be done by the proxy */ 74 | public Object object; 75 | /** only for server usage */ 76 | public java.nio.ByteBuffer buffer; 77 | 78 | public boolean isTCP; 79 | 80 | protected void read0(ByteBufferInput read) { 81 | isTCP = read.readBoolean(); 82 | } 83 | 84 | protected void write0(ByteBufferOutput write) { 85 | write.writeBoolean(isTCP); 86 | } 87 | } 88 | 89 | public static class ConnectionClosedPacket extends ConnectionWrapperPacket { 90 | private static DcReason[] reasons = DcReason.values(); 91 | 92 | public DcReason reason; 93 | 94 | protected void read0(ByteBufferInput read) { 95 | byte b = read.readByte(); 96 | reason = b < 0 || b >= reasons.length ? DcReason.error : reasons[b]; 97 | } 98 | 99 | protected void write0(ByteBufferOutput write) { 100 | write.writeByte((byte)reason.ordinal()); 101 | } 102 | } 103 | 104 | public static class ConnectionJoinPacket extends ConnectionWrapperPacket { 105 | public long roomId = -1; 106 | 107 | protected void read0(ByteBufferInput read) { 108 | roomId = read.readLong(); 109 | } 110 | 111 | protected void write0(ByteBufferOutput write) { 112 | write.writeLong(roomId); 113 | } 114 | } 115 | 116 | public static class ConnectionIdlingPacket extends ConnectionWrapperPacket { 117 | } 118 | 119 | public static class RoomCreateRequestPacket extends Packet { 120 | public String version; 121 | 122 | public void read(ByteBufferInput read) { 123 | if (read.buffer.hasRemaining()) { 124 | try { version = read.readUTF(); } 125 | catch (Exception e) { throw new RuntimeException(e); } 126 | } 127 | } 128 | 129 | public void write(ByteBufferOutput write) { 130 | try { write.writeUTF(version); } 131 | catch (Exception e) { throw new RuntimeException(e); } 132 | } 133 | } 134 | 135 | public static class RoomCloseRequestPacket extends Packet { 136 | } 137 | 138 | public static class RoomLinkPacket extends Packet { 139 | public long roomId = -1; 140 | 141 | public void read(ByteBufferInput read) { 142 | roomId = read.readLong(); 143 | } 144 | 145 | public void write(ByteBufferOutput write) { 146 | write.writeLong(roomId); 147 | } 148 | } 149 | 150 | public static class RoomJoinPacket extends RoomLinkPacket { 151 | } 152 | 153 | public static class ClajMessagePacket extends Packet { 154 | public String message; 155 | 156 | public void read(ByteBufferInput read) { 157 | try { message = read.readUTF(); } 158 | catch (Exception e) { throw new RuntimeException(e); } 159 | } 160 | 161 | public void write(ByteBufferOutput write) { 162 | try { write.writeUTF(message); } 163 | catch (Exception e) { throw new RuntimeException(e); } 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/java/scheme/SchemeVars.java: -------------------------------------------------------------------------------- 1 | package scheme; 2 | 3 | import arc.Events; 4 | import arc.graphics.Color; 5 | import arc.graphics.Texture; 6 | import arc.graphics.Texture.TextureFilter; 7 | import arc.graphics.g2d.Draw; 8 | import arc.graphics.g2d.TextureRegion; 9 | import arc.graphics.gl.FrameBuffer; 10 | import arc.struct.Seq; 11 | import arc.util.Log; 12 | import mindustry.content.StatusEffects; 13 | import mindustry.core.UI; 14 | import mindustry.game.EventType; 15 | import mindustry.graphics.Pal; 16 | import mindustry.type.Item; 17 | import mindustry.type.StatusEffect; 18 | import mindustry.type.UnitType; 19 | import scheme.moded.*; 20 | import scheme.tools.*; 21 | import scheme.tools.admins.AdminsTools; 22 | import scheme.ui.*; 23 | import scheme.ui.dialogs.*; 24 | 25 | import static arc.Core.*; 26 | import static mindustry.Vars.*; 27 | 28 | public class SchemeVars { 29 | 30 | public static ModedSchematics m_schematics; 31 | public static ModedInputHandler m_input; 32 | 33 | public static RuleSetterDialog rulesetter; 34 | public static AdminsTools admins; 35 | public static RendererTools render; 36 | public static BuildingTools build; 37 | public static UnitsCache units; 38 | public static BuildsCache builds; 39 | 40 | public static AdminsConfigDialog adminscfg; 41 | public static RendererConfigDialog rendercfg; 42 | 43 | public static AISelectDialog ai; 44 | public static TeamSelectDialog team; 45 | public static TileSelectDialog tile; 46 | public static TagSelectDialog tag; 47 | 48 | public static ContentSelectDialog unit; 49 | public static ContentSelectDialog effect; 50 | public static ContentSelectDialog item; 51 | 52 | public static SettingsMenuDialog m_settings; 53 | public static SchemasDialog schemas; 54 | public static ImageParserDialog parser; 55 | public static WaveApproachingDialog approaching; 56 | 57 | public static HudFragment hudfrag; 58 | public static PlayerListFragment listfrag; 59 | public static ShortcutFragment shortfrag; 60 | public static CoreInfoFragment corefrag; 61 | 62 | public static void load() { 63 | Events.on(EventType.ClientLoadEvent.class, e -> { 64 | try { 65 | TextureRegion base = atlas.find("scheme-size-status-invincible"); 66 | if (base == null) { 67 | Log.err("Region scheme-size-status-invincible not found in atlas!"); 68 | return; 69 | } 70 | 71 | FrameBuffer fb = new FrameBuffer(base.width, base.height); 72 | fb.begin(); 73 | 74 | Draw.proj().setOrtho(0, 0, base.width, base.height); 75 | Draw.reset(); 76 | 77 | for (int dx = -3; dx <= 3; dx++) { 78 | for (int dy = -3; dy <= 3; dy++) { 79 | if (Math.abs(dx) + Math.abs(dy) <= 3) { 80 | Draw.color(Pal.gray); 81 | Draw.rect(base, base.width / 2f + dx, base.height / 2f + dy, base.width, base.height); 82 | } 83 | } 84 | } 85 | 86 | Draw.color(Color.white); 87 | Draw.rect(base, base.width / 2f, base.height / 2f, base.width, base.height); 88 | Draw.flush(); 89 | 90 | fb.end(); 91 | 92 | Texture outlined = fb.getTexture(); 93 | outlined.setFilter(Texture.TextureFilter.linear); 94 | 95 | atlas.addRegion("status-invincible-ui", outlined, 0, 0, base.width, base.height); 96 | 97 | StatusEffects.invincible.uiIcon = atlas.find("status-invincible-ui"); 98 | 99 | fb.dispose(); 100 | } catch (Throwable ex) { 101 | Log.err("Failed to create invincible outlined icon", ex); 102 | } 103 | }); 104 | // m_schematics is created in Main to prevent dual loading 105 | m_input = mobile ? new ModedMobileInput() : new ModedDesktopInput(); 106 | 107 | rulesetter = new RuleSetterDialog(); 108 | admins = AdminsConfigDialog.getTools(); 109 | render = new RendererTools(); 110 | build = new BuildingTools(); 111 | units = new UnitsCache(); 112 | builds = new BuildsCache(); 113 | 114 | adminscfg = new AdminsConfigDialog(); 115 | rendercfg = new RendererConfigDialog(); 116 | 117 | ai = new AISelectDialog(); 118 | team = new TeamSelectDialog(); 119 | tile = new TileSelectDialog(); 120 | tag = new TagSelectDialog(); 121 | 122 | unit = new ContentSelectDialog<>("@select.unit", content.units(), 0, 100, 1, value -> { 123 | return value == 0 ? "@select.unit.clear" : bundle.format("select.units", value); 124 | }); 125 | effect = new ContentSelectDialog<>("@select.effect", content.statusEffects(), 0, 500 * 3600, 60, value -> { 126 | return value == 0 ? "@select.effect.clear" : bundle.format("select.seconds", value / 60f); 127 | }); 128 | item = new ContentSelectDialog<>("@select.item", content.items(), -1000000, 1000000, 500, value -> { 129 | return value == 0 ? "@select.item.clear" : bundle.format("select.items", UI.formatAmount(value.longValue())); 130 | }); 131 | 132 | m_settings = new SettingsMenuDialog(); 133 | schemas = new SchemasDialog(); 134 | parser = new ImageParserDialog(); 135 | approaching = new WaveApproachingDialog(); 136 | 137 | hudfrag = new HudFragment(); 138 | listfrag = new PlayerListFragment(); 139 | shortfrag = new ShortcutFragment(); 140 | corefrag = new CoreInfoFragment(); 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /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.Vars; 7 | import mindustry.content.Blocks; 8 | import mindustry.entities.units.BuildPlan; 9 | import mindustry.game.Rules; 10 | import mindustry.gen.Call; 11 | import mindustry.gen.Groups; 12 | import mindustry.gen.Player; 13 | import mindustry.world.Block; 14 | import mindustry.world.Tile; 15 | import mindustry.world.blocks.environment.Prop; 16 | import mindustry.world.blocks.storage.CoreBlock.CoreBuild; 17 | import scheme.tools.RainbowTeam; 18 | 19 | import java.lang.reflect.Field; 20 | 21 | import static arc.Core.*; 22 | import static mindustry.Vars.*; 23 | import static scheme.SchemeVars.*; 24 | 25 | public class Internal implements AdminsTools { 26 | 27 | public void manageRuleBool(boolean value, String name) { 28 | try { 29 | Field fiel = Rules.class.getField(name); 30 | fiel.setBoolean(Vars.state.rules, value); 31 | 32 | Call.setRules(Vars.state.rules); 33 | } catch(Exception e){ 34 | e.printStackTrace(); 35 | } 36 | } 37 | 38 | public void manageRuleStr(String value, String name) { 39 | try { 40 | Field fiel = Rules.class.getField(name); 41 | fiel.set(Vars.state.rules, value); 42 | 43 | Call.setRules(Vars.state.rules); 44 | } catch (Exception e) { 45 | e.printStackTrace(); 46 | } 47 | } 48 | 49 | public void manageUnit() { 50 | if (unusable()) return; 51 | unit.select(false, true, false, (target, team, unit, amount) -> { 52 | if (!canCreate(team, unit)) return; 53 | target.unit().spawnedByCore(true); 54 | target.unit(unit.spawn(team, target)); 55 | units.refresh(); 56 | }); 57 | } 58 | 59 | public void spawnUnits() { 60 | if (unusable()) return; 61 | unit.select(true, true, true, (target, team, unit, amount) -> { 62 | if (amount == 0f) { 63 | Groups.unit.each(u -> u.team == team && u.type == unit, u -> u.spawnedByCore(true)); 64 | return; 65 | } 66 | 67 | if (!canCreate(team, unit)) return; 68 | for (int i = 0; i < amount; i++) unit.spawn(team, target); 69 | units.refresh(); 70 | }); 71 | } 72 | 73 | public void manageEffect() { 74 | if (unusable()) return; 75 | effect.select(true, true, false, (target, team, effect, amount) -> { 76 | if (amount == 0f) target.unit().unapply(effect); 77 | else target.unit().apply(effect, amount); 78 | }); 79 | } 80 | 81 | public void manageItem() { 82 | if (unusable()) return; 83 | item.select(true, false, true, (target, team, item, amount) -> { 84 | if (!hasCore(team)) return; 85 | team.core().items.add(item, fixAmount(item, amount)); 86 | }); 87 | } 88 | 89 | public void manageTeam() { 90 | if (unusable()) return; 91 | team.select((target, team) -> { 92 | if (team != null) { 93 | RainbowTeam.remove(target); 94 | target.team(team); 95 | } else 96 | RainbowTeam.add(target, target::team); 97 | }); 98 | } 99 | 100 | public void placeCore() { 101 | if (unusable()) return; 102 | Tile tile = player.tileOn(); 103 | if (tile != null) tile.setNet(tile.build instanceof CoreBuild ? Blocks.air : Blocks.coreShard, player.team(), 0); 104 | } 105 | 106 | public void despawn(Player target) { 107 | if (unusable()) return; 108 | target.unit().spawnedByCore(true); 109 | target.clearUnit(); 110 | } 111 | 112 | public void teleport(Position pos) { 113 | player.unit().set(pos); // it's always available 114 | } 115 | 116 | public void fill(int sx, int sy, int ex, int ey) { 117 | if (unusable()) return; 118 | tile.select((floor, block, overlay, building) -> { 119 | for (int x = sx; x <= ex; x++) 120 | for (int y = sy; y <= ey; y++) 121 | edit(floor, block, overlay, building, x, y); 122 | }); 123 | } 124 | 125 | public void brush(int x, int y, int radius) { 126 | if (unusable()) return; 127 | tile.select((floor, block, overlay, building) -> Geometry.circle(x, y, radius, (cx, cy) -> edit(floor, block, overlay, building, cx, cy))); 128 | } 129 | 130 | public void flush(Seq plans) { 131 | plans.each(plan -> { 132 | if (plan.block.isFloor() && !plan.block.isOverlay()) 133 | edit(plan.block, null, null, null, plan.x, plan.y); 134 | else if (plan.block instanceof Prop) 135 | edit(null, plan.block, null, null, plan.x, plan.y); 136 | else if (plan.block.isOverlay()) 137 | edit(null, null, plan.block, null, plan.x, plan.y); 138 | else if (plan.block instanceof Block) 139 | edit(null, null, null, plan.block, plan.x, plan.y); 140 | }); 141 | } 142 | 143 | public boolean unusable() { 144 | boolean admin = net.client() && !settings.getBool("adminsalways"); 145 | if (!settings.getBool("adminsenabled")) { 146 | ui.showInfoFade(disabled); 147 | return true; 148 | } else if (admin) ui.showInfoFade(unabailable); 149 | return admin; 150 | } 151 | 152 | private static void edit(Block floor, Block block, Block overlay, Block building, int x, int y) { 153 | Tile tile = world.tile(x, y); 154 | if (tile == null) return; 155 | 156 | if ((floor != null && tile.floor() != floor) || (overlay != null && tile.overlay() != overlay)) 157 | tile.setFloorNet(floor == null ? tile.floor() : floor, overlay == null ? tile.overlay() : overlay); 158 | 159 | if (block != null && tile.block() != block) tile.setNet(block); 160 | if (building != null && tile.block() != building) tile.setNet(building, player.team(), 0); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /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.Building; 12 | import mindustry.gen.Groups; 13 | import mindustry.gen.Unit; 14 | import mindustry.graphics.Drawf; 15 | import mindustry.graphics.Layer; 16 | import mindustry.graphics.Pal; 17 | import mindustry.world.blocks.defense.turrets.BaseTurret.BaseTurretBuild; 18 | import mindustry.world.blocks.power.ImpactReactor; 19 | import mindustry.world.blocks.power.NuclearReactor; 20 | 21 | import static arc.Core.*; 22 | import static mindustry.Vars.*; 23 | import static scheme.SchemeVars.*; 24 | 25 | public class RendererTools { 26 | 27 | public Rect bounds = new Rect(); 28 | public boolean xray, grid, ruler, unitInfo, borderless, unitRadius, turretRadius, reactorRadius, overdriveRadius; 29 | 30 | public void draw() { 31 | camera.bounds(bounds); // do NOT use Tmp.r1 32 | int xwidth = (int) (bounds.x + bounds.width), yheigth = (int) (bounds.y + bounds.height); 33 | 34 | if (xray) builds.each(bounds, tile -> tile.floor().drawBase(tile)); 35 | 36 | if (grid) Draw.draw(Layer.blockUnder, () -> { 37 | Lines.stroke(1f, Pal.darkMetal); 38 | 39 | int sx = Mathf.round(bounds.x, tilesize) + 4; 40 | int sy = Mathf.round(bounds.y, tilesize) + 4; 41 | 42 | for (int x = sx; x < xwidth; x += tilesize) 43 | for (int y = sy - 2; y < yheigth; y += tilesize) 44 | Lines.line(x, y, x, y + 4); 45 | 46 | for (int y = sy; y < yheigth; y += tilesize) 47 | for (int x = sx - 2; x < xwidth; x += tilesize) 48 | Lines.line(x, y, x + 4, y); 49 | }); 50 | 51 | if (ruler) Draw.draw(Layer.legUnit, () -> { 52 | Lines.stroke(1f, Pal.accent); 53 | 54 | int x = Mathf.round(input.mouseWorldX() - 4, tilesize) + 4; 55 | int y = Mathf.round(input.mouseWorldY() - 4, tilesize) + 4; 56 | 57 | Lines.line(x, bounds.y, x, yheigth); 58 | Lines.line(x + tilesize, bounds.y, x + tilesize, yheigth); 59 | Lines.line(bounds.x, y, xwidth, y); 60 | Lines.line(bounds.x, y + tilesize, xwidth, y + tilesize); 61 | }); 62 | 63 | if (turretRadius) Draw.draw(Layer.overlayUI, () -> builds.turrets.each(BaseTurretBuild::drawSelect)); 64 | 65 | if (reactorRadius) Draw.draw(Layer.overlayUI, () -> { 66 | builds.nuclears.each(nuclear -> drawRadius(nuclear, ((NuclearReactor) nuclear.block).explosionRadius, Pal.thoriumPink)); 67 | builds.impacts.each(impact -> drawRadius(impact, ((ImpactReactor) impact.block).explosionRadius, Pal.meltdownHit)); 68 | }); 69 | 70 | if (overdriveRadius) Draw.draw(Layer.overlayUI, () -> builds.overdrives.each(Building::drawSelect)); 71 | 72 | Seq units = unitInfo || unitRadius ? Groups.unit.intersect(bounds.x, bounds.y, bounds.width, bounds.height) : null; 73 | 74 | if (unitInfo) Draw.draw(Layer.overlayUI, () -> units.each(unit -> unit != player.unit(), unit -> { 75 | if (unit.isPlayer()) { 76 | Tmp.v1.set(unit.aimX, unit.aimY).sub(unit).setLength(unit.hitSize); 77 | Lines.stroke(2f, unit.team.color); 78 | Lines.line(unit.x + Tmp.v1.x, unit.y + Tmp.v1.y, unit.aimX, unit.aimY); 79 | } 80 | 81 | drawBar(unit, 3f, Pal.darkishGray, 1f); 82 | drawBar(unit, 3f, Pal.health, Mathf.clamp(unit.healthf())); 83 | 84 | if (!state.rules.unitAmmo) return; 85 | 86 | drawBar(unit, -3f, unit.type.ammoType.color(), 1f); 87 | drawBar(unit, -3f, Pal.darkishGray, 1f - Mathf.clamp(unit.ammof())); 88 | })); 89 | 90 | if (unitRadius) Draw.draw(Layer.overlayUI, () -> units.each(unit -> Drawf.circles(unit.x, unit.y, unit.range(), unit.team.color))); 91 | 92 | // asynchrony requires sacrifice 93 | Draw.draw(Layer.blockUnder, Draw::reset); 94 | Draw.draw(Layer.legUnit, Draw::reset); 95 | Draw.draw(Layer.overlayUI, Draw::reset); 96 | } 97 | 98 | public void showUnits(boolean hide) { 99 | if (hide) units.cache(); 100 | else units.uncache(); 101 | } 102 | 103 | public void toggleCoreItems() { 104 | settings.put("coreitems", !settings.getBool("coreitems")); 105 | } 106 | 107 | public void drawPlans(Unit unit, boolean valid) { 108 | if (unit == null || unit.plans == null) return; 109 | 110 | Draw.draw(Layer.plans, valid ? unit::drawBuildPlans : () -> { 111 | unit.plans.each(plan -> { 112 | if (plan == null || plan.block == null) return; 113 | plan.animScale = 1f; 114 | plan.block.drawPlan(plan, unit.plans, valid); 115 | }); 116 | }); 117 | } 118 | 119 | 120 | private void drawRadius(Building build, int radius, Color color) { 121 | Drawf.dashCircle(build.x, build.y, radius * tilesize, color); 122 | } 123 | 124 | private void drawBar(Unit unit, float size, Color color, float fract) { 125 | Draw.color(color); 126 | 127 | size = Mathf.sqrt(unit.hitSize) * size; 128 | float x = unit.x - size / 2f, y = unit.y - size; 129 | 130 | float height = size * 2f, stroke = size * -.35f, xs = x - size; 131 | float f1 = Math.min(fract * 2f, 1f), f2 = (fract - .5f) * 2f; 132 | 133 | float bo = -(1f - f1) * (-size - stroke); 134 | Fill.quad( 135 | x, y, 136 | x + stroke, y, 137 | xs + bo, y + size * f1, 138 | xs - stroke + bo, y + size * f1); 139 | 140 | if (f2 < 0) return; 141 | 142 | float bx = (1f - f2) * (-size - stroke) + x; 143 | Fill.quad( 144 | xs, y + size, 145 | xs - stroke, y + size, 146 | bx, y + height * fract, 147 | bx + stroke, y + height * fract); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /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 | import static scheme.tools.MessageQueue.send; 19 | 20 | public class SlashJs implements AdminsTools { 21 | 22 | public void manageRuleBool(boolean value, String name) { 23 | send("Vars.state.rules." + name + " = " + value + "; Call.setRules(Vars.state.rules);"); 24 | } 25 | 26 | public void manageRuleStr(String value, String name) { 27 | send("Vars.state.rules." + name + " = " + value + "; Call.setRules(Vars.state.rules);"); 28 | } 29 | 30 | public void manageUnit() { 31 | if (unusable()) return; 32 | unit.select(false, true, false, (target, team, unit, amount) -> { 33 | if (!canCreate(team, unit)) return; 34 | getPlayer(target); 35 | send("player.unit().spawnedByCore = true"); 36 | send("player.unit(@.spawn(player.team(), player))", getUnit(unit)); 37 | units.refresh(); 38 | }); 39 | } 40 | 41 | public void spawnUnits() { 42 | if (unusable()) return; 43 | unit.select(true, true, true, (target, team, unit, amount) -> { 44 | if (amount == 0f) { 45 | send("Groups.unit.each(u => u.team == Team.@ && u.type == @, u => u.spawnedByCore = true)", team, getUnit(unit)); 46 | return; 47 | } 48 | 49 | if (!canCreate(team, unit)) return; 50 | getPlayer(target); 51 | send("var unit = @", getUnit(unit)); 52 | send("for (var i = 0; i < @; i++) unit.spawn(Team.@, player)", amount, team); 53 | units.refresh(); 54 | }); 55 | } 56 | 57 | public void manageEffect() { 58 | if (unusable()) return; 59 | effect.select(true, true, false, (target, team, effect, amount) -> { 60 | getPlayer(target); 61 | if (amount == 0f) send("player.unit().unapply(" + getEffect(effect) + ")"); 62 | else send("player.unit().apply(" + getEffect(effect) + ", " + amount + ")"); 63 | }); 64 | } 65 | 66 | public void manageItem() { 67 | if (unusable()) return; 68 | item.select(true, false, true, (target, team, item, amount) -> { 69 | if (!hasCore(team)) return; 70 | send("Team.@.core().items.add(@, @)", team, getItem(item), fixAmount(item, amount)); 71 | }); 72 | } 73 | 74 | public void manageTeam() { 75 | if (unusable()) return; 76 | team.select((target, team) -> { 77 | if (team != null) { 78 | RainbowTeam.remove(target); 79 | send("Groups.player.getByID(@).team(Team.@)", target.id, team); 80 | } else 81 | RainbowTeam.add(target, t -> send("Groups.player.getByID(@).team(Team.get(@))", target.id, t.id)); 82 | }); 83 | } 84 | 85 | public void placeCore() { 86 | if (unusable()) return; 87 | getPlayer(player); 88 | send("var tile = player.tileOn()"); 89 | send("if (tile != null) tile.setNet(tile.build instanceof CoreBlock.CoreBuild ? Blocks.air : Blocks.coreShard, player.team(), 0)"); 90 | } 91 | 92 | public void despawn(Player target) { 93 | if (unusable()) return; 94 | getPlayer(target); 95 | send("player.unit().spawnedByCore = true"); 96 | send("player.clearUnit()"); 97 | } 98 | 99 | public void teleport(Position pos) { 100 | if (unusable()) return; 101 | String conpos = "(player.con, " + pos.toString().replace("(", ""); // Vec2 and Point2 returns (x, y) 102 | getPlayer(player); 103 | send("var spawned = player.unit().spawnedByCore; var unit = player.unit(); unit.spawnedByCore = false; player.clearUnit()"); 104 | send("unit.set@; Call.setPosition@; Call.setCameraPosition@", pos, conpos, conpos); 105 | send("player.unit(unit); unit.spawnedByCore = spawned"); 106 | } 107 | 108 | public void fill(int sx, int sy, int ex, int ey) { 109 | if (unusable()) return; 110 | tile.select((floor, block, overlay, building) -> { 111 | edit(floor, block, overlay, building); 112 | send("for (var x = @; x <= @; x++) for (var y = @; y <= @; y++) todo(Vars.world.tile(x, y))", sx, ex, sy, ey); 113 | }); 114 | } 115 | 116 | public void brush(int x, int y, int radius) { 117 | if (unusable()) return; 118 | tile.select((floor, block, overlay, building) -> { 119 | edit(floor, block, overlay, building); 120 | send("Geometry.circle(@, @, @, (cx, cy) => todo(Vars.world.tile(cx, cy)))", x, y, radius); 121 | }); 122 | } 123 | 124 | public void flush(Seq plans) { 125 | if (unusable()) return; 126 | ui.showInfoFade("@admins.notsupported"); 127 | } 128 | 129 | public boolean unusable() { 130 | boolean admin = !player.admin && !settings.getBool("adminsalways"); 131 | if (!settings.getBool("adminsenabled")) { 132 | ui.showInfoFade(disabled); 133 | return true; 134 | } else if (admin) ui.showInfoFade("@admins.notanadmin"); 135 | return admin; 136 | } 137 | 138 | private static void send(String command, Object... args) { 139 | MessageQueue.send("/js " + Strings.format(command, args)); 140 | } 141 | 142 | private static void getPlayer(Player target) { 143 | send("var player = Groups.player.getByID(@)", target.id); 144 | } 145 | 146 | private static String getUnit(UnitType unit) { 147 | return "Vars.content.unit(" + unit.id + ")"; 148 | } 149 | 150 | private static String getEffect(StatusEffect effect) { 151 | return "Vars.content.statusEffects().get(" + effect.id + ")"; 152 | } 153 | 154 | private static String getItem(Item item) { 155 | return "Vars.content.item(" + item.id + ")"; 156 | } 157 | 158 | private static String getBlock(Block block) { 159 | return block == null ? "null" : "Vars.content.block(" + block.id + ")"; 160 | } 161 | 162 | private static void edit(Block floor, Block block, Block overlay, Block building) { 163 | boolean fo = floor != null || overlay != null; 164 | 165 | send("f = @; b = @; o = @; d = @", getBlock(floor), getBlock(block), getBlock(overlay), getBlock(building)); 166 | send("todo = tile => { if(tile!=null){" + (fo ? "sflr(tile);" : "") + (block != null ? "if(tile.block()!=b)tile.setNet(b)" : "") + (building != null ? "if(tile.block()!=d)tile.setNet(d, Team." + player.team() + ", 0)" : "") + "} }"); 167 | if (fo) send("sflr = tile => { if(tile.floor()!=f||tile.overlay()!=o)tile.setFloorNet(f==null?tile.floor():f,o==null?tile.overlay():o) }"); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /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}]<解除するには  を押してください。 > 12 | 13 | # just for fun 14 | team.rainbow.name = レ イ ン ボ ー 15 | 16 | manage.name = CLaJ由来のルームを管理する 17 | manage.create = ルームを作成およびそのルームのリンクの生成 18 | manage.tooltip = ルームへは遠隔サーバーを通じた友人が、リンクを通じてのみ参加できます。 19 | 20 | approaching.name = ウェーブ {0} 21 | approaching.health = [lightgray]合計体力: []{0}\uE813 22 | approaching.shield = [lightgray]合計シールド: []{0}\uE84D 23 | approaching.enemies = [lightgray]敵の総数: 24 | approaching.bosses = [lightgray]ガーディアン: 25 | approaching.info = [ [scarlet]次のウェーブが接近中[] ]\n\uF129をクリックしてウェーブの詳細を確認する 26 | 27 | schematic.importimage = 画像を読み込む 28 | parser.name = 画像解析プログラム 29 | parser.row = ディスプレイの数(縦): {0} 30 | parser.column = ディスプレイの数(横): {0} 31 | parser.filter = 画像フィルターの適用 32 | parser.filter.tooltip = 大型ディスプレイを使った、画素数が高いものを生成します。その代わり、使用するプロセッサーの数は増えます。 33 | parser.import = 読み込む 34 | 35 | gamma.tooltip = 36 | gamma.range = 半径: {0} 37 | gamma.speed = 速度: {0}% 38 | gamma.none = なにもしない 39 | gamma.circle = 円を描く 40 | gamma.cursor = カーソルを追尾する 41 | gamma.follow = 追尾する 42 | gamma.help = 建築を手伝う 43 | gamma.destroy = 建築を邪魔する 44 | gamma.repair = 建物を建て直す 45 | 46 | setting.panspeedmul.name = カメラ自体の移動速度 47 | setting.maxzoommul.name = 最小縮小倍率 48 | setting.minzoommul.name = 最大拡大倍率 49 | setting.mobilebuttons.name = モバイルボタン 50 | setting.mobilebuttons.description = 設定を反映させるためにゲームを再起動します。 51 | setting.hardscheme.name = 設計図の常時許可 52 | setting.hardscheme.description = 入っているサーバーが設計図を禁止しているかどうかに関わらす、設計図を使えるようにします。\nその代わりサーバーからキックまたはバンされる恐れがあります。 53 | setting.approachenabled.name = 接近中のウェーブを知らせる 54 | setting.approachenabled.description = このお知らせからウェーブの詳細も知ることができます。 55 | setting.welcome.name = 挨拶のメッセージの表示 56 | setting.welcome.description = ...こんなの誰がいるんだ? 57 | setting.check4update.name = アップデートの自動確認 58 | setting.check4update.description = アップデートが来たかどうかをMODに確認させるようにできます。 59 | setting.subtitle.name = 自己紹介 60 | setting.subtitle.description = これはこのMODと結びつきを持ったサーバーで、他プレイヤーが見ることができるものです。\nここに書くことについてとやかく言うことはできません。なので、せめて常識のある文章を書いてください。 61 | category.mod.name = MOD設定 62 | category.bt.name = 建築ツール 63 | category.add.name = いろいろ表示 64 | category.radius.name = 範囲シリーズ 65 | 66 | layer = 設計図のレイヤー: {0} 67 | layer.building = \uF865建物 68 | layer.floor = \uF8EE地面 69 | layer.block = \uF7D3ブロック 70 | layer.overlay = \uF8C0トッピング 71 | layer.terrain = \uF8EA環境 72 | 73 | keybind.adminscfg.name = 管理者ツールの設定 74 | keybind.rendercfg.name = 便利機能の設定 75 | keybind.schematic_shortcut.name = 設計図のショートカットの表示 76 | keybind.toggle_core_items.name = コア資源数の表示 77 | keybind.toggle_ai.name = AI機能に関するあれこれの表示 78 | keybind.manage_unit.name = ユニットに変身 79 | keybind.manage_effect.name = ステータス効果の付与・削除 80 | keybind.manage_item.name = コア資源の追加・削除 81 | keybind.manage_team.name = チームの変更 82 | keybind.place_core.name = コアの設置 83 | keybind.alternative.name = 代替可能キー ([accent]Hold + Block Info[]) 84 | 85 | keycomb.name = 割り当てキーの組み合わせ一覧 86 | keycomb.view_comb = この一覧を見る 87 | keycomb.view_sets = グラフィック設定を見る 88 | keycomb.reset_ai = AIをリセット 89 | keycomb.spawn_unit = ユニットを召喚 90 | keycomb.despawn = 自らをデスポーンする 91 | keycomb.teleport = テレポート 92 | keycomb.lock_move = ユニットの動きを制限 93 | keycomb.schematic = 設計図のレイヤーを変える 94 | keycomb.toggle_bt = 建築ツールの表示を切り替える 95 | keycomb.return = さっき解体した建物の建て直し 96 | keycomb.drop = 建物にあるアイテムを捨てる 97 | 98 | tooltip.drop = カーソルに重なっている建物からアイテムを捨てる 99 | tooltip.replace =隣接した建物の一括置換 100 | tooltip.remove = 隣接した建物の一括削除 101 | tooltip.connect = 最寄りの電力使用ブロックからノードをつなげる 102 | tooltip.fill = ブロックの範囲設置 103 | tooltip.square = 四角く建築 104 | tooltip.circle = 丸く建築 105 | tooltip.pick = カーソルに重なっている地形をコピー 106 | tooltip.edit = 一括タイル設置 107 | tooltip.brush = タイルブラシ 108 | 109 | select.ai = AIを選択 110 | select.ai.tooltip = ガンマAIを選択してプレイヤーに対して行動をとることができます。 111 | select.team = チームを選択 112 | select.tile = タイルを選択 113 | select.tag = タグを設定 114 | select.unit = ユニットを選択 115 | select.unit.clear = ユニットをデスポーンさせる 116 | select.effect = ステータス効果を選択 117 | select.effect.clear = ステータス効果を消去する 118 | select.item = 資源を選択 119 | select.item.clear = 資源を消去する 120 | 121 | select.all = 全ユニットに適応 122 | select.units = {0} 機 123 | select.seconds = {0} 秒 124 | select.items = {0} つ 125 | select.floor = 地面: {0} 126 | select.block = ブロック: {0} 127 | select.overlay = トッピング: {0} 128 | select.tagdesc = 設計図のショートカットはタグを用いて使用する設計図を表示します。\n簡単に言えば設計図一覧のタグ選択みたいな感じです。\n今割り当てられているタグは {0}[white] ですが、別のタグも選択できます。 129 | 130 | trace.type.show = Scheme Sizeを使っている仲間を強調 131 | trace.type.nodata = データがサーバーにありません。\nサーバー主にこのMODとサーバーを結びつけるよう頼んでください。 132 | trace.type.mod = 多分Scheme Size使ってるな。 133 | trace.type.vanilla = 多分バニラのプレイヤーだな。 134 | trace.type.self = 多分あんただな。 135 | trace.type.host = 多分ホストだな。 136 | 137 | render.name = 便利機能の設定 138 | render.desc = この項目をオンにするとラグくなる恐れがあります!\nその時はごめんなさい<(_ _)>。 139 | render.power = 電線 140 | render.status = 建物の状態 141 | render.light = 霧 142 | render.dark = 暗いところ 143 | render.fog = 戦場の霧 144 | render.xray = Xレイ 145 | render.hide = ユニットを非表示 146 | render.grid = マップグリッド 147 | render.ruler = カーソルものさし 148 | render.info = ユニット情報 149 | render.border = ボーダーレスなディスプレイ 150 | render.unit = ユニットの射程距離 151 | render.turret = タレットの射程距離 152 | render.reactor = リアクターの爆破範囲 153 | render.drive = 加速プロジェクターの適応範囲 154 | 155 | admins.name = 管理者の設定 156 | admins.lever = 管理者の機能を {0} 157 | admins.enabled = オン 158 | admins.disabled = オフ 159 | admins.way = どのパスでこのMODの管理者限定機能を使いますか? 160 | admins.way.internal.name = Internal 161 | admins.way.internal.desc = ローカル環境で遊んでたり、君がサーバーのホストだったりするならこれを使おう。 162 | admins.way.slashjs.name = Slash Js 163 | admins.way.slashjs.desc = JSEvalプラグインを使ったサーバーで遊んでいるならこれを使おう。\nスパム判定されかねないから、乱用は控えよう。 164 | admins.way.darkdustry.name = Darkdustry 165 | admins.way.darkdustry.desc = [accent]darkdustry.tk[]サーバーで遊んでいるならこれを使おう。 166 | admins.always = 管理者またはサーバーのチェックをスキップします。あまり推奨しませんが、アナーキーサーバーだったら使えるかもしれません。 167 | admins.strict = プレイヤーがテレポートできないようにします。 普通のサーバーならデフォでオンになって今いますが、ローカルサーバーでは基本的にオンになっていません。 168 | 169 | admins.notenabled = [scarlet]\u26A0[]この機能は機能しません!\n使いたいなら、管理者の設定 () でこの機能をオンにしてください。 170 | admins.notanadmin = [scarlet]\u26A0[]管理者ではないので、この機能は使えません! 171 | admins.notavailable = [scarlet]\u26A0[]設定に対応したサーバーではないので、この機能は使えません!\n管理者なのにこの機能が使えないならなら、管理者の設定 () でこの機能を使えるようにして下さい。 172 | admins.notsupported = [scarlet]\u26A0[]この機能はDarkdustryのプラグインではサポートされていません! 173 | admins.nocore = [scarlet]\u26A0[]選択したチームにはコアがありません! 174 | admins.nounit = [scarlet]\u26A0[]選択したチームではもうそのユニットは作れません! 175 | 176 | console.classic = クラシック 177 | console.multiline = マルチライン 178 | console.multiline.send = 送信済 179 | console.schedule = スケジュール 180 | console.schedule.new = 新しいタスク 181 | console.schedule.interval = インターバル/秒 182 | console.schedule.tooltip = 最小値は0.01秒 183 | console.schedule.cancel = タスクをキャンセル 184 | 185 | updater.name = [scarlet]\u26A0[]最新のMODではありません! 186 | updater.info = [accent]Scheme Size[]の新しいバージョンが出ました。\nこんなウザいメッセージを見たくないなら、設定でオフにできます。\n\n[gray]現在のバージョンは v{0} ですが、最新のバージョンは v{1} です。[] 187 | updater.load = 今すぐインストール 188 | -------------------------------------------------------------------------------- /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 | if (observed.unit() == null) return; 98 | camera.position.set(observed.unit()); // idk why, but unit moves smoother 99 | if (input.isTouched(0) && !scene.hasMouse()) observed = null; 100 | } 101 | 102 | buildInput(); 103 | if (movementLocked) { 104 | if (player.unit() == null) return; 105 | drawLocked(player.unit().x, player.unit().y); 106 | } 107 | 108 | } 109 | 110 | @Override 111 | protected void updateMovement(Unit unit) { 112 | if (ai.ai != null && !input.isTouched()) { 113 | if (!movementLocked) camera.position.set(unit.x, unit.y); 114 | ai.update(); 115 | } else if (!movementLocked) super.updateMovement(unit); 116 | 117 | if (shootingLocked) { 118 | unit.aimLook(player.mouseX, player.mouseY); 119 | unit.controlWeapons(true, false); 120 | player.shooting = unit.isShooting = false; 121 | } 122 | } 123 | 124 | public void buildInput() { 125 | if (!hudfrag.building.fliped) build.setMode(Mode.none); 126 | if (build.mode == Mode.none) return; 127 | 128 | int cursorX = tileX(); 129 | int cursorY = tileY(); 130 | 131 | boolean has = hasMoved(cursorX, cursorY); 132 | if (has) build.plan.clear(); 133 | 134 | if (using) { 135 | if (build.mode == Mode.drop) build.drop(cursorX, cursorY); 136 | if (has) { 137 | if (build.mode == Mode.replace) build.replace(cursorX, cursorY); 138 | if (build.mode == Mode.remove) build.remove(cursorX, cursorY); 139 | if (build.mode == Mode.connect) { 140 | if (block instanceof PowerNode == false) block = Blocks.powerNode; 141 | build.connect(cursorX, cursorY, (x, y) -> { 142 | updateLine(x, y); 143 | build.plan.addAll(linePlans).remove(0); 144 | }); 145 | } 146 | 147 | if (build.mode == Mode.fill) build.fill(buildX, buildY, cursorX, cursorY, maxSchematicSize); 148 | if (build.mode == Mode.circle) build.circle(cursorX, cursorY); 149 | if (build.mode == Mode.square) build.square(cursorX, cursorY, (x1, y1, x2, y2) -> { 150 | updateLine(x1, y1, x2, y2); 151 | build.plan.addAll(linePlans); 152 | }); 153 | 154 | if (build.mode == Mode.brush) admins.brush(cursorX, cursorY, build.size); 155 | 156 | lastX = cursorX; 157 | lastY = cursorY; 158 | lastSize = build.size; 159 | linePlans.clear(); 160 | } 161 | 162 | if (isRelease()) { 163 | flushBuildingTools(); 164 | 165 | if (build.mode == Mode.pick) tile.select(cursorX, cursorY); 166 | if (build.mode == Mode.edit) { 167 | NormalizeResult result = Placement.normalizeArea(buildX, buildY, cursorX, cursorY, 0, false, maxSchematicSize); 168 | admins.fill(result.x, result.y, result.x2, result.y2); 169 | } 170 | } 171 | } 172 | 173 | if (isTap() && !scene.hasMouse()) { 174 | buildX = cursorX; 175 | buildY = cursorY; 176 | using = true; 177 | } 178 | 179 | if (isRelease()) using = false; 180 | 181 | lastTouched = input.isTouched(); 182 | } 183 | 184 | public boolean hasMoved(int x, int y) { 185 | return lastX != x || lastY != y || lastSize != build.size; 186 | } 187 | 188 | // there is nothing because, you know, it's mobile 189 | public void changePanSpeed(float value) {} 190 | 191 | public void lockMovement() { 192 | movementLocked = !movementLocked; 193 | } 194 | 195 | public void lockShooting() { 196 | shootingLocked = !shootingLocked; 197 | } 198 | 199 | public void observe(Player target) { 200 | observed = target; 201 | } 202 | 203 | public void flush(Seq plans) { 204 | flushPlans(plans); 205 | } 206 | 207 | public InputHandler asHandler() { 208 | return this; 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/java/scheme/ui/CoreInfoFragment.java: -------------------------------------------------------------------------------- 1 | package scheme.ui; 2 | 3 | import arc.Events; 4 | import arc.graphics.Color; 5 | import arc.math.Mathf; 6 | import arc.scene.Group; 7 | import arc.scene.style.Drawable; 8 | import arc.scene.style.TextureRegionDrawable; 9 | import arc.scene.ui.layout.Table; 10 | import arc.struct.ObjectSet; 11 | import arc.util.Interval; 12 | import arc.util.Time; 13 | import mindustry.core.UI; 14 | import mindustry.game.Team; 15 | import mindustry.game.EventType.*; 16 | import mindustry.gen.Icon; 17 | import mindustry.gen.Tex; 18 | import mindustry.type.Item; 19 | import mindustry.ui.Styles; 20 | import mindustry.world.modules.ItemModule; 21 | import scheme.ui.dialogs.ListDialog; 22 | 23 | import static arc.Core.*; 24 | import static mindustry.Vars.*; 25 | import static scheme.SchemeVars.*; 26 | 27 | public class CoreInfoFragment { 28 | 29 | public CoreItemsDisplay items = new CoreItemsDisplay(); 30 | public PowerBars power = new PowerBars(); 31 | 32 | /** Whether the player chooses a power node, team or views resource statistics. */ 33 | public boolean choosesTeam, choosesNode, viewStats; 34 | /** Used when changing the schematic layer. */ 35 | public Interval timer = new Interval(2); 36 | /** Icon of the selected team. */ 37 | public Drawable chosenTeamRegion; 38 | 39 | public void build(Group parent) { 40 | Events.run(WorldLoadEvent.class, power::refreshNode); 41 | Events.run(BlockBuildEndEvent.class, power::refreshNode); 42 | Events.run(BlockDestroyEvent.class, power::refreshNode); 43 | Events.run(ConfigEvent.class, power::refreshNode); 44 | 45 | Events.run(ResetEvent.class, items::resetUsed); 46 | Events.run(WorldLoadEvent.class, () -> { 47 | items.rebuild(player.team()); 48 | chosenTeamRegion = texture(player.team()); 49 | }); 50 | 51 | var root = (Table) ((Table) ui.hudGroup.find("coreinfo")).getChildren().get(1); 52 | 53 | root.defaults().fillX().top(); 54 | root.visible(() -> !mobile && ui.hudfrag.shown); 55 | 56 | root.clear(); 57 | root.collapser(cont -> { // Core Items Display 58 | cont.background(Styles.black6).margin(8f); 59 | 60 | cont.add(items).growX(); 61 | cont.button(Icon.edit, Styles.clearNoneTogglei, () -> choosesTeam = !choosesTeam).checked(t -> choosesTeam).size(44f).top().padLeft(8f) 62 | .update(i -> i.getStyle().imageUp = chosenTeamRegion); 63 | }, () -> settings.getBool("coreitems")).row(); 64 | 65 | root.collapser(cont -> { // Team Selection 66 | cont.background(Styles.black6).margin(8f).left(); 67 | 68 | int[] amount = new int[1]; 69 | Runnable rebuild = () -> { 70 | cont.clear(); 71 | 72 | for (Team team : Team.all) { 73 | if (!team.active()) continue; 74 | 75 | var texture = texture(team); 76 | cont.button(texture, Styles.clearNoneTogglei, 36f, () -> { 77 | items.rebuild(team); 78 | chosenTeamRegion = texture; 79 | }).checked(i -> items.team == team).size(44f); 80 | } 81 | }; 82 | 83 | cont.update(() -> { 84 | var active = state.teams.getActive(); 85 | if (amount[0] == active.size) return; 86 | 87 | amount[0] = active.size; 88 | rebuild.run(); 89 | }); 90 | }, true, () -> settings.getBool("coreitems") && choosesTeam).row(); 91 | 92 | root.collapser(cont -> { // Power Bars 93 | cont.background(Styles.black6).margin(8f); 94 | 95 | cont.table(bars -> { 96 | bars.defaults().height(18f).growX(); 97 | bars.add(power.balance()).row(); 98 | bars.add(power.stored()).padTop(8f); 99 | }).growX(); 100 | cont.button(Icon.edit, Styles.clearNoneTogglei, () -> choosesNode = !choosesNode).checked(t -> choosesNode).size(44f).padLeft(8f); 101 | }, () -> settings.getBool("coreitems")).row(); 102 | 103 | float[] coreAttackTime = new float[1]; 104 | Events.run(Trigger.teamCoreDamage, () -> coreAttackTime[0] = 240f); 105 | 106 | root.collapser(cont -> { // Core Under Attack 107 | cont.background(Styles.black6).margin(8f); 108 | cont.add("@coreattack").update(label -> label.color.set(Color.orange).lerp(Color.scarlet, Mathf.absin(Time.time, 2f, 1f))); 109 | }, true, () -> { 110 | if (state.isPaused()) return false; 111 | if (state.isMenu() || player.team().data().noCores()) { 112 | coreAttackTime[0] = 0f; 113 | return false; 114 | } 115 | 116 | return (coreAttackTime[0] -= Time.delta) > 0; 117 | }).row(); 118 | 119 | root.collapser(cont -> { // Schematic Layer 120 | cont.background(Styles.black6).margin(8f); 121 | 122 | timer.reset(0, 240f); 123 | cont.label(() -> bundle.format("layer", bundle.get("layer." + m_schematics.layer))); 124 | }, true, () -> !timer.check(0, 240f)); 125 | } 126 | 127 | public void trySetNode(int x, int y) { 128 | if (choosesNode && power.setNode(world.build(x, y))) choosesNode = false; 129 | } 130 | 131 | public void nextLayer() { 132 | if (!timer.get(0, 240f)) m_schematics.nextLayer(); 133 | } 134 | 135 | public static Drawable texture(Team team) { 136 | if (team.id < 6) 137 | return new TextureRegionDrawable(ListDialog.texture(team)); 138 | else { 139 | var white = (TextureRegionDrawable) Tex.whiteui; 140 | return white.tint(team.color); 141 | } 142 | } 143 | 144 | /** Same as vanilla, but supports display of any team & resource statistics. */ 145 | public class CoreItemsDisplay extends Table { 146 | 147 | public final ObjectSet used = new ObjectSet<>(); 148 | 149 | public ItemModule display, core, last = new ItemModule(); 150 | public Team team; 151 | 152 | public void resetUsed() { 153 | used.clear(); 154 | clear(); 155 | } 156 | 157 | public void rebuild(Team team) { 158 | this.team = team; 159 | 160 | clear(); 161 | content.items().each(item -> { 162 | if (!used.contains(item)) return; 163 | 164 | image(item.uiIcon).size(iconSmall).padRight(3f); 165 | label(() -> display == null ? "0" : format(display.get(item))).padRight(3f).minWidth(52f).left(); 166 | 167 | if (children.size % 8 == 0) row(); 168 | }); 169 | 170 | hovered(() -> { 171 | viewStats = true; 172 | display = new ItemModule(); 173 | }); 174 | 175 | exited(() -> { 176 | viewStats = false; 177 | last.clear(); 178 | }); 179 | 180 | update(() -> { 181 | core = team.data().hasCore() ? team.core().items : null; 182 | if (!viewStats) display = core; 183 | 184 | if (core == null) return; 185 | 186 | if (content.items().contains(item -> core.get(item) > 0 && used.add(item))) rebuild(team); 187 | if (viewStats && timer.get(1, 30f)) updateStats(); // update resource stats only once every half second 188 | }); 189 | 190 | } 191 | 192 | // region stats 193 | 194 | public void updateStats() { 195 | if (last.any()) core.each((item, amount) -> display.set(item, amount - last.get(item))); 196 | last.set(core); 197 | } 198 | 199 | public String format(int amount) { 200 | if (viewStats) 201 | return (amount == 0 ? "" : amount > 0 ? "[lime]+" : "[scarlet]") + amount + "[gray]" + bundle.get("unit.persecond"); 202 | else 203 | return UI.formatAmount(amount); 204 | } 205 | 206 | // endregion 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/java/scheme/tools/BuildingTools.java: -------------------------------------------------------------------------------- 1 | package scheme.tools; 2 | 3 | import arc.Events; 4 | import arc.func.Cons; 5 | import arc.func.Cons2; 6 | import arc.func.Cons4; 7 | import arc.math.Mathf; 8 | import arc.struct.Seq; 9 | import arc.util.Strings; 10 | import mindustry.content.Items; 11 | import mindustry.entities.Units; 12 | import mindustry.entities.units.BuildPlan; 13 | import mindustry.game.EventType.*; 14 | import mindustry.gen.Building; 15 | import mindustry.gen.Call; 16 | import mindustry.input.InputHandler; 17 | import mindustry.input.Placement; 18 | import mindustry.input.Placement.NormalizeResult; 19 | import mindustry.type.Item; 20 | import mindustry.world.Block; 21 | import mindustry.world.Tile; 22 | import mindustry.world.modules.ItemModule; 23 | import scheme.Main; 24 | 25 | import static arc.Core.*; 26 | import static mindustry.Vars.*; 27 | import static scheme.SchemeVars.*; 28 | 29 | public class BuildingTools { 30 | 31 | public InputHandler input; 32 | public Seq useful; 33 | 34 | public Mode mode = Mode.none; 35 | public int size = 8; 36 | 37 | public Seq plan = new Seq<>(); 38 | public Seq removed = new Seq<>(); 39 | 40 | public Cons iterator; 41 | public Block iterated; 42 | public int ibsize; 43 | 44 | public BuildingTools() { 45 | this.input = m_input.asHandler(); 46 | this.useful = Seq.with(Mode.drop, Mode.replace, Mode.remove, Mode.connect); 47 | 48 | Events.on(WorldLoadEvent.class, event -> { 49 | if (settings.getBool("hardscheme")) state.rules.schematicsAllowed = true; 50 | }); 51 | } 52 | 53 | public void drop(int x, int y) { 54 | if (!MessageQueue.drop()) return; 55 | 56 | Tile tile = world.tile(x, y); 57 | if (tile == null || tile.build == null) return; 58 | 59 | ItemModule items = tile.build.items; 60 | if (items == null || items.empty()) return; 61 | else if (items.has(Items.sand)) drop(tile, Items.sand); 62 | else if (items.has(Items.coal)) drop(tile, Items.coal); 63 | else drop(tile, items.first()); 64 | } 65 | 66 | private void drop(Tile tile, Item item) { 67 | Call.requestItem(player, tile.build, item, units.maxAccepted); 68 | if (player.unit().stack.amount > 0) Call.dropItem(units.maxAccepted); 69 | } 70 | 71 | public void replace(int x, int y) { 72 | if (block() == null) return; 73 | 74 | Tile tile = world.tile(x, y); 75 | if (tile == null || tile.build == null) return; 76 | 77 | iterator = build -> plan(build.tileX(), build.tileY(), build.rotation); 78 | iterated = tile.block(); 79 | ibsize = iterated.size; 80 | 81 | try { // StackOverflowException was here 82 | if (block().size == iterated.size && block() != iterated) iterate(tile); 83 | } catch (Throwable e) { Main.error(e); } 84 | } 85 | 86 | public void remove(int x, int y) { 87 | Tile tile = world.tile(x, y); 88 | if (tile == null || tile.build == null) return; 89 | 90 | iterator = build -> plan(build.tileX(), build.tileY()); 91 | iterated = tile.block(); 92 | ibsize = iterated.size; 93 | 94 | try { iterate(tile); } catch (Throwable e) { Main.error(e); } 95 | } 96 | 97 | private void iterate(Tile tile){ 98 | if (tile.block() != iterated) return; 99 | 100 | int bx = tile.build.tileX(), by = tile.build.tileY(); 101 | if (plan.contains(plan -> plan.x == bx && plan.y == by)) return; 102 | iterator.get(tile.build); 103 | 104 | for (int x = bx - ibsize + 1; x <= bx + ibsize - 1; x += ibsize) iterate(world.tile(x, by + ibsize)); 105 | for (int y = by + ibsize - 1; y >= by - ibsize + 1; y -= ibsize) iterate(world.tile(bx + ibsize, y)); 106 | for (int x = bx + ibsize - 1; x >= bx - ibsize + 1; x -= ibsize) iterate(world.tile(x, by - ibsize)); 107 | for (int y = by - ibsize + 1; y <= by + ibsize - 1; y += ibsize) iterate(world.tile(bx - ibsize, y)); 108 | } // bruhness is everywhere bruhness is everywhere bruhness is everywhere bruhness is everywhere bruhness 109 | 110 | public void connect(int x, int y, Cons2 callback) { 111 | if (block() == null) return; 112 | 113 | Building power = Units.closestBuilding(player.team(), x * tilesize, y * tilesize, 999999f, build -> { 114 | return build.power != null && ((build.tileX() < x - size || build.tileX() > x + size) || (build.tileY() < y - size || build.tileY() > y + size)); 115 | }); // search for a power build that is not in the zone 116 | 117 | if (power == null) return; 118 | int px = power.tileX(), py = power.tileY(); 119 | callback.get(x > px ? px - 1 : px + 1, y > py ? py - 1 : py + 1); // magic 120 | } 121 | 122 | public void fill(int x1, int y1, int x2, int y2, int maxLength) { 123 | if (block() == null) return; 124 | 125 | NormalizeResult result = Placement.normalizeArea(x1, y1, x2, y2, 0, false, maxLength); 126 | for (int x = result.x; x <= result.x2; x += block().size) 127 | for (int y = result.y; y <= result.y2; y += block().size) 128 | plan(x, y, 0); 129 | } 130 | 131 | public void square(int x, int y, Cons4 callback) { 132 | if (block() == null) return; 133 | 134 | callback.get(x - size, y + size, x + size - 1, y + size); 135 | callback.get(x + size, y + size, x + size, y - size + 1); 136 | callback.get(x + size, y - size, x - size + 1, y - size); 137 | callback.get(x - size, y - size, x - size, y + size - 1); 138 | } 139 | 140 | public void circle(int x, int y){ 141 | if (block() == null) return; 142 | 143 | ibsize = block().size; 144 | for (int dx = -size; dx <= size; dx += ibsize) 145 | for (int dy = -size; dy <= size; dy += ibsize) 146 | if (Mathf.within(dx, dy, size) && !Mathf.within(dx, dy, size - ibsize)) plan(x + dx, y + dy, 0); 147 | } 148 | 149 | public void save(int x1, int y1, int x2, int y2, int maxLength) { 150 | removed.clear(); 151 | 152 | NormalizeResult result = Placement.normalizeArea(x1, y1, x2, y2, 0, false, maxLength); 153 | for (int x = result.x; x <= result.x2; x++) 154 | for (int y = result.y; y <= result.y2; y++) { 155 | Building build = world.build(x, y); 156 | if (build != null) plan(build); 157 | } 158 | } 159 | 160 | public boolean isPlacing() { 161 | return (!plan.isEmpty() || mode == Mode.connect) && mode != Mode.none && (input.isPlacing() || mode == Mode.remove); 162 | } 163 | 164 | public void resized() { 165 | size = Mathf.clamp(size, 1, 512); 166 | hudfrag.size.setText(String.valueOf(size)); 167 | } 168 | 169 | public void resize(int amount) { 170 | size += amount; 171 | resized(); 172 | } 173 | 174 | public void resize(float amount) { 175 | if (amount == 0) return; 176 | amount = (size / 16f) * amount; 177 | size += amount > 0 ? Mathf.clamp(amount, 1, 8) : Mathf.clamp(amount, -8, -1); 178 | resized(); 179 | } 180 | 181 | public void resize(String amount) { 182 | if (amount.isEmpty()) return; 183 | size = Strings.parseInt(amount); 184 | resized(); 185 | } 186 | 187 | public void setMode(Mode set) { 188 | if (set == Mode.none && useful.contains(mode)) return; 189 | mode = mode == set ? Mode.none : set; 190 | } 191 | 192 | private void plan(int x, int y) { 193 | plan.add(new BuildPlan(x, y)); 194 | } 195 | 196 | private void plan(int x, int y, int rotation) { 197 | plan.add(new BuildPlan(x, y, rotation, block(), block().nextConfig())); 198 | } 199 | 200 | private void plan(Building build) { 201 | removed.add(new BuildPlan(build.tileX(), build.tileY(), build.rotation, build.block, build.config())); 202 | } 203 | 204 | private Block block() { 205 | return input.block; 206 | } 207 | 208 | public enum Mode { 209 | none, drop, replace, remove, connect, fill, square, circle, pick, edit, brush; 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /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}]< 按  以解锁> 12 | 13 | # just for fun 仅仅是有趣 14 | team.rainbow.name = 彩虹 15 | 16 | manage.name = 管理CLaJ房间 17 | manage.create = 创建房间并生成链接 18 | manage.tooltip = CLaJ房间允许您的朋友通过远程服务器连接到您 19 | 20 | approaching.name = 波次 {0} 21 | approaching.health = [lightgray]总血量: []{0}\uE813 22 | approaching.shield = [lightgray]总护盾: []{0}\uE84D 23 | approaching.enemies = [lightgray]全部敌军: 24 | approaching.bosses = [lightgray]BOSS: 25 | approaching.info = [ [scarlet]即将下一波[] ]\n点击 \uF129 查看波次信息 26 | 27 | schematic.importimage = 导入图片 28 | parser.name = 图像分析器 29 | parser.row = 显示每一行: {0} 30 | parser.column = 显示每列: {0} 31 | parser.filter = 启用图像过滤器 32 | parser.filter.tooltip = 使图像在大型显示器上更流畅,但可能会增加处理器的数量 33 | parser.import = 导入 34 | 35 | gamma.tooltip = 36 | gamma.range = 范围: {0} 37 | gamma.speed = 速度: {0}% 38 | gamma.none = 无操作 39 | gamma.circle = 圆 40 | gamma.cursor = 跟随鼠标 41 | gamma.follow = 跟随 42 | gamma.help = 辅助建造 43 | gamma.destroy = 建造方块 44 | gamma.repair = 修复建筑 45 | 46 | setting.panspeedmul.name = 视角移动速度 47 | setting.maxzoommul.name = 放大极限 48 | setting.minzoommul.name = 缩小极限 49 | setting.mobilebuttons.name = 移动按钮 50 | setting.mobilebuttons.description = 重启以应用更改 51 | setting.hardscheme.name = 总是允许使用蓝图 52 | setting.hardscheme.description = 允许在当前禁用蓝图游戏中使用蓝图\n您可能在某些服务器被踢出/封禁 53 | setting.approachenabled.name = 通知即将下一波 54 | setting.approachenabled.description = 您可以在此通知中得知下一波的某些信息 55 | setting.welcome.name = 显示欢迎消息 56 | setting.welcome.description = 谁会需要这些烦人的消息呢? 57 | setting.check4update.name = 检查更新 58 | setting.check4update.description = 选择模组是否应该检查更新 59 | setting.subtitle.name = 关于我 60 | setting.subtitle.description = 这个信息将对装本模组的其他玩家和集成服务器可见\n我无法控制您在这里写的东西,所以我只求您是个有教养的人 61 | 62 | category.mod.name = 改动 63 | category.bt.name = 建造工具 64 | category.add.name = 添加 65 | category.radius.name = 半径 66 | 67 | layer = 蓝图图层: {0} 68 | layer.building = \uF865建筑 69 | layer.floor = \uF8EE地板 70 | layer.block = \uF7D3方块 71 | layer.overlay = \uF8C0覆盖 72 | layer.terrain = \uF8EA地形 73 | 74 | keybind.adminscfg.name = 配置管理工具 75 | keybind.rendercfg.name = 配置渲染工具 76 | keybind.schematic_shortcut.name = 显示蓝图快捷键 77 | keybind.toggle_core_items.name = 调整核心资源 78 | keybind.toggle_ai.name = AI切换窗口 79 | keybind.manage_unit.name = 调整附身单位 80 | keybind.manage_effect.name = 调整状态效果 81 | keybind.manage_item.name = 调整核心资源 82 | keybind.manage_team.name = 调整队伍 83 | keybind.place_core.name = 放置核心 84 | keybind.alternative.name = 可选功能 ([accent]按住 + 方块信息[]) 85 | # for tooltip 关于功能提示 86 | keybind.lock_shoot.name = 开火锁定 87 | 88 | keycomb.name = 绑定键组合 89 | keycomb.view_comb = 显示绑定键组合 90 | keycomb.view_sets = 查看图形设置 91 | keycomb.reset_ai = 重置AI 92 | keycomb.spawn_unit = 刷单位 93 | keycomb.despawn = 自杀 94 | keycomb.teleport = 瞬移 95 | keycomb.lock_move = 移动锁定 96 | keycomb.schematic = 改变蓝图图层 97 | keycomb.toggle_bt = 切换建筑工具 98 | keycomb.return = 撤销最后拆除建筑 99 | keycomb.drop = 清空建筑中的材料 100 | 101 | tooltip.drop = 清空鼠标下建筑中的材料 102 | tooltip.replace = 将建物筑物及其附近同类建筑覆盖为所选建筑 103 | tooltip.remove = 拆除建筑物以及附近同类建筑 104 | tooltip.connect = 从最近的电源连接到节点 105 | tooltip.fill = 用所选建筑填充区域 106 | tooltip.square = 建造正方形 107 | tooltip.circle = 建造圆环 108 | tooltip.pick = 复制鼠标下的地板 109 | tooltip.edit = 地板填充 110 | tooltip.brush = 地板画笔 111 | 112 | select.ai = 选择AI 113 | select.ai.tooltip = 你可以替换一个玩家的伽马ai 114 | select.team = 选择队伍 115 | select.tile = 选择地板 116 | select.tag = 选择标签 117 | select.unit = 选择单位 118 | select.unit.clear = 清除单位 119 | select.effect = 选择状态效果 120 | select.effect.clear = 清除状态效果 121 | select.item = 选择材料 122 | select.item.clear = 清除材料 123 | 124 | select.all = 选择全部 125 | select.units = {0} 单位 126 | select.seconds = {0} 二级 127 | select.items = {0} 材料 128 | select.floor = 地板: {0} 129 | select.block = 建筑: {0} 130 | select.overlay = 覆盖: {0} 131 | select.tagdesc = 蓝图快捷方式,显示所选标签内蓝图\n就像蓝图库中的标签原理\n您所选标签是{0}[white],您也可以任意选择其他您喜欢的标签 132 | 133 | trace.type.show = 显示参数值玩家 134 | trace.type.nodata = 此服务器没有提供数据\n请要求服主集成参数值 135 | trace.type.mod = 看起来这个玩家正在使用参数值 136 | trace.type.vanilla = 看起来这个玩家使用的原版 137 | trace.type.self = 看起来这是您 138 | trace.type.host = 看起来这是服主 139 | 140 | render.name = 渲染设置 141 | render.desc = 这些功能可能导致延迟!\n如果发生这种事,我很抱歉 142 | render.power = 显示电力激光 143 | render.status = 显示建筑状态 144 | render.light = 显示光照 145 | render.dark = 显示黑暗 146 | render.fog = 显示战争迷雾 147 | render.xray = 显示X光 148 | render.hide = 显示隐藏单位 149 | render.grid = 显示建筑网格 150 | render.ruler = 显示鼠标量尺 151 | render.info = 显示单位血量信息 152 | render.border = 不显示显示屏边界 153 | render.unit = 显示单位攻击半径 154 | render.turret = 显示炮台攻击半径 155 | render.reactor = 显示反应堆爆炸半径 156 | render.drive = 显示超速投影半径 157 | 158 | admins.name = 管理员配置 159 | admins.lever = 管理员功能 {0} 160 | admins.enabled = 授权 161 | admins.disabled = 禁用 162 | admins.way = 您想使用哪个方式进行游戏? 163 | admins.way.internal.name = 内置 164 | admins.way.internal.desc = 如果您是主机或本地游戏,请选择它 165 | admins.way.slashjs.name = 脚本 166 | admins.way.slashjs.desc = 如果你在一个有JSEval的服务器上游玩,请选择它\n请小心滥用,因为它会制造大量垃圾信息 167 | admins.way.darkdustry.name = Darkdustry 168 | admins.way.darkdustry.desc = 如果您游玩的是[accent]Darkdustry.tk[]服务器,请选择它 169 | admins.always = 跳过管理员/服务器检测,不推荐使用。但在一些无政府状态服务器中可能有用 170 | admins.strict = 防止玩家传送。 这在服务器上默认启用,但在本地主机上不启用 171 | 172 | admins.notenabled = [scarlet]\u26A0[]此功能已被禁用!\n要使用它,请启用管理员配置中的相关功能() 173 | admins.notanadmin = [scarlet]\u26A0[]此功能不可用,因为您不是管理员! 174 | admins.notavailable = [scarlet]\u26A0[]此功能不可用,因为您不是服务端!\n如果您是管理员,请去管理员配置() 175 | admins.notsupported = [scarlet]\u26A0[]此功能不被darkdustry支持! 176 | admins.nocore = [scarlet]\u26A0[]所选队伍没有核心! 177 | admins.nounit = [scarlet]\u26A0[]所选队伍不能创建更多单位! 178 | 179 | console.classic = 原版 180 | console.multiline = 多线程 181 | console.multiline.send = 发送 182 | console.schedule = 列表 183 | console.schedule.new = 新任务 184 | console.schedule.interval = 秒间隔 185 | console.schedule.tooltip = 最小为0.01秒 186 | console.schedule.cancel = 取消任务 187 | 188 | updater.name = [scarlet]\u26A0[]旧版模组! 189 | updater.info = 您的[accent]参数值[]为旧版.\n这个烦人的对话框可以在设置中关闭\n\n[gray]当前版本为v{0}最新版本为v{1}[] 190 | updater.load = 安装 191 | 192 | claj.join.name=Join via CLaJ 193 | claj.join.link=Link: 194 | claj.join.invalid=[red]Invalid link: 195 | claj.join.valid=[lime]Valid link, you can try to connect 196 | claj.join.note=[lightgray]Note: CLaJ v2 links are not compatibles with the older ones. 197 | 198 | claj.manage.name=Manage CLaJ Room 199 | claj.manage.create=Create Room 200 | claj.manage.delete=Close Room 201 | claj.manage.copy=Copy Link 202 | claj.manage.tip=[lightgray]CLaJ Rooms allows your friends to connect to you, through a remote server.\n\ 203 | The room is only accessible via the link, make sure you don't send it to everyone. 204 | claj.manage.custom-servers=Custom servers 205 | claj.manage.public-servers=Public servers 206 | claj.manage.server-name=Name: 207 | claj.manage.missing-host=[red]Missing host name or ip 208 | claj.manage.missing-port=[red]Missing port 209 | claj.manage.invalid-port=[red]Invalid port or port range 210 | claj.manage.creating-room=Creating room... 211 | claj.manage.room-creation-failed=[scarlet]Unable to negotiate the room link![]\n\ 212 | The server is probably not a CLaJ server or is not updated. 213 | claj.manage.fetch-failed=Unable to fetch the public servers list. 214 | 215 | claj.message.server-closing=The server is shutting down, please wait a minute or choose another server. 216 | claj.message.packet-spamming=A player was kicked for packet spamming. 217 | claj.message.already-hosting=You're already hosting a room! \nCannot join or create another one. 218 | claj.message.room-closure-denied=[yellow]WARNING:[] Someone connected via CLaJ tried to close the room without permission. 219 | claj.message.con-closure-denied=[yellow]WARNING:[] Someone connected via CLaJ tried to kick a player without permission. 220 | 221 | claj.room.closed=The room has been closed by the server. 222 | claj.room.obsolete-client=Your CLaJ version is not compatible with this server. 223 | claj.room.outdated-version=Your CLaJ version is not updated.\nPlease update it by reinstalling the 'claj' mod. 224 | claj.room.server-closed=The CLaJ server is closed, please wait a minute or choose another one. 225 | 226 | -------------------------------------------------------------------------------- /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 | import com.github.bsideup.jabel.Desugar; 22 | 23 | public class ImageParser { 24 | 25 | public static final String processorSeparator = "#"; 26 | public static final String flush = "drawflush display1\n"; 27 | 28 | // region parse 29 | 30 | /** Reads an image from a file and converts it to schematic. */ 31 | public static Schematic parseSchematic(Fi file, Config cfg) { 32 | try { 33 | return parseSchematic(file.nameWithoutExtension(), new Pixmap(file), cfg); 34 | } catch (Throwable ingored) { // file is corrupted or idk what 35 | return null; 36 | } 37 | } 38 | 39 | /** Converts a pixmap to schematic with logical processors and displays. */ 40 | public static Schematic parseSchematic(String name, Pixmap image, Config cfg) { 41 | final int size = cfg.display.size, pixels = cfg.display.displaySize; 42 | final int width = cfg.columns * size, height = cfg.rows * size; 43 | 44 | Pixmaps.flip(image); 45 | if (cfg.filter) 46 | image = Pixmaps.scale(image, pixels * cfg.columns, pixels * cfg.rows, true); 47 | else 48 | image = Pixmaps.scale(image, (float) pixels * cfg.columns / image.width, (float) pixels * cfg.rows / image.height); 49 | 50 | // region creating tiles 51 | 52 | int layer = 0; // the current layer on which the processors are located 53 | Seq available = new Seq<>(); // sequence of all positions available to the processor 54 | 55 | Seq tiles = new Seq<>(); 56 | for (int row = 0; row < cfg.rows; row++) { 57 | for (int column = 0; column < cfg.columns; column++) { 58 | 59 | int x = column * size - cfg.offset(), y = row * size - cfg.offset(); 60 | tiles.add(new Stile(cfg.display, x, y, null, (byte) 0)); 61 | 62 | Display block = new Display(image, column * pixels, row * pixels, pixels); 63 | for (String code : parseCode(block).split(processorSeparator)) { 64 | 65 | var pos = next(available, x, y, cfg.range()); 66 | if (pos == null) refill(available, layer++, width, height); 67 | 68 | pos = next(available, x, y, cfg.range()); 69 | if (pos == null) return null; // processor range is too small 70 | 71 | byte[] compressed = LogicBlock.compress(code, Seq.with(new LogicLink(x - pos.x, y - pos.y, "display1", true))); 72 | tiles.add(new Stile(cfg.processor, pos.x, pos.y, compressed, (byte) 0)); 73 | available.remove(pos); // this position is now filled 74 | } 75 | } 76 | } 77 | 78 | // endregion 79 | 80 | int minx = tiles.min(st -> st.x).x; 81 | int miny = tiles.min(st -> st.y).y; 82 | 83 | tiles.each(st -> { 84 | st.x -= minx; 85 | st.y -= miny; 86 | }); 87 | 88 | return new Schematic(tiles, StringMap.of("name", name), width + layer * 2, height + layer * 2); 89 | } 90 | 91 | /** Converts a display into a sequence of instructions for a logical processor. */ 92 | public static String parseCode(Display display) { 93 | StringBuilder code = new StringBuilder(); 94 | 95 | Color last = null; 96 | int instructions = 0; 97 | for (Line rect : parseLines(display).sort(rect -> rect.color.abgr())) { 98 | if (!rect.color.equals(last)) { 99 | last = rect.color; 100 | 101 | code.append(rect.colorCode()); 102 | instructions++; 103 | } 104 | 105 | code.append(rect.rectCode()); 106 | instructions++; 107 | 108 | if (instructions + 2 >= LExecutor.maxInstructions) { 109 | code.append(flush).append(processorSeparator); 110 | instructions = 0; 111 | last = null; 112 | 113 | continue; 114 | } 115 | 116 | if (instructions % LExecutor.maxGraphicsBuffer <= 1) { 117 | code.append(flush).append(rect.colorCode()); 118 | instructions += 2; 119 | } 120 | } 121 | 122 | return code.append(flush).toString(); 123 | } 124 | 125 | /** Converts a display into a sequence of colored lines. */ 126 | public static Seq parseLines(Display display) { 127 | var result = new Seq(); 128 | 129 | for (int y = 0; y < display.size; y++) { 130 | for (int x = 0; x < display.size; x++) { 131 | int raw = display.get(x, y); 132 | 133 | int length = 1; 134 | for (int i = x + 1; i < display.size; i++) 135 | if (display.get(i, y) == raw) length++; 136 | else break; 137 | 138 | result.add(new Line(new Color(raw), x, y, length)); 139 | x += length - 1; // skip same pixels 140 | } 141 | } 142 | 143 | return result; 144 | } 145 | 146 | // endregion 147 | // region available positions 148 | 149 | private static void refill(Seq available, int layer, int width, int height) { 150 | int amount = 2 * (width + height) + 8 * layer + 4; 151 | Point2 pos = new Point2(-layer - 1, -layer), dir = new Point2(0, 1); 152 | 153 | for (int i = 0; i < amount; i++) { 154 | available.add(pos.cpy()); 155 | pos.add(dir); 156 | 157 | if (pos.equals(-layer - 1, height + layer)) dir.set(1, 0); 158 | if (pos.equals(width + layer, height + layer)) dir.set(0, -1); 159 | if (pos.equals(width + layer, -layer - 1)) dir.set(-1, 0); 160 | } 161 | } 162 | 163 | private static Point2 next(Seq available, int x, int y, float range) { 164 | var inRange = available.select(point -> point.dst(x, y) < range); 165 | return inRange.isEmpty() ? null : inRange.first(); 166 | } 167 | 168 | // endregion 169 | 170 | public static class Display { 171 | public final Pixmap pixmap; 172 | public final int x; 173 | public final int y; 174 | public final int size; 175 | 176 | public Display(Pixmap pixmap, int x, int y, int size) { 177 | this.pixmap = pixmap; 178 | this.x = x; 179 | this.y = y; 180 | this.size = size; 181 | } 182 | 183 | public int get(int x, int y) { 184 | return pixmap.getRaw(this.x + x, this.y + y); 185 | } 186 | } 187 | 188 | 189 | public static class Line { 190 | public final Color color; 191 | public final int x; 192 | public final int y; 193 | public final int length; 194 | 195 | public Line(Color color, int x, int y, int length) { 196 | this.color = color; 197 | this.x = x; 198 | this.y = y; 199 | this.length = length; 200 | } 201 | 202 | public String colorCode() { 203 | return Strings.format( 204 | "draw color @ @ @ @\n", 205 | (int) (color.r * 255), 206 | (int) (color.g * 255), 207 | (int) (color.b * 255), 208 | (int) (color.a * 255) 209 | ); 210 | } 211 | 212 | public String rectCode() { 213 | return Strings.format("draw rect @ @ @ 1\n", x, y, length); 214 | } 215 | } 216 | 217 | 218 | public static class Config { 219 | 220 | public LogicBlock processor = (LogicBlock) Blocks.microProcessor; 221 | public LogicDisplay display = (LogicDisplay) Blocks.logicDisplay; 222 | 223 | public int rows, columns; 224 | public boolean filter; 225 | 226 | public int offset() { 227 | return display.sizeOffset; 228 | } 229 | 230 | public float range() { 231 | return processor.range / tilesize + display.size / 2 - 1; 232 | } 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH="\\\"\\\"" 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /src/java/scheme/moded/ModedDesktopInput.java: -------------------------------------------------------------------------------- 1 | package scheme.moded; 2 | 3 | import arc.graphics.g2d.Draw; 4 | import arc.graphics.g2d.Lines; 5 | import arc.math.Mathf; 6 | import arc.math.geom.Vec2; 7 | import arc.scene.ui.layout.Scl; 8 | import arc.struct.Seq; 9 | import arc.util.Time; 10 | import arc.util.Tmp; 11 | import mindustry.content.Blocks; 12 | import mindustry.entities.units.BuildPlan; 13 | import mindustry.gen.Player; 14 | import mindustry.gen.Unit; 15 | import mindustry.graphics.Pal; 16 | import mindustry.input.*; 17 | import mindustry.input.Placement.NormalizeDrawResult; 18 | import mindustry.input.Placement.NormalizeResult; 19 | import mindustry.world.blocks.power.PowerNode; 20 | import scheme.ai.GammaAI; 21 | import scheme.tools.BuildingTools.Mode; 22 | 23 | import static arc.Core.*; 24 | import static mindustry.Vars.*; 25 | import static mindustry.input.PlaceMode.*; 26 | import static scheme.SchemeVars.*; 27 | 28 | /** Last update - Apr 18, 2023 */ 29 | public class ModedDesktopInput extends DesktopInput implements ModedInputHandler { 30 | 31 | public boolean using, movementLocked; 32 | public int buildX, buildY, lastX, lastY, lastSize = 8; 33 | 34 | public Vec2 lastCamera = new Vec2(); 35 | public Player observed; 36 | 37 | @Override 38 | protected void removeSelection(int x1, int y1, int x2, int y2, int maxLength) { 39 | build.save(x1, y1, x2, y2, maxSchematicSize); 40 | super.removeSelection(x1, y1, x2, y2, maxSchematicSize); 41 | } 42 | 43 | @Override 44 | protected void flushPlans(Seq plans) { 45 | if (m_schematics.isCursed(plans)) admins.flush(plans); 46 | else super.flushPlans(plans); 47 | } 48 | 49 | @Override 50 | public void drawTop() { 51 | Lines.stroke(1f); 52 | int cursorX = tileX(); 53 | int cursorY = tileY(); 54 | 55 | if (mode == breaking) { 56 | drawBreakSelection(selectX, selectY, cursorX, cursorY, maxSchematicSize); 57 | drawSize(selectX, selectY, cursorX, cursorY, maxSchematicSize); 58 | } else if (input.keyDown(Binding.schematicSelect) && !scene.hasKeyboard()) { 59 | drawSelection(schemX, schemY, cursorX, cursorY, maxSchematicSize); 60 | drawSize(schemX, schemY, cursorX, cursorY, maxSchematicSize); 61 | } else if (input.keyDown(Binding.rebuildSelect) && !scene.hasKeyboard()) { 62 | drawSelection(schemX, schemY, cursorX, cursorY, 0, Pal.sapBulletBack, Pal.sapBullet, false); 63 | 64 | NormalizeDrawResult result = Placement.normalizeDrawArea(Blocks.air, schemX, schemY, cursorX, cursorY, false, 0, 1f); 65 | Tmp.r1.set(result.x, result.y, result.x2 - result.x, result.y2 - result.y); 66 | 67 | for (var plan : player.team().data().plans) { 68 | var block = plan.block; 69 | if (block.bounds(plan.x, plan.y, Tmp.r2).overlaps(Tmp.r1)) 70 | drawSelected(plan.x, plan.y, plan.block, Pal.sapBullet); 71 | } 72 | } 73 | 74 | if (using) { 75 | if (build.mode == Mode.edit) 76 | drawEditSelection(buildX, buildY, cursorX, cursorY, maxSchematicSize); 77 | 78 | if (build.mode == Mode.connect && isPlacing()) 79 | drawEditSelection(cursorX - build.size, cursorY - build.size, cursorX + build.size, cursorY + build.size, maxSchematicSize); 80 | } 81 | 82 | if (build.mode == Mode.brush) 83 | drawEditSelection(cursorX, cursorY, build.size); 84 | 85 | drawCommanded(); 86 | 87 | Draw.reset(); 88 | } 89 | 90 | @Override 91 | public void drawBottom() { 92 | if (!build.isPlacing()) super.drawBottom(); 93 | else build.plan.each(plan -> { 94 | plan.animScale = 1f; 95 | if (build.mode != Mode.remove) drawPlan(plan); 96 | else drawBreaking(plan); 97 | }); 98 | if (ai.ai instanceof GammaAI gamma) gamma.draw(); 99 | } 100 | 101 | @Override 102 | public void update() { 103 | lastCamera.set(camera.position); 104 | super.update(); // prevent unit clear, is it a crutch? 105 | 106 | if (locked()) return; 107 | 108 | if (observed != null) { 109 | if (observed.unit() == null) return; 110 | camera.position.set(observed.unit()); // idk why, but unit moves smoother 111 | panning = true; 112 | 113 | // stop viewing a player if movement key is pressed 114 | if ((input.axis(Binding.moveX) != 0 || input.axis(Binding.moveY) != 0 || input.keyDown(Binding.pan)) && !scene.hasKeyboard()) observed = null; 115 | } 116 | 117 | if (movementLocked && !scene.hasKeyboard() && observed == null) { 118 | if (player.unit() == null) return; 119 | drawLocked(player.unit().x, player.unit().y); 120 | panning = true; // panning is always enabled when unit movement is locked 121 | 122 | float speed = (input.keyDown(Binding.boost) ? panBoostSpeed : panSpeed) * Time.delta; 123 | 124 | movement.set(input.axis(Binding.moveX), input.axis(Binding.moveY)).nor().scl(speed); 125 | camera.position.set(lastCamera).add(movement); 126 | 127 | if (input.keyDown(Binding.pan)) { 128 | camera.position.x += Mathf.clamp((input.mouseX() - graphics.getWidth() / 2f) * panScale, -1, 1) * speed; 129 | camera.position.y += Mathf.clamp((input.mouseY() - graphics.getHeight() / 2f) * panScale, -1, 1) * speed; 130 | } 131 | } 132 | 133 | if (scene.hasField()) { 134 | if (ai.ai != null && !player.dead() && !state.isPaused()) ai.update(); 135 | return; // update the AI even if the player is typing a message 136 | } 137 | 138 | if (scene.hasKeyboard()) return; 139 | 140 | modedInput(); 141 | buildInput(); 142 | } 143 | 144 | @Override 145 | protected void updateMovement(Unit unit) { 146 | if (ai.ai != null 147 | && input.axis(Binding.moveX) == 0 && input.axis(Binding.moveY) == 0 148 | && !input.keyDown(Binding.mouseMove) && !input.keyDown(Binding.select)) 149 | ai.update(); 150 | else if (!movementLocked) super.updateMovement(unit); 151 | } 152 | 153 | public void buildInput() { 154 | if (!hudfrag.building.fliped) build.setMode(Mode.none); 155 | if (build.mode == Mode.none) return; 156 | 157 | int cursorX = tileX(); 158 | int cursorY = tileY(); 159 | 160 | boolean has = hasMoved(cursorX, cursorY); 161 | if (has) build.plan.clear(); 162 | 163 | if (using) { 164 | if (build.mode == Mode.drop) build.drop(cursorX, cursorY); 165 | if (has) { 166 | if (build.mode == Mode.replace) build.replace(cursorX, cursorY); 167 | if (build.mode == Mode.remove) build.remove(cursorX, cursorY); 168 | if (build.mode == Mode.connect) { 169 | if (block instanceof PowerNode == false) block = Blocks.powerNode; 170 | build.connect(cursorX, cursorY, (x, y) -> { 171 | updateLine(x, y); 172 | build.plan.addAll(linePlans).remove(0); 173 | }); 174 | } 175 | 176 | if (build.mode == Mode.fill) build.fill(buildX, buildY, cursorX, cursorY, maxSchematicSize); 177 | if (build.mode == Mode.circle) build.circle(cursorX, cursorY); 178 | if (build.mode == Mode.square) build.square(cursorX, cursorY, (x1, y1, x2, y2) -> { 179 | updateLine(x1, y1, x2, y2); 180 | build.plan.addAll(linePlans); 181 | }); 182 | 183 | if (build.mode == Mode.brush) admins.brush(cursorX, cursorY, build.size); 184 | 185 | lastX = cursorX; 186 | lastY = cursorY; 187 | lastSize = build.size; 188 | linePlans.clear(); 189 | } 190 | 191 | if (input.keyRelease(Binding.select)) { 192 | flushBuildingTools(); 193 | 194 | if (build.mode == Mode.pick) tile.select(cursorX, cursorY); 195 | if (build.mode == Mode.edit) { 196 | NormalizeResult result = Placement.normalizeArea(buildX, buildY, cursorX, cursorY, 0, false, maxSchematicSize); 197 | admins.fill(result.x, result.y, result.x2, result.y2); 198 | } 199 | } else build.resize(input.axis(Binding.zoom)); 200 | } 201 | 202 | if (input.keyTap(Binding.select) && !scene.hasMouse()) { 203 | buildX = cursorX; 204 | buildY = cursorY; 205 | using = true; 206 | 207 | var scl = renderer.getScale() == Scl.scl(renderer.minZoom) ? renderer.getScale() : Mathf.round(renderer.getScale(), 0.5f); 208 | renderer.minZoom = renderer.maxZoom = scl / Scl.scl(); // a crutch to lock camera zoom 209 | } 210 | 211 | if (input.keyRelease(Binding.select) || input.keyTap(Binding.deselect) || input.keyTap(Binding.breakBlock)) { 212 | using = false; 213 | build.plan.clear(); 214 | m_settings.apply(); 215 | } 216 | } 217 | 218 | public boolean hasMoved(int x, int y) { 219 | return lastX != x || lastY != y || lastSize != build.size; 220 | } 221 | 222 | public void changePanSpeed(float value) { 223 | panSpeed = 4.5f * value / 4f; 224 | panBoostSpeed = 15f * Mathf.sqrt(value / 4f + .1f); 225 | } 226 | 227 | public void lockMovement() { 228 | movementLocked = !movementLocked; 229 | } 230 | 231 | // there is nothing because, you know, it's desktop 232 | public void lockShooting() {} 233 | 234 | public void observe(Player target) { 235 | observed = target; 236 | } 237 | 238 | public void flush(Seq plans) { 239 | flushPlans(plans); 240 | } 241 | 242 | public InputHandler asHandler() { 243 | return this; 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/java/scheme/ui/PlayerListFragment.java: -------------------------------------------------------------------------------- 1 | package scheme.ui; 2 | 3 | import arc.graphics.Color; 4 | import arc.graphics.g2d.Draw; 5 | import arc.graphics.g2d.Lines; 6 | import arc.scene.Element; 7 | import arc.scene.Group; 8 | import arc.scene.event.*; 9 | import arc.scene.ui.*; 10 | import arc.scene.ui.ImageButton.ImageButtonStyle; 11 | import arc.scene.ui.layout.Scl; 12 | import arc.scene.ui.layout.Table; 13 | import arc.struct.Seq; 14 | import arc.util.Scaling; 15 | import arc.util.Strings; 16 | import arc.util.Structs; 17 | import mindustry.game.Team; 18 | import mindustry.gen.Call; 19 | import mindustry.gen.Groups; 20 | import mindustry.gen.Icon; 21 | import mindustry.gen.Player; 22 | import mindustry.gen.Tex; 23 | import mindustry.graphics.Pal; 24 | import mindustry.net.Packets.AdminAction; 25 | import mindustry.ui.Styles; 26 | import mindustry.ui.dialogs.BaseDialog; 27 | import scheme.ServerIntegration; 28 | 29 | import static arc.Core.*; 30 | import static mindustry.Vars.*; 31 | import static scheme.SchemeVars.*; 32 | 33 | /** Last update - Jun 12, 2023 */ 34 | public class PlayerListFragment extends mindustry.ui.fragments.PlayerListFragment { 35 | 36 | public boolean show; 37 | public TextField search; 38 | 39 | @Override 40 | public void build(Group parent) { 41 | super.build(parent); 42 | ui.hudGroup.getChildren().remove(11); 43 | 44 | search = getSearch(); 45 | Table pane = getPane(), menu = getMenu(); 46 | 47 | pane.row(); 48 | pane.check("@trace.type.show", value -> show = value).pad(10f).padBottom(0f).left().row(); 49 | menu.getCells().get(1).padLeft(10f).padRight(10f); // looks better 50 | 51 | pane.getCells().insert(3, pane.getCells().remove(4)); 52 | pane.row(); // idk why but it crashes without it 53 | } 54 | 55 | @Override 56 | public void rebuild() { 57 | if (TooltipLocker.locked || search == null) return; // tooltips may bug during rebuild 58 | 59 | content.clear(); 60 | float h = 50f; 61 | 62 | Seq players = new Seq<>(); 63 | Groups.player.copy(players); 64 | 65 | players.sort(Structs.comps(Structs.comparing(Player::team), Structs.comparingBool(p -> !p.admin))); 66 | if (search.getText().length() > 0) players.removeAll(p -> !Strings.stripColors(p.name().toLowerCase()).contains(search.getText().toLowerCase())); 67 | 68 | if (players.isEmpty()) content.add("@players.notfound").padBottom(6f).width(350f).maxHeight(h + 14); 69 | else for (Player user : players) { 70 | if (user.con == null && net.server() && !user.isLocal()) return; 71 | ClickListener listener = new ClickListener(); 72 | 73 | Table table = new Table() { 74 | @Override 75 | public void draw() { 76 | super.draw(); 77 | Draw.colorMul(user.team().color, listener.isOver() ? 1.3f : 1f); 78 | Draw.alpha(parentAlpha); 79 | Lines.stroke(Scl.scl(4f)); 80 | Lines.rect(x, y, width, height); 81 | Draw.reset(); 82 | } 83 | }; 84 | 85 | table.margin(8f); 86 | table.add(new Image(user.icon()).setScaling(Scaling.bounded)).grow(); 87 | table.touchable = Touchable.enabled; 88 | 89 | table.addListener(listener); // viewing players is always available 90 | table.addListener(new HandCursorListener()); 91 | table.addListener(new TooltipLocker(user.id)); 92 | 93 | table.tapped(() -> { 94 | if (user.dead()) return; 95 | 96 | m_input.observe(user); 97 | ui.showInfoFade(bundle.format("viewplayer", user.name), 1f); 98 | }); 99 | 100 | Table button = new Table(); 101 | button.left().margin(5f).marginBottom(10f); 102 | button.background(show && ServerIntegration.isModded(user.id) ? Tex.underlineOver : Tex.underline); 103 | 104 | button.add(table).size(h); 105 | button.labelWrap(user.coloredName()).style(Styles.outlineLabel).width(170f).pad(10f); 106 | button.add().grow(); 107 | 108 | var ustyle = new ImageButtonStyle() {{ 109 | down = up = Styles.none; 110 | imageDownColor = Pal.accent; 111 | imageUpColor = Color.white; 112 | imageOverColor = Color.lightGray; 113 | }}; 114 | 115 | if (!user.isLocal()) { 116 | button.add().growY(); 117 | button.table(t -> { 118 | t.defaults().size(35f, h); 119 | 120 | t.button(Icon.logic, ustyle, () -> ai.gotoppl(user)); 121 | t.button(Icon.copy, ustyle, () -> { 122 | app.setClipboardText(user.coloredName()); 123 | ui.showInfoFade("@copied"); 124 | }); 125 | t.button(atlas.drawable("status-blasted"), ustyle, () -> admins.despawn(user)); 126 | }).padRight(12f).size(105f, h); 127 | } 128 | 129 | if (user.admin) button.image(Icon.admin).size(h); 130 | 131 | if (net.server() || (player.admin && (!user.admin || user == player))) { 132 | button.add().growY(); 133 | button.button(Icon.menu, ustyle, () -> { 134 | var dialog = new BaseDialog(user.coloredName()); 135 | dialog.setFillParent(false); 136 | dialog.title.getStyle().fontColor = Color.white; 137 | 138 | var btns = dialog.buttons; 139 | btns.defaults().size(220f, 55f).pad(4f); 140 | 141 | if (user != player) { 142 | btns.button("@player.ban", Icon.hammer, Styles.defaultt, () -> { 143 | ui.showConfirm("@confirm", bundle.format("confirmban", user.name()), () -> Call.adminRequest(user, AdminAction.ban, null)); 144 | dialog.hide(); 145 | }).row(); 146 | 147 | btns.button("@player.kick", Icon.cancel, Styles.defaultt, () -> { 148 | ui.showConfirm("@confirm", bundle.format("confirmkick", user.name()), () -> Call.adminRequest(user, AdminAction.kick, null)); 149 | dialog.hide(); 150 | }).row(); 151 | 152 | btns.button("@player.trace", Icon.zoom, Styles.defaultt, () -> { 153 | Call.adminRequest(user, AdminAction.trace, null); 154 | dialog.hide(); 155 | }).row(); 156 | } 157 | 158 | btns.button("@player.team", Icon.redo, Styles.defaultt, () -> { 159 | dialog.hide(); 160 | 161 | var select = new BaseDialog("@player.team"); 162 | select.setFillParent(false); 163 | 164 | int i = 0; 165 | for (Team team : Team.baseTeams) { 166 | select.cont.button(Tex.whiteui, Styles.clearNoneTogglei, () -> { 167 | Call.adminRequest(user, AdminAction.switchTeam, team); 168 | select.hide(); 169 | }).with(b -> { 170 | b.getStyle().imageUpColor = team.color; 171 | b.getImageCell().size(44f); 172 | }).margin(4f).checked(ib -> user.team() == team); 173 | 174 | if (i++ % 3 == 2) select.cont.row(); 175 | } 176 | 177 | select.addCloseButton(); 178 | select.show(); 179 | }).row(); 180 | 181 | if (!net.client() && !user.isLocal()) { 182 | btns.button("@player.admin", Icon.admin, Styles.togglet, () -> { 183 | dialog.hide(); 184 | 185 | if (user.admin) { 186 | ui.showConfirm("@confirm", bundle.format("confirmunadmin", user.name()), () -> { 187 | netServer.admins.unAdminPlayer(user.uuid()); 188 | user.admin = false; 189 | }); 190 | } else { 191 | ui.showConfirm("@confirm", bundle.format("confirmadmin", user.name()), () -> { 192 | netServer.admins.adminPlayer(user.uuid(), user.usid()); 193 | user.admin = true; 194 | }); 195 | } 196 | }).checked(b -> user.admin).row(); 197 | } 198 | 199 | btns.button("@back", Icon.left, dialog::hide); 200 | dialog.show(); 201 | }).size(h); 202 | } else if (!user.isLocal() && !user.admin && net.client() && Groups.player.size() >= 3 && player.team() == user.team()) { 203 | button.add().growY(); 204 | button.button(Icon.hammer, ustyle, 205 | () -> ui.showTextInput("@votekick.reason", bundle.format("votekick.reason.message", user.name()), "", 206 | reason -> Call.sendChatMessage("/votekick #" + user.id + " " + reason))) 207 | .size(h); 208 | } 209 | 210 | content.add(button).width(350f + 117f).height(h + 14f); 211 | content.row(); 212 | } 213 | } 214 | 215 | private Table getPane() { 216 | return ((Table) ((Table) ui.hudGroup.find("playerlist")).getChildren().get(0)); 217 | } 218 | 219 | private TextField getSearch() { 220 | return (TextField) getPane().getChildren().get(1); 221 | } 222 | 223 | private Table getMenu() { 224 | return (Table) getPane().getChildren().get(3); 225 | } 226 | 227 | public static class TooltipLocker extends Tooltip { 228 | 229 | public static boolean locked; 230 | 231 | public TooltipLocker(int id) { 232 | this(ServerIntegration.tooltip(id)); 233 | } 234 | 235 | public TooltipLocker(String text) { 236 | super(table -> table.background(Styles.black6).margin(4f).add(text)); 237 | allowMobile = true; // why is it false by default? 238 | } 239 | 240 | @Override 241 | public void show(Element element, float x, float y) { 242 | super.show(element, x, y); 243 | locked = true; 244 | } 245 | 246 | @Override 247 | public void hide() { 248 | super.hide(); 249 | locked = false; 250 | } 251 | 252 | @Override 253 | public void exit(InputEvent event, float x, float y, int pointer, Element toActor) { 254 | if (toActor != null && toActor.isDescendantOf(event.listenerActor)) return; 255 | hide(); // hide on exit even if on mobile 256 | } 257 | } 258 | } --------------------------------------------------------------------------------