├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src └── main │ ├── resources │ └── plugin.hjson │ └── java │ └── fr │ └── zetamap │ └── morecommands │ ├── module │ ├── SaveableModule.java │ ├── Module.java │ ├── AbstractSaveableModule.java │ └── AbstractModule.java │ ├── modules │ ├── selector │ │ ├── properties │ │ │ ├── FamilyProperty.java │ │ │ ├── LimitProperty.java │ │ │ ├── VolumeXProperty.java │ │ │ ├── XProperty.java │ │ │ ├── YProperty.java │ │ │ ├── VolumeYProperty.java │ │ │ ├── SortProperty.java │ │ │ ├── RotationProperty.java │ │ │ ├── DistanceProperty.java │ │ │ ├── TypeProperty.java │ │ │ ├── DataProperty.java │ │ │ ├── HasitemProperty.java │ │ │ ├── UnitProperty.java │ │ │ └── TeamProperty.java │ │ ├── Sorting.java │ │ ├── SelectorProperties.java │ │ ├── SelectorProperty.java │ │ ├── SelectorModule.java │ │ ├── Selectors.java │ │ └── Selector.java │ ├── effect │ │ └── Effects.java │ ├── security │ │ ├── CrackedClientsModule.java │ │ ├── PunishmentDuration.java │ │ ├── AdminUsidModule.java │ │ ├── Punishment.java │ │ ├── ReservedNamesModule.java │ │ └── AntiEvadeModule.java │ ├── voting │ │ ├── PlayerVoteSession.java │ │ └── VoteNewWaveSession.java │ ├── server │ │ ├── PreConnect.java │ │ └── Server.java │ ├── help │ │ └── HelpModule.java │ ├── godmode │ │ └── GodmodeModule.java │ ├── tp │ │ └── TeleportModule.java │ └── manager │ │ └── ManagerModule.java │ ├── misc │ ├── UnitFamily.java │ ├── UnitCategory.java │ ├── RangeParser.java │ ├── MCEvents.java │ ├── Range.java │ └── CoordinatesParser.java │ ├── util │ ├── Timekeeper.java │ ├── IntervalProv.java │ ├── VersionChecker.java │ ├── Autosaver.java │ └── Logger.java │ ├── command │ ├── ServerCommandHandler.java │ └── ClientCommandHandler.java │ ├── Main.java │ └── Modules.java ├── changelog.md ├── .github └── workflows │ └── build.yml ├── gradlew.bat ├── .gitignore ├── gradlew └── README.md /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZetaMap/moreCommands/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/main/resources/plugin.hjson: -------------------------------------------------------------------------------- 1 | name: more-commands 2 | displayName: More Commands 3 | author: ZetaMap 4 | main: fr.zetamap.morecommands.Main 5 | description: This mindustry plugin adds a bunch of commands (60+) to your server. 6 | version: "v12" 7 | minGameVersion: "151" 8 | java: true 9 | hidden: true 10 | repo: ZetaMap/MoreCommands 11 | softDependencies: [ 12 | simple-blacklist, 13 | anti-vpn-service, 14 | slf4md 15 | ] -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | * The entire plugin has been refactored, cleaned up and optimized. 2 | * Removed the anti-vpn system in favor of the [Anti-VPN-Service plugin](https://github.com/xpdustry/Anti-VPN-Service).
3 | The configuration will be migrated automatically to this one, if it's present. 4 | * The blacklist system is removed since the 11.4 update, but now the configuration will be migrated automatically to [Anti-VPN-Service](https://github.com/xpdustry/Anti-VPN-Service) and [Simple-Blacklist](https://github.com/xpdustry/Simple-Blacklist), if these plugins are present. 5 | * New configuration system, storing settings to independent files in ``config/mods/more-commands``.
6 | The current configuration will be migrated automatically at server startup. 7 | * Removed Admin logs because it's was a mess and nobody use it. 8 | * The plugin has been split into independent and controllable modules with the ``morecommands`` command. 9 | * New punishment system with reason and duration. 10 | * New commands. *(e.g. ``/place``, ``/fill``, ``/freeze``, etc)* 11 | * Removed useless commands. 12 | * Added events to track a bit what the plugin is doing. 13 | * API friendly modules: You can now expend this plugin to add more modules to the ``ModuleFactory`` or custom rules to the ``Gatekeeper``. 14 | * Added minecraft-like target selectors *(e.g. ``@p``, ``@a``, ``@e``)* 15 | * Added minecraft-like coordinates *(e.g. ``~``, ``~-10``, ``~,~``, ``~-10,~``)* 16 | * Changed license from MIT to GPL-3 17 | * Fixed GodMode instant build 18 | * Added GodMode instant unit/building kill 19 | * Added more commands 20 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/module/SaveableModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.module; 20 | 21 | import fr.zetamap.morecommands.util.Autosaver.Saveable; 22 | 23 | 24 | public interface SaveableModule extends Module, Saveable { 25 | /** {@inheritDoc} */ 26 | String name(); 27 | 28 | /** @return whether the module's content has been modified. */ 29 | boolean modified(); 30 | 31 | /** Loads the settings. Can be called multiple times, e.g. to reload the configuration. */ 32 | void load(); 33 | 34 | /** 35 | * @return whether the module parameters have been loaded or not.
36 | * This does not guarantee that the module has been initialized. 37 | */ 38 | boolean loaded(); 39 | 40 | /** Save the settings. */ 41 | void save(); 42 | } 43 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ "**" ] 6 | tags-ignore: [ "**" ] 7 | pull_request: 8 | release: 9 | types: [published, edited] 10 | 11 | jobs: 12 | build: 13 | name: "Build" 14 | # Only run on PRs if the source branch is on someone else's repo 15 | if: ${{ github.event_name != 'pull_request' || github.repository != github.event.pull_request.head.repo.full_name }} 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout Repository 20 | uses: actions/checkout@v4 21 | 22 | - name: Set up JDK 17 23 | uses: actions/setup-java@v4 24 | with: 25 | java-version: 17 26 | distribution: 'temurin' 27 | 28 | - name: Setup Gradle 29 | uses: gradle/actions/setup-gradle@v4 30 | 31 | - name: Build Artifact 32 | run: gradle build 33 | 34 | - name: Upload Artifact to Actions 35 | uses: actions/upload-artifact@v4 36 | with: 37 | name: "${{ github.event.repository.name }}-artifacts" 38 | path: build/libs/*.jar 39 | if-no-files-found: error 40 | 41 | - name: Publish Artifact 42 | if: ${{ github.event_name == 'release' }} 43 | run: gradle publish 44 | env: 45 | ORG_GRADLE_PROJECT_xpdustryUsername: "${{ secrets.XPDUSTRY_MAVEN_USERNAME }}" 46 | ORG_GRADLE_PROJECT_xpdustryPassword: "${{ secrets.XPDUSTRY_MAVEN_PASSWORD }}" 47 | ORG_GRADLE_PROJECT_signingKey: "${{ secrets.ZETAMAP_MAVEN_SIGNING_KEY }}" 48 | ORG_GRADLE_PROJECT_signingPassword: "${{ secrets.ZETAMAP_MAVEN_SIGNING_PASSWORD }}" 49 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/selector/properties/FamilyProperty.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.selector.properties; 20 | 21 | import arc.math.geom.Vec2; 22 | import fr.zetamap.morecommands.modules.selector.Selector; 23 | import fr.zetamap.morecommands.modules.selector.SelectorProperty; 24 | import fr.zetamap.morecommands.util.StringReader; 25 | import mindustry.gen.Player; 26 | import mindustry.gen.Unit; 27 | 28 | 29 | /** WIP */ 30 | public class FamilyProperty extends SelectorProperty { 31 | @Override 32 | public Parsed read(StringReader reader) { 33 | return new Parsed(); 34 | } 35 | 36 | 37 | public class Parsed extends SelectorProperty.Parsed { 38 | @Override 39 | public boolean passes(Selector selector, Player executor, Vec2 pos, Unit entity) { 40 | return false; 41 | } 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/misc/UnitFamily.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.misc; 20 | 21 | 22 | /** Map of all family of units, as defined in {@link UnitTypes}. WIP. */ 23 | public enum UnitFamily { 24 | /* 25 | * block 26 | * ground 27 | * attack 28 | * support 29 | * tank 30 | * legs 31 | * air 32 | * naval 33 | * core 34 | * serpulo 35 | * serpulo:ground 36 | * serpulo:ground:attack 37 | * serpulo:ground:support 38 | * serpulo:ground:legs, serpulo:legs 39 | * serpulo:air 40 | * serpulo:air:attack 41 | * serpulo:air:support 42 | * serpulo:naval 43 | * serpulo:naval:attack 44 | * serpulo:naval:support 45 | * serpulo:core 46 | * erekir 47 | * erekir:ground:tank, erekir:tank 48 | * erekir:ground:attack 49 | * erekir:ground:support 50 | * erekir:mech 51 | * erekir:air 52 | * erekir:neoplasm 53 | * erekir:core 54 | * erekir:drone 55 | */ 56 | } -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/module/Module.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.module; 20 | 21 | import arc.util.Disposable; 22 | 23 | import fr.zetamap.morecommands.command.*; 24 | 25 | 26 | public interface Module extends Disposable { 27 | /** Initialize the module. */ 28 | void init(); 29 | 30 | /** @return whether the module is initialized or not. */ 31 | boolean initialized(); 32 | 33 | /** The module name, used for logging. Default is capitalized class name without the 'Module' part. */ 34 | String name(); 35 | 36 | /** The module {@link #name()}, without spaces and in lower case. */ 37 | String internalName(); 38 | 39 | /** Register any commands to be used on the server side, e.g. from the console. */ 40 | void registerServerCommands(ServerCommandHandler handler); 41 | 42 | /** Register any commands to be used on the client side, e.g. sent from an in-game player. */ 43 | void registerClientCommands(ClientCommandHandler handler); 44 | 45 | /** {@inheritDoc} */ 46 | void dispose(); 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/util/Timekeeper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.util; 20 | 21 | import arc.util.Time; 22 | 23 | 24 | /** Improved version of {@link arc.util.Timekeeper}. */ 25 | public class Timekeeper{ 26 | private final long intervalms; 27 | private long time; 28 | 29 | public Timekeeper(long ms) { 30 | intervalms = ms; 31 | } 32 | 33 | public Timekeeper(float seconds) { 34 | intervalms = (long)(seconds * 1000); 35 | } 36 | 37 | public long interval() { 38 | return intervalms; 39 | } 40 | 41 | @Deprecated 42 | public boolean get() { 43 | return exceeded(); 44 | } 45 | 46 | public boolean exceeded() { 47 | return elapsed() > intervalms; 48 | } 49 | 50 | public long elapsed() { 51 | return Time.timeSinceMillis(time); 52 | } 53 | 54 | public long remaining() { 55 | return Math.max(intervalms - elapsed(), 0); 56 | } 57 | 58 | public long last() { 59 | return time; 60 | } 61 | 62 | public void reset() { 63 | time = Time.millis(); 64 | } 65 | 66 | public void zero() { 67 | time = 0; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/effect/Effects.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2021-2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.effect; 20 | 21 | import mindustry.entities.Effect; 22 | 23 | 24 | /** Class that adds some properties to an effect */ 25 | public class Effects { 26 | protected final EffectsModule module; 27 | 28 | public final Effect effect; 29 | public final String name; 30 | public final int id; 31 | public final boolean needsResizement; 32 | protected boolean disabled, adminOnly; 33 | 34 | public Effects(EffectsModule module, Effect effect, String name, boolean needsResizement) { 35 | this.module = module; 36 | this.effect = effect; 37 | this.name = name; 38 | this.id = effect.id; 39 | this.needsResizement = needsResizement; 40 | } 41 | 42 | public boolean disabled() { return disabled; } 43 | public void disabled(boolean disabled) { 44 | this.disabled = disabled; 45 | this.module.setModified0(); 46 | } 47 | public boolean adminOnly() { return adminOnly; } 48 | public void adminOnly(boolean adminOnly) { 49 | this.adminOnly = adminOnly; 50 | this.module.setModified0(); 51 | } 52 | } -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/selector/Sorting.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.selector; 20 | 21 | import arc.func.Cons2; 22 | import arc.math.Mathf; 23 | import arc.math.geom.Position; 24 | import arc.struct.Seq; 25 | 26 | import mindustry.gen.Unit; 27 | 28 | 29 | public enum Sorting { 30 | // mindustry.entities.UnitSorts 31 | 32 | /** Sorts by nearest unit. */ 33 | nearest((l, p) -> l.sort(u -> u.dst2(p))), 34 | /** Sorts by furthest unit. */ 35 | furthest((l, p) -> l.sort(u -> -u.dst(p))), 36 | /** Sorts by the strongest unit. */ 37 | strongest((l, p) -> l.sort(u -> -u.maxHealth + Mathf.dst2(u.x, u.y, p.getX(), p.getY()) / 6400f)), 38 | /** Sorts by the weakest unit. */ 39 | weakest((l, p) -> l.sort(u -> u.maxHealth + Mathf.dst2(u.x, u.y, p.getX(), p.getY()) / 6400f)), 40 | /** Shuffle the list. */ 41 | random((l, p) -> l.shuffle()), 42 | /** Sort by id. */ 43 | arbitrary((l, p) -> l.sort(u -> u.id)), 44 | // /** No sorting. */ 45 | // none((l, p) -> {}), 46 | ; 47 | 48 | final Cons2, Position> sorter; 49 | Sorting(Cons2, Position> sorter) { this.sorter = sorter; } 50 | 51 | public void sort(Seq units, Position pos) { 52 | sorter.get(units, pos); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/selector/properties/LimitProperty.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.selector.properties; 20 | 21 | import arc.math.geom.Vec2; 22 | import arc.struct.Seq; 23 | import fr.zetamap.morecommands.modules.selector.Selector; 24 | import fr.zetamap.morecommands.modules.selector.SelectorProperty; 25 | import fr.zetamap.morecommands.util.StringReader; 26 | import mindustry.gen.Player; 27 | import mindustry.gen.Unit; 28 | 29 | 30 | public class LimitProperty extends SelectorProperty { 31 | public LimitProperty() { super(Category.limiting); } 32 | 33 | @Override 34 | public Parsed read(StringReader reader) { 35 | int limit = (int)reader.readNumeric(true); 36 | if (limit < 1) throw reader.error("Limit must be greater than 1"); 37 | return new Parsed(limit); 38 | } 39 | 40 | 41 | public class Parsed extends SelectorProperty.Parsed { 42 | public final int limit; 43 | public Parsed(int limit) { this.limit = limit; } 44 | 45 | @Override 46 | public boolean passes(Selector selector, Player executor, Vec2 pos, Unit entity) { 47 | return true; 48 | } 49 | 50 | @Override 51 | public void update(Selector selector, Seq entities, Player executor, Vec2 pos) { 52 | entities.truncate(limit); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/selector/properties/VolumeXProperty.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.selector.properties; 20 | 21 | import arc.math.geom.Vec2; 22 | 23 | import mindustry.Vars; 24 | import mindustry.gen.Player; 25 | import mindustry.gen.Unit; 26 | 27 | import fr.zetamap.morecommands.modules.selector.Selector; 28 | import fr.zetamap.morecommands.modules.selector.SelectorProperty; 29 | import fr.zetamap.morecommands.util.StringReader; 30 | 31 | /** Accepted formats: {@code dx=10} (normal volume x) or {@code dx=10w} (world scaled volume x: {@code 10*8=80}). */ 32 | public class VolumeXProperty extends SelectorProperty { 33 | public VolumeXProperty() { super("dx", Category.bounding); } 34 | 35 | @Override 36 | public Parsed read(StringReader reader) { 37 | int x = reader.readNumeric(); 38 | if (reader.peekNext() != 'w') return new Parsed(x * Vars.tilesize); 39 | reader.readNext(); 40 | return new Parsed(x); 41 | } 42 | 43 | 44 | public class Parsed extends SelectorProperty.Parsed { 45 | public final float dx; 46 | public Parsed(float dx) { this.dx = dx; } 47 | 48 | @Override 49 | public boolean passes(Selector selector, Player executor, Vec2 pos, Unit entity) { 50 | float min = Math.min(pos.x, dx), max = Math.max(pos.x, dx), 51 | x = entity.getX(), r = entity.hitSize / 2f; 52 | return x + r >= min && x - r <= max; 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/selector/properties/XProperty.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.selector.properties; 20 | 21 | import arc.math.geom.Vec2; 22 | import arc.struct.Seq; 23 | 24 | import mindustry.Vars; 25 | import mindustry.gen.Player; 26 | import mindustry.gen.Unit; 27 | 28 | import fr.zetamap.morecommands.modules.selector.Selector; 29 | import fr.zetamap.morecommands.modules.selector.SelectorProperty; 30 | import fr.zetamap.morecommands.util.StringReader; 31 | 32 | 33 | /** Accepted formats: {@code x=5} (normal x) or {@code x=5w} (world scaled x: {@code 5*8=40}). */ 34 | public class XProperty extends SelectorProperty { 35 | public XProperty() { super(Category.positioning); } 36 | 37 | @Override 38 | public Parsed read(StringReader reader) { 39 | int x = reader.readNumeric(); 40 | if (reader.peekNext() != 'w') return new Parsed(x * Vars.tilesize); 41 | reader.readNext(); 42 | return new Parsed(x); 43 | } 44 | 45 | 46 | public class Parsed extends SelectorProperty.Parsed { 47 | public final float x; 48 | public Parsed(float x) { this.x = x; } 49 | 50 | @Override 51 | public boolean passes(Selector selector, Player executor, Vec2 pos, Unit entity) { 52 | return true; 53 | } 54 | 55 | @Override 56 | public void update(Selector selector, Seq entities, Player executor, Vec2 pos) { 57 | pos.x = x; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/selector/properties/YProperty.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.selector.properties; 20 | 21 | import arc.math.geom.Vec2; 22 | import arc.struct.Seq; 23 | 24 | import mindustry.Vars; 25 | import mindustry.gen.Player; 26 | import mindustry.gen.Unit; 27 | 28 | import fr.zetamap.morecommands.modules.selector.Selector; 29 | import fr.zetamap.morecommands.modules.selector.SelectorProperty; 30 | import fr.zetamap.morecommands.util.StringReader; 31 | 32 | 33 | /** Accepted formats: {@code y=5} (normal y) or {@code y=5w} (world scaled y: {@code 5*8=40}). */ 34 | public class YProperty extends SelectorProperty { 35 | public YProperty() { super(Category.positioning); } 36 | 37 | @Override 38 | public Parsed read(StringReader reader) { 39 | int y = reader.readNumeric(); 40 | if (reader.peekNext() != 'w') return new Parsed(y * Vars.tilesize); 41 | reader.readNext(); 42 | return new Parsed(y); 43 | } 44 | 45 | 46 | public class Parsed extends SelectorProperty.Parsed { 47 | public final float y; 48 | public Parsed(float y) { this.y = y; } 49 | 50 | @Override 51 | public boolean passes(Selector selector, Player executor, Vec2 pos, Unit entity) { 52 | return true; 53 | } 54 | 55 | @Override 56 | public void update(Selector selector, Seq entities, Player executor, Vec2 pos) { 57 | pos.y = y; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/security/CrackedClientsModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.security; 20 | 21 | import arc.struct.ObjectSet; 22 | 23 | import fr.zetamap.morecommands.misc.Gatekeeper; 24 | import fr.zetamap.morecommands.module.AbstractModule; 25 | 26 | 27 | /** Go figure why but some people are using cracked clients (of the Steam version) on a free game... Incredible. */ 28 | public class CrackedClientsModule extends AbstractModule { 29 | /** List of all known cracked clients in lower case. */ 30 | public final ObjectSet crackedClientUsernames = ObjectSet.with( 31 | "valve", "tuttop", "codex", "igggames", "igg-games.com", "igruhaorg", "freetp.org", "goldberg", "rog" 32 | ); 33 | public String crackedClientsKickMessage = "[green]Mindustry is a free and open source game.[]\n" 34 | + "[white]It is available on: [royal]https://anuke.itch.io/mindustry[].[]\n\n" 35 | + "[red]Please, get a legit copy of the game.[]"; 36 | 37 | public boolean isCrackedClient(String username) { 38 | return crackedClientUsernames.contains(username.toLowerCase()); 39 | } 40 | 41 | @Override 42 | protected void initImpl() { 43 | Gatekeeper.add(internalName(), Gatekeeper.Priority.low, 44 | ctx -> isCrackedClient(ctx.strippedName) ? Gatekeeper.reject(crackedClientsKickMessage) : Gatekeeper.accept()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/selector/properties/VolumeYProperty.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.selector.properties; 20 | 21 | import arc.math.geom.Vec2; 22 | 23 | import mindustry.Vars; 24 | import mindustry.gen.Player; 25 | import mindustry.gen.Unit; 26 | 27 | import fr.zetamap.morecommands.modules.selector.Selector; 28 | import fr.zetamap.morecommands.modules.selector.SelectorProperty; 29 | import fr.zetamap.morecommands.util.StringReader; 30 | 31 | 32 | /** Accepted formats: {@code dy=10} (normal volume y) or {@code dy=10w} (world scaled volume y: {@code 10*8=80}). */ 33 | public class VolumeYProperty extends SelectorProperty { 34 | public VolumeYProperty() { super("dy", Category.bounding); } 35 | 36 | @Override 37 | public Parsed read(StringReader reader) { 38 | int y = reader.readNumeric(); 39 | if (reader.peekNext() != 'w') return new Parsed(y * Vars.tilesize); 40 | reader.readNext(); 41 | return new Parsed(y); 42 | } 43 | 44 | 45 | public class Parsed extends SelectorProperty.Parsed { 46 | public final float dy; 47 | public Parsed(float dy) { this.dy = dy; } 48 | 49 | @Override 50 | public boolean passes(Selector selector, Player executor, Vec2 pos, Unit entity) { 51 | float min = Math.min(pos.y, this.dy), max = Math.max(pos.y, this.dy), 52 | y = entity.getY(), r = entity.hitSize / 2f; 53 | return y + r >= min && y - r <= max; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/selector/properties/SortProperty.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.selector.properties; 20 | 21 | import arc.math.geom.Vec2; 22 | import arc.struct.Seq; 23 | import mindustry.gen.Player; 24 | import mindustry.gen.Unit; 25 | 26 | import fr.zetamap.morecommands.modules.selector.Selector; 27 | import fr.zetamap.morecommands.modules.selector.SelectorProperty; 28 | import fr.zetamap.morecommands.modules.selector.Sorting; 29 | import fr.zetamap.morecommands.util.StringReader; 30 | 31 | 32 | public class SortProperty extends SelectorProperty { 33 | public SortProperty() { super(Category.sorting); } 34 | 35 | @Override 36 | public Parsed read(StringReader reader) { 37 | String name = reader.readString(); 38 | if (name == null) throw reader.expected("a sorting name"); 39 | try { return new Parsed(Sorting.valueOf(name.toLowerCase())); } 40 | catch (IllegalArgumentException e) { throw reader.notFound("sorting", name); } 41 | } 42 | 43 | 44 | public class Parsed extends SelectorProperty.Parsed { 45 | public final Sorting sort; 46 | public Parsed(Sorting sort) { this.sort = sort; } 47 | 48 | @Override 49 | public boolean passes(Selector selector, Player executor, Vec2 pos, Unit entity) { 50 | return true; 51 | } 52 | 53 | @Override 54 | public void update(Selector selector, Seq entities, Player executor, Vec2 pos) { 55 | sort.sort(entities, pos); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/security/PunishmentDuration.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.security; 20 | 21 | import fr.zetamap.morecommands.util.DurationFormatter.DurationUnit; 22 | 23 | 24 | /** Common punishments duration. */ 25 | public enum PunishmentDuration { 26 | thirtyMinutes(DurationUnit.minute, 30), 27 | oneHour(DurationUnit.hour), 28 | threeHours(DurationUnit.hour, 3), 29 | oneDay(DurationUnit.day), 30 | threeDays(DurationUnit.day, 3), 31 | oneWeek(DurationUnit.week), 32 | oneMonth(DurationUnit.month), 33 | threeMonths(DurationUnit.month, 3 + 1f/30f), //plus one day 34 | oneYear(DurationUnit.year.duration), 35 | permanant(-1); 36 | 37 | public static final PunishmentDuration[] all = values(); 38 | 39 | public final DurationUnit unit; 40 | public final float multiplier; 41 | /** Total duration, after applied the {@link #multiplier}. */ 42 | public final long duration; 43 | 44 | PunishmentDuration(long duration) { 45 | this.unit = null; 46 | this.multiplier = 1; 47 | this.duration = duration; 48 | } 49 | 50 | PunishmentDuration(DurationUnit unit) { this(unit, 1); } 51 | PunishmentDuration(DurationUnit unit, float multiplier) { 52 | this.unit = unit; 53 | this.multiplier = multiplier; 54 | this.duration = (long)(unit.duration * multiplier); 55 | } 56 | 57 | public static PunishmentDuration of(long duration) { 58 | for (PunishmentDuration u : all) { 59 | if (u.duration == duration) return u; 60 | } 61 | return null; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/module/AbstractSaveableModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.module; 20 | 21 | import arc.util.serialization.Json; 22 | 23 | import fr.zetamap.morecommands.util.JsonSettings; 24 | 25 | 26 | public abstract class AbstractSaveableModule extends AbstractModule implements SaveableModule { 27 | private boolean modified, loaded; 28 | 29 | protected void setModified() { 30 | modified = true; 31 | } 32 | 33 | @Override 34 | public boolean modified() { 35 | return modified; 36 | } 37 | 38 | @Override 39 | public boolean loaded() { 40 | return loaded; 41 | } 42 | 43 | protected void addSerializer(Class clazz, Json.Serializer serializer) { 44 | ModuleFactory.getSettings(this, true).getJson().setSerializer(clazz, serializer); 45 | } 46 | 47 | @Override 48 | public final void load() { 49 | if (!ModuleFactory.enabled(this)) return; 50 | boolean wasModified = modified; // Avoid to suppress modified status if sets while loading 51 | JsonSettings settings = ModuleFactory.getSettings(this, true); 52 | settings.load(); 53 | loadImpl(settings); 54 | loaded = true; 55 | if (wasModified) modified = false; 56 | } 57 | 58 | @Override 59 | public final void save() { 60 | if (!modified() || !loaded) return; 61 | JsonSettings settings = ModuleFactory.getSettings(this, true); 62 | saveImpl(settings); 63 | settings.save(); 64 | modified = false; 65 | } 66 | 67 | protected abstract void loadImpl(JsonSettings settings); 68 | protected abstract void saveImpl(JsonSettings settings); 69 | } -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/util/IntervalProv.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.util; 20 | 21 | import java.util.Arrays; 22 | 23 | import arc.func.Prov; 24 | import arc.util.Interval; 25 | 26 | 27 | /** Return the last {@link Prov} result until new check. */ 28 | public class IntervalProv extends Interval { 29 | Object[] results; 30 | boolean[] hasResults; 31 | 32 | public IntervalProv() { this(1); } 33 | public IntervalProv(int capacity) { 34 | super(capacity); 35 | results = new Object[capacity]; 36 | hasResults = new boolean[capacity]; 37 | } 38 | 39 | public T get(float time, Prov prov) { 40 | return get(0, time, prov); 41 | } 42 | 43 | @SuppressWarnings("unchecked") 44 | public T get(int id, float time, Prov prov) { 45 | if (get(id, time)) { 46 | T result = prov.get(); 47 | results[id] = result; 48 | hasResults[id] = true; 49 | return result; 50 | } 51 | return (T)results[id]; 52 | } 53 | 54 | @Override 55 | public void reset(int id, float time) { 56 | super.reset(id, time); 57 | results[id] = null; 58 | hasResults[id] = false; 59 | } 60 | 61 | @Override 62 | public void clear() { 63 | super.clear(); 64 | Arrays.fill(results, null); 65 | Arrays.fill(hasResults, false); 66 | } 67 | 68 | public boolean hasResult(int id) { 69 | return hasResults[id]; 70 | } 71 | 72 | @SuppressWarnings("unchecked") 73 | public T getResult(int id) { 74 | return (T)results[id]; 75 | } 76 | 77 | public Object[] getResults() { 78 | return results; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/selector/properties/RotationProperty.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.selector.properties; 20 | 21 | import arc.math.geom.Vec2; 22 | 23 | import mindustry.gen.Player; 24 | import mindustry.gen.Unit; 25 | 26 | import fr.zetamap.morecommands.misc.Range; 27 | import fr.zetamap.morecommands.misc.RangeParser; 28 | import fr.zetamap.morecommands.modules.selector.Selector; 29 | import fr.zetamap.morecommands.modules.selector.SelectorProperty; 30 | import fr.zetamap.morecommands.util.StringReader; 31 | 32 | 33 | /** Accepted formats: {@code rotation=}, {@code rotation=90}, {@code rotation=..90}, {@code rotation=45..} or 34 | * {@code rotation=45..90}. */ 35 | public class RotationProperty extends SelectorProperty { 36 | public RotationProperty() { super(Category.bounding); } 37 | 38 | @Override 39 | public Parsed read(StringReader reader) { 40 | return new Parsed(reader.peekWord() == null ? null : RangeParser.parseFloat(reader, false)); 41 | } 42 | 43 | 44 | public class Parsed extends SelectorProperty.Parsed { 45 | /** {@code null} means the executor's rotation. */ 46 | public final Range range; 47 | public Parsed(Range range) { this.range = range; } 48 | 49 | @Override 50 | public boolean passes(Selector selector, Player executor, Vec2 pos, Unit entity) { 51 | return range != null ? range.contains(entity.rotation) : 52 | executor == null || executor.dead() || 53 | // loses precision because the rotations are never completely identical 54 | (int)executor.unit().rotation == (int)entity.rotation; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created with gitignore.io: https://www.toptal.com/developers/gitignore/api/eclipse,gradle,java 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=eclipse,gradle,java 3 | 4 | ### Eclipse ### 5 | .metadata 6 | bin/ 7 | tmp/ 8 | *.tmp 9 | *.bak 10 | *.swp 11 | *~.nib 12 | local.properties 13 | .settings/ 14 | .loadpath 15 | .recommenders 16 | 17 | # External tool builders 18 | .externalToolBuilders/ 19 | 20 | # Locally stored "Eclipse launch configurations" 21 | *.launch 22 | 23 | # PyDev specific (Python IDE for Eclipse) 24 | *.pydevproject 25 | 26 | # CDT-specific (C/C++ Development Tooling) 27 | .cproject 28 | 29 | # CDT- autotools 30 | .autotools 31 | 32 | # Java annotation processor (APT) 33 | .factorypath 34 | 35 | # PDT-specific (PHP Development Tools) 36 | .buildpath 37 | 38 | # sbteclipse plugin 39 | .target 40 | 41 | # Tern plugin 42 | .tern-project 43 | 44 | # TeXlipse plugin 45 | .texlipse 46 | 47 | # STS (Spring Tool Suite) 48 | .springBeans 49 | 50 | # Code Recommenders 51 | .recommenders/ 52 | 53 | # Annotation Processing 54 | .apt_generated/ 55 | .apt_generated_test/ 56 | 57 | # Scala IDE specific (Scala & Java development for Eclipse) 58 | .cache-main 59 | .scala_dependencies 60 | .worksheet 61 | 62 | ### Eclipse Patch ### 63 | # Spring Boot Tooling 64 | .sts4-cache/ 65 | 66 | ### Java ### 67 | # Compiled class file 68 | *.class 69 | 70 | # Log file 71 | *.log 72 | 73 | # BlueJ files 74 | *.ctxt 75 | 76 | # Mobile Tools for Java (J2ME) 77 | .mtj.tmp/ 78 | 79 | # Package Files # 80 | *.jar 81 | *.war 82 | *.nar 83 | *.ear 84 | *.zip 85 | *.tar.gz 86 | *.rar 87 | 88 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 89 | hs_err_pid* 90 | replay_pid* 91 | 92 | ### Gradle ### 93 | .gradle 94 | **/build/ 95 | !src/**/build/ 96 | 97 | # Ignore Gradle GUI config 98 | gradle-app.setting 99 | 100 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 101 | !gradle-wrapper.jar 102 | 103 | # Avoid ignore Gradle wrappper properties 104 | !gradle-wrapper.properties 105 | 106 | # Cache of project 107 | .gradletasknamecache 108 | 109 | # Eclipse Gradle plugin generated files 110 | # Eclipse Core 111 | .project 112 | # JDT-specific (Eclipse Java Development Tools) 113 | .classpath 114 | 115 | ### Gradle Patch ### 116 | # Java heap dump 117 | *.hprof 118 | 119 | # End of https://www.toptal.com/developers/gitignore/api/eclipse,gradle,java 120 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/command/ServerCommandHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.command; 20 | 21 | import arc.func.Cons; 22 | import arc.struct.Seq; 23 | import arc.util.CommandHandler; 24 | import arc.util.CommandHandler.*; 25 | 26 | import fr.zetamap.morecommands.util.Logger; 27 | 28 | 29 | public class ServerCommandHandler { 30 | private static final Logger logger = new Logger("Server Commands"); 31 | 32 | public final CommandHandler handler; 33 | public final Seq all = new Seq<>(); 34 | 35 | public ServerCommandHandler(CommandHandler handler) { 36 | this.handler = handler; 37 | } 38 | 39 | public void add(String name, String desc, Cons runner) { 40 | add(name, "", desc, runner); 41 | } 42 | 43 | public void add(String name, String params, String desc, Cons runner) { 44 | // If the command already exist, try to place the new at the same position 45 | int index = handler.getCommandList().indexOf(c -> c.text.equals(name)); 46 | all.add(handler.register(name, params, desc, args -> { 47 | try { runner.get(args); } 48 | catch (Exception e) { logger.err("Error while running server command '@'", e, name); } 49 | })); 50 | if (index != -1) handler.getCommandList().insert(index, handler.getCommandList().pop()); 51 | } 52 | 53 | public Command get(String name) { 54 | return handler.getCommandList().find(c -> c.text.equals(name)); 55 | } 56 | 57 | public void remove(String name) { 58 | handler.removeCommand(name); 59 | all.remove(c -> c.text.equals(name)); 60 | } 61 | 62 | public void clear() { 63 | all.each(c -> handler.removeCommand(c.text)); 64 | all.clear(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/selector/properties/DistanceProperty.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.selector.properties; 20 | 21 | import arc.math.geom.Vec2; 22 | 23 | import mindustry.Vars; 24 | import mindustry.gen.Player; 25 | import mindustry.gen.Unit; 26 | 27 | import fr.zetamap.morecommands.misc.Range; 28 | import fr.zetamap.morecommands.misc.RangeParser; 29 | import fr.zetamap.morecommands.modules.selector.Selector; 30 | import fr.zetamap.morecommands.modules.selector.SelectorProperty; 31 | import fr.zetamap.morecommands.util.StringReader; 32 | 33 | 34 | /** 35 | * Accepted formats: {@code distance=10}, {@code distance=..10}, {@code distance=10..}, {@code distance=5..10} 36 | * (normal distance).
37 | * Or {@code distance=10w}, {@code distance=..10w}, {@code distance=10..w}, {@code distance=5..10w} 38 | * (world scaled distance: {@code 5*8=40}{@code ..}{@code 10*8=80}). 39 | */ 40 | public class DistanceProperty extends SelectorProperty { 41 | public DistanceProperty() { super(Category.bounding); } 42 | 43 | @Override 44 | public Parsed read(StringReader reader) { 45 | Range range = RangeParser.parseFloat(reader, true); 46 | boolean scaled = reader.peekNext() == 'w'; 47 | if (scaled) reader.readNext(); 48 | return new Parsed(range, scaled); 49 | } 50 | 51 | 52 | public class Parsed extends SelectorProperty.Parsed { 53 | public final Range range; 54 | public final boolean worldScaled; 55 | 56 | public Parsed(Range range, boolean worldScaled) { 57 | this.range = range; 58 | this.worldScaled = worldScaled; 59 | } 60 | 61 | @Override 62 | public boolean passes(Selector selector, Player executor, Vec2 pos, Unit entity) { 63 | return range.contains(worldScaled ? entity.dst(pos) : entity.dst(pos) / Vars.tilesize); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/voting/PlayerVoteSession.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.voting; 20 | 21 | import arc.Events; 22 | 23 | import fr.zetamap.morecommands.PlayerData; 24 | import fr.zetamap.morecommands.misc.MCEvents; 25 | 26 | 27 | /** Class that removes the "people" abstraction layer ({@link PlayerData}) and fires events of done actions. */ 28 | public abstract class PlayerVoteSession extends VoteSession { 29 | public PlayerVoteSession(float duration, float cooldown) { super(duration, cooldown); } 30 | 31 | @Override 32 | public boolean start(PlayerData player, O objective) { 33 | boolean started = super.start(player, objective); 34 | if (started) Events.fire(new MCEvents.VoteSessionStartedEvent(this, player)); 35 | return started; 36 | } 37 | 38 | @Override 39 | protected boolean vote(PlayerData people, VoteType type, boolean silent) { 40 | boolean voted = super.vote(people, type, silent); 41 | if (voted && !silent) Events.fire(new MCEvents.VoteSessionVotedEvent(this, type, people)); 42 | return voted; 43 | } 44 | 45 | public void force() { 46 | if (!canStop()) return; 47 | super.force(); 48 | Events.fire(new MCEvents.VoteSessionClosedEvent(this, null, true)); 49 | } 50 | 51 | public void force(PlayerData player) { 52 | if (!canStop(player)) return; 53 | super.force(player); 54 | Events.fire(new MCEvents.VoteSessionClosedEvent(this, player, true)); 55 | } 56 | 57 | public void cancel() { 58 | if (!canStop()) return; 59 | super.cancel(); 60 | Events.fire(new MCEvents.VoteSessionClosedEvent(this, null, false)); 61 | } 62 | 63 | public void cancel(PlayerData player) { 64 | if (!canStop(player)) return; 65 | super.cancel(player); 66 | Events.fire(new MCEvents.VoteSessionClosedEvent(this, player, false)); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/module/AbstractModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.module; 20 | 21 | import mindustry.mod.Mod; 22 | 23 | import fr.zetamap.morecommands.command.*; 24 | import fr.zetamap.morecommands.util.Logger; 25 | import fr.zetamap.morecommands.util.Strings; 26 | 27 | 28 | public abstract class AbstractModule implements Module { 29 | protected final Logger logger; 30 | private final String name, internalName; 31 | private boolean disposed, initialized; 32 | 33 | { 34 | String n = getClass().getSimpleName(); 35 | n = n.endsWith("Module") ? n.substring(0, n.length()-6) : n; 36 | name = Strings.insertSpaces(n); 37 | internalName = Strings.camelToKebab(n); 38 | 39 | logger = new Logger(name()); 40 | } 41 | 42 | @Override 43 | public String name() { 44 | return name; 45 | } 46 | 47 | @Override 48 | public String internalName() { 49 | return internalName; 50 | } 51 | 52 | /** Register this module to the {@link ModuleFactory}. Do nothing if the module is already registered. */ 53 | public/* final*/ void register(Mod context) { 54 | ModuleFactory.add(context, this); 55 | } 56 | 57 | @Override 58 | public final void init() { 59 | if (initialized()) return; 60 | initImpl(); 61 | initialized = true; 62 | } 63 | 64 | protected void initImpl() {} 65 | 66 | @Override 67 | public boolean initialized() { 68 | return initialized; 69 | } 70 | 71 | @Override 72 | public void registerServerCommands(ServerCommandHandler handler) {} 73 | 74 | @Override 75 | public void registerClientCommands(ClientCommandHandler handler) {} 76 | 77 | @Override 78 | public final void dispose() { 79 | if (isDisposed()) return; 80 | disposeImpl(); 81 | disposed = true; 82 | } 83 | 84 | protected void disposeImpl() {} 85 | 86 | @Override 87 | public boolean isDisposed() { 88 | return disposed; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/selector/SelectorProperties.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.selector; 20 | 21 | import arc.func.Cons; 22 | import arc.struct.ObjectMap; 23 | import arc.struct.Seq; 24 | 25 | import fr.zetamap.morecommands.modules.selector.properties.*; 26 | 27 | 28 | public class SelectorProperties { 29 | private static final ObjectMap all = new ObjectMap<>(); 30 | 31 | public static SelectorProperty 32 | // positioning 33 | positionXProperty, positionYProperty, 34 | // bounding 35 | volumeXProperty, volumeYProperty, distanceProperty, rotationProperty, 36 | // sorting 37 | sortProperty, 38 | // limiter 39 | limitProperty, 40 | // other 41 | unitProperty, typeProperty/*, familyProperty*/, teamProperty/*, dataProperty*/, hasItemProperty 42 | ; 43 | 44 | public static void init() { 45 | positionXProperty = new XProperty(); 46 | positionYProperty = new YProperty(); 47 | volumeXProperty = new VolumeXProperty(); 48 | volumeYProperty = new VolumeYProperty(); 49 | distanceProperty = new DistanceProperty(); 50 | rotationProperty = new RotationProperty(); 51 | sortProperty = new SortProperty(); 52 | limitProperty = new LimitProperty(); 53 | unitProperty = new UnitProperty(); 54 | typeProperty = new TypeProperty(); 55 | //familyProperty = new FamilyProperty(); 56 | teamProperty = new TeamProperty(); 57 | //dataProperty = new DataProperty(); 58 | hasItemProperty = new HasitemProperty(); 59 | } 60 | 61 | public static SelectorProperty get(String property) { 62 | return all.get(property); 63 | } 64 | 65 | /** @throws IllegalArgumentException if another selector property is registered with the same name. */ 66 | public static int add(SelectorProperty property) { 67 | if (all.containsKey(property.name)) 68 | throw new IllegalArgumentException("another selector property is named '"+property.name+"'"); 69 | all.put(property.name, property); 70 | return all.size-1; 71 | } 72 | 73 | public static void each(Cons consumer) { 74 | all.each((n, s) -> consumer.get(s)); 75 | } 76 | 77 | public static Seq all() { 78 | return all.values().toSeq(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/misc/UnitCategory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.misc; 20 | 21 | import arc.func.Boolf; 22 | 23 | import mindustry.Vars; 24 | import mindustry.gen.*; 25 | import mindustry.type.unit.NeoplasmUnitType; 26 | import mindustry.world.blocks.storage.CoreBlock; 27 | 28 | 29 | /** Map of all kind of units, as defined in {@link mindustry.content.UnitTypes} and 30 | * {@link mindustry.mod.ContentParser#unitType(JsonValue)}. */ 31 | public enum UnitCategory { 32 | player(u -> u.isPlayer()), 33 | mech(u -> u instanceof Mechc), 34 | legs(u -> u instanceof Legsc), 35 | air(u -> u.type().flying || u.type().hovering && !u.type().allowLegStep), // if hovering and has no legs, consider flying 36 | payload(u -> u instanceof Payloadc), 37 | naval(u -> u.type().naval), 38 | tank(u -> u instanceof Tankc), 39 | //missile(u -> u instanceof TimedKillc), 40 | tether(u -> u instanceof BuildingTetherc), 41 | //crawl(u -> u instanceof Crawlc), 42 | neoplasm(u -> u.type() instanceof NeoplasmUnitType), 43 | //core(u -> Vars.content.blocks().contains(b -> b instanceof CoreBlock && u.type() == ((CoreBlock)b).unitType)), 44 | core(u -> Vars.state.teams.getActive().contains(t -> t.cores.contains(b -> u.type() == ((CoreBlock)b.block).unitType))), 45 | other(u -> true), 46 | ; 47 | 48 | public static final UnitCategory[] all = values(); 49 | 50 | final Boolf checker; 51 | UnitCategory(Boolf checker) { this.checker = checker; } 52 | 53 | 54 | public boolean sameCategory(Unitc unit) { 55 | for (UnitCategory c : all) { 56 | if (c.checker.get(unit) && c == this) return true; 57 | } 58 | return false; 59 | } 60 | 61 | public static UnitCategory of(String name) { 62 | for (UnitCategory c : all) { 63 | if (c.name().equals(name)) return c; 64 | } 65 | return null; 66 | } 67 | 68 | /** 69 | * Attempts to dynamically determine the unit's category. 70 | *

71 | * This is unreliable, as a unit can belong to multiple categories.
72 | * Consider using a comparison, with {@link #sameCategory(Unitc)}, instead. 73 | */ 74 | public static UnitCategory of(Unitc unit) { 75 | for (UnitCategory c : all) { 76 | if (c.checker.get(unit)) return c; 77 | } 78 | return null; // never happen 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/selector/SelectorProperty.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.selector; 20 | 21 | import arc.math.geom.Vec2; 22 | import arc.struct.Seq; 23 | 24 | import mindustry.gen.Player; 25 | import mindustry.gen.Unit; 26 | 27 | import fr.zetamap.morecommands.util.StringReader; 28 | import fr.zetamap.morecommands.util.Strings; 29 | 30 | 31 | public abstract class SelectorProperty { 32 | public final String name; 33 | public final Category category; 34 | public final int id; 35 | 36 | /** Uses the class name to determine the property name. Class name without {@code "Property"}, in kebab case. */ 37 | public SelectorProperty() { this(Category.other); } 38 | /** Uses the class name to determine the property name. Class name without {@code "Property"}, in kebab case. */ 39 | public SelectorProperty(Category category) { 40 | this.name = Strings.kebabize(getClass().getSimpleName().replace("Property", "")); 41 | this.category = category; 42 | this.id = SelectorProperties.add(this); 43 | } 44 | 45 | public SelectorProperty(String name) { this(name, Category.other); } 46 | public SelectorProperty(String name, Category category) { 47 | this.name = name; 48 | this.category = category; 49 | this.id = SelectorProperties.add(this); 50 | } 51 | 52 | public abstract Parsed read(StringReader reader); 53 | 54 | 55 | /** Used to sort properties or override the default selector's behavior. */ 56 | public static enum Category { 57 | // Order is important 58 | positioning, bounding, other, sorting, limiting; 59 | } 60 | 61 | 62 | public abstract class Parsed { 63 | public final SelectorProperty property = SelectorProperty.this; 64 | 65 | /** Note that {@code executor} can be {@code null} (e.g. when running from console). */ 66 | public abstract boolean passes(Selector selector, Player executor, Vec2 pos, Unit entity); 67 | /** 68 | * Called before or after entities selection, depending on {@link SelectorProperty#category}.
69 | * Note that {@code entities} is an unordered list for optimization purposes, and {@code executor} 70 | * can be {@code null} (e.g. when running from console).
71 | */ 72 | public void update(Selector selector, Seq entities, Player executor, Vec2 pos) {} 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/selector/properties/TypeProperty.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.selector.properties; 20 | 21 | import arc.math.geom.Vec2; 22 | import arc.struct.Seq; 23 | 24 | import mindustry.gen.Player; 25 | import mindustry.gen.Unit; 26 | 27 | import fr.zetamap.morecommands.misc.UnitCategory; 28 | import fr.zetamap.morecommands.modules.selector.Selector; 29 | import fr.zetamap.morecommands.modules.selector.SelectorProperty; 30 | import fr.zetamap.morecommands.util.StringReader; 31 | 32 | 33 | // Doesn't allow empty category because determination is not reliable. 34 | /** 35 | * Accepted formats: {@code type=category}, {@code type=!category}, 36 | * {@code type=[category1, ...]} or {@code type=![category1, ...]}. 37 | */ 38 | public class TypeProperty extends SelectorProperty { 39 | @Override 40 | public Parsed read(StringReader reader) { 41 | boolean negated = reader.isNegated(); 42 | return !reader.isArray() ? new Parsed(readCategory(reader), negated) : 43 | new Parsed(reader.readSet(this::readCategory, "unit category").toSeq(), negated); 44 | } 45 | 46 | public UnitCategory readCategory(StringReader reader) { 47 | String name = reader.readString(); 48 | if (name == null) throw reader.expected("a unit category"); 49 | UnitCategory category = UnitCategory.of(name = name.toLowerCase()); 50 | if (category == null) throw reader.notFound("unit category", name); 51 | return category; 52 | } 53 | 54 | 55 | public class Parsed extends SelectorProperty.Parsed { 56 | /** Not {@code null} if multiple types are specified. */ 57 | public final Seq types; 58 | /** {@code null} if multiple types are specified. */ 59 | public final UnitCategory type; 60 | public final boolean negated; 61 | 62 | public Parsed(UnitCategory type, boolean negated) { 63 | this.types = null; 64 | this.type = type; 65 | this.negated = negated; 66 | } 67 | 68 | public Parsed(Seq types, boolean negated) { 69 | this.types = types; 70 | this.type = null; 71 | this.negated = negated; 72 | } 73 | 74 | @Override 75 | public boolean passes(Selector selector, Player executor, Vec2 pos, Unit entity) { 76 | // Quite slow because i have no other choice to determine the unit category 77 | return negated ^ (types != null ? types.contains(c -> c.sameCategory(entity)) : type.sameCategory(entity)); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/selector/properties/DataProperty.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.selector.properties; 20 | 21 | import java.lang.reflect.Field; 22 | 23 | import arc.math.geom.Vec2; 24 | import arc.struct.ObjectMap; 25 | import arc.util.serialization.JsonReader; 26 | import arc.util.serialization.JsonValue; 27 | 28 | import mindustry.gen.Player; 29 | import mindustry.gen.Unit; 30 | 31 | import fr.zetamap.morecommands.modules.selector.Selector; 32 | import fr.zetamap.morecommands.modules.selector.SelectorProperty; 33 | import fr.zetamap.morecommands.util.MindustryJson; 34 | import fr.zetamap.morecommands.util.StringReader; 35 | 36 | 37 | /** minecraft's nbt like property. WIP */ 38 | public class DataProperty extends SelectorProperty { 39 | private final JsonReader reader = new JsonReader(); 40 | 41 | @Override 42 | public Parsed read(StringReader reader) { 43 | boolean negated = reader.isNegated(); 44 | if (reader.peekNext() != '{') throw reader.expected("a map start", reader.peekNext()); 45 | // TODO: Find a way to read json without error about leading comma or brace 46 | try { return new Parsed(this.reader.parse(reader.readRemaining()), negated); } 47 | catch (Exception e) { throw reader.error(e.getMessage()); } 48 | } 49 | 50 | 51 | public class Parsed extends SelectorProperty.Parsed { 52 | public final JsonValue data; 53 | public final boolean negated; 54 | protected Class readedType; 55 | protected ObjectMap readed; 56 | 57 | public Parsed(JsonValue data, boolean negated) { 58 | this.data = data; 59 | this.negated = negated; 60 | // Should be an object 61 | if (!data.isObject()) throw new IllegalArgumentException("Should be a json object"); 62 | } 63 | 64 | @Override 65 | public boolean passes(Selector selector, Player executor, Vec2 pos, Unit entity) { 66 | // Try to cache the result, better than nothing ¯\_(ツ)_/¯ 67 | if (entity.getClass() != readedType) { 68 | readed = MindustryJson.get().readFields(entity.getClass(), data); 69 | readedType = entity.getClass(); 70 | } 71 | 72 | // This is really inefficient (can hold lot of memory) but this is the only way 73 | for (ObjectMap.Entry e : readed) { 74 | try { 75 | if (!e.key.get(entity).equals(e.value)) return negated; 76 | } catch (Exception ignored) {} 77 | } 78 | return !negated; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/selector/properties/HasitemProperty.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | package fr.zetamap.morecommands.modules.selector.properties; 21 | 22 | import arc.math.geom.Vec2; 23 | import arc.struct.ObjectMap; 24 | 25 | import mindustry.Vars; 26 | import mindustry.gen.Player; 27 | import mindustry.gen.Unit; 28 | import mindustry.type.Item; 29 | 30 | import fr.zetamap.morecommands.misc.Range; 31 | import fr.zetamap.morecommands.misc.RangeParser; 32 | import fr.zetamap.morecommands.modules.selector.Selector; 33 | import fr.zetamap.morecommands.modules.selector.SelectorProperty; 34 | import fr.zetamap.morecommands.util.StringReader; 35 | 36 | 37 | /** Accepted formats: {@code hasitem=[item1, !item2, item2=quantity, !item3=..max, item4=min.., item5=min..max]} */ 38 | public class HasitemProperty extends SelectorProperty { 39 | @Override 40 | public Parsed read(StringReader reader) { 41 | reader.skipWhitespaces(); 42 | if (!reader.isArray()) throw reader.expected("an array"); 43 | ObjectMap> items = new ObjectMap<>(); 44 | ObjectMap negated = new ObjectMap<>(); 45 | reader.readArray(r -> { 46 | boolean negate = r.isNegated(); 47 | String name = r.readString(); 48 | if (name == null || name.isEmpty()) throw r.expected("an item name"); 49 | Item item = Vars.content.item(name); 50 | if (item == null) throw r.notFound("item", name); 51 | Range range = null; 52 | if (r.peek() == '=') { 53 | r.skip(); 54 | range = RangeParser.parseInt(reader, false); 55 | } 56 | items.put(item, range); 57 | negated.put(item, negate); 58 | }); 59 | return new Parsed(items, negated); 60 | } 61 | 62 | 63 | public class Parsed extends SelectorProperty.Parsed { 64 | public final ObjectMap> items; 65 | public final ObjectMap negated; 66 | 67 | public Parsed(ObjectMap> items, ObjectMap negated) { 68 | this.items = items; 69 | this.negated = negated; 70 | } 71 | 72 | @Override 73 | public boolean passes(Selector selector, Player executor, Vec2 pos, Unit entity) { 74 | if (!entity.hasItem() || !items.containsKey(entity.item())) return false; 75 | Range range = items.get(entity.item()); 76 | Boolean negate = negated.get(entity.item()); 77 | return (range == null || range.contains(entity.stack().amount)) ^ (negate != null && negate); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/misc/RangeParser.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | package fr.zetamap.morecommands.misc; 21 | 22 | import fr.zetamap.morecommands.util.StringReader; 23 | 24 | 25 | public class RangeParser { 26 | public static Range parseInt(String input, boolean notNegative) throws StringReader.ParseException { 27 | return parseInt(new StringReader(input), notNegative); 28 | } 29 | public static Range parseInt(StringReader reader, boolean notNegative) throws StringReader.ParseException { 30 | reader.skipWhitespaces(); 31 | if (!reader.canPeekNext()) throw reader.expected("an int or a range"); 32 | Integer min = reader.readNumeric(notNegative, false); 33 | if (!reader.canPeekNext() || reader.peekNext() != '.') { 34 | if (min == null) throw reader.expected("an int or a range"); 35 | return Range.fixed(min); 36 | } 37 | if (reader.peekNext(1) != '.') throw reader.error("Invalid int range"); 38 | reader.skip(2); 39 | Integer max = reader.readNumeric(notNegative, false); 40 | if (min == null && max == null) throw reader.error("Invalid int range"); 41 | if (min != null && max != null && min > max) throw reader.error("Min range greater than max"); 42 | return Range.of(min, max); 43 | } 44 | 45 | public static Range parseFloat(String input, boolean notNegative) throws StringReader.ParseException { 46 | return parseFloat(new StringReader(input), notNegative); 47 | } 48 | public static Range parseFloat(StringReader reader, boolean notNegative) throws StringReader.ParseException { 49 | reader.skipWhitespaces(); 50 | if (!reader.canPeekNext()) throw reader.expected("a float or a range"); 51 | Float min = reader.readDecimal(notNegative, false); 52 | 53 | // The first dot can be consumed by the first decimal read 54 | if (reader.peekNext(-1) == '.') reader.skip(-1); 55 | if (!reader.canPeekNext() || reader.peekNext() != '.') { 56 | if (min == null) throw reader.expected("a float or a range"); 57 | return Range.fixed(min); 58 | } 59 | if (reader.peekNext(1) != '.') throw reader.error("Invalid float range"); 60 | reader.skip(2); 61 | if (reader.peekNext() == '.') throw reader.error("Invalid float range"); 62 | 63 | Float max = reader.readDecimal(notNegative, false); 64 | if (min == null && max == null) throw reader.error("Invalid float range"); 65 | if (min != null && max != null && min > max) throw reader.error("Min range greater than max"); 66 | return Range.of(min, max); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/security/AdminUsidModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.security; 20 | 21 | import arc.struct.ObjectMap; 22 | import arc.struct.ObjectSet; 23 | import arc.struct.Seq; 24 | 25 | import mindustry.Vars; 26 | import mindustry.net.Administration; 27 | 28 | import fr.zetamap.morecommands.module.AbstractSaveableModule; 29 | import fr.zetamap.morecommands.util.JsonSettings; 30 | 31 | 32 | /** Modify some {@link Administration} methods to handle multiple usid. */ 33 | public class AdminUsidModule extends AbstractSaveableModule { 34 | /** Direct access to the modified {@link Administration}. */ 35 | public WrappedAdministration admins; 36 | 37 | @Override 38 | protected void initImpl() { 39 | Vars.netServer.admins.forceSave(); // in case of 40 | Vars.netServer.admins = admins = new WrappedAdministration(Vars.netServer.admins); 41 | } 42 | 43 | @SuppressWarnings("unchecked") 44 | @Override 45 | protected void loadImpl(JsonSettings settings) { 46 | if (admins == null) return; 47 | 48 | admins.usids.clear(); 49 | for (String uuid : settings.keys()) 50 | admins.usids.put(uuid, settings.getOrPut(uuid, ObjectSet.class, String.class, ObjectSet::new)); 51 | 52 | // Add missing admin usids 53 | admins.getAdmins().each(p -> admins.usids.get(p.id, ObjectSet::new).add(p.adminUsid)); 54 | } 55 | 56 | @Override 57 | protected void saveImpl(JsonSettings settings) { 58 | if (admins == null) return; 59 | 60 | admins.usids.each((uuid, usids) -> settings.put(uuid, String.class, usids.toSeq())); 61 | } 62 | 63 | 64 | public class WrappedAdministration extends Administration { 65 | public final ObjectMap> usids = new ObjectMap<>(); 66 | 67 | public WrappedAdministration(Administration old) { 68 | // Copy callbacks 69 | chatFilters.set(old.chatFilters); 70 | actionFilters.set(old.actionFilters); 71 | } 72 | 73 | public Seq getUsids(String id) { 74 | return usids.get(id).toSeq(); 75 | } 76 | 77 | @Override 78 | public boolean adminPlayer(String id, String usid) { 79 | usids.get(id, ObjectSet::new).add(usid); 80 | setModified(); 81 | return super.adminPlayer(id, usid); 82 | } 83 | 84 | @Override 85 | public boolean unAdminPlayer(String id) { 86 | usids.remove(id); 87 | setModified(); 88 | return super.unAdminPlayer(id); 89 | } 90 | 91 | @Override 92 | public boolean isAdmin(String id, String usid) { 93 | ObjectSet u; 94 | return super.isAdmin(id, usid) || (u = usids.get(id)) != null && u.contains(usid); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/selector/SelectorModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.selector; 20 | 21 | import fr.zetamap.morecommands.PlayerData; 22 | import fr.zetamap.morecommands.misc.CoordinatesParser; 23 | import fr.zetamap.morecommands.misc.Gatekeeper; 24 | import fr.zetamap.morecommands.misc.Players; 25 | import fr.zetamap.morecommands.module.AbstractModule; 26 | import fr.zetamap.morecommands.module.ModuleFactory; 27 | 28 | 29 | /** 30 | * Minecraft-like selectors.
31 | * Nicknames must not start with {@code '@'} or {@code '~'} to avoid ambiguity with selector and coordinate prefixes. 32 | *

33 | * To understand more about selectors and properties, please read: https://minecraft.wiki/w/Target_selectors 34 | */ 35 | public class SelectorModule extends AbstractModule { 36 | public boolean enabled() { 37 | return ModuleFactory.enabled(this); 38 | } 39 | 40 | public void enable() { 41 | ModuleFactory.enable(this); 42 | } 43 | 44 | public void disable() { 45 | ModuleFactory.disable(this); 46 | } 47 | 48 | public SelectorParser parse(PlayerData executor, String[] args) { 49 | return parse(executor, args, 0, args.length, false); 50 | } 51 | public SelectorParser parse(PlayerData executor, String[] args, boolean onlyPlayers) { 52 | return parse(executor, args, 0, args.length, onlyPlayers); 53 | } 54 | public SelectorParser parse(PlayerData executor, String[] args, int from, int to) { 55 | return parse(executor, args, from, to, false); 56 | } 57 | /** This method will notify the user if selectors are disabled. */ 58 | public SelectorParser parse(PlayerData executor, String[] args, int from, int to, boolean onlyPlayers) { 59 | if (args.length == 0 || from < 0 || to > args.length || from >= to || args[from].isEmpty()) { 60 | Players.err(executor, "Missing player name/uuid or selector."); 61 | return null; 62 | } else if (Selectors.isSelector(args[from]) && !enabled()) { 63 | Players.err(executor, "Selectors are disabled, you cannot use them."); 64 | return null; 65 | } 66 | return SelectorParser.parse(executor, args, from, to, onlyPlayers); 67 | } 68 | 69 | @Override 70 | protected void initImpl() { 71 | Selectors.init(); 72 | SelectorProperties.init(); 73 | 74 | Gatekeeper.add(internalName(), ctx -> 75 | !ctx.strippedName.isEmpty() && (ctx.strippedName.charAt(0) == Selectors.prefix || 76 | ctx.strippedName.charAt(0) == CoordinatesParser.worldRelativePrefix) ? 77 | Gatekeeper.reject("Your nickname cannot start with '[orange]" + ctx.strippedName.charAt(0) + "[]'.") : 78 | Gatekeeper.accept()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/selector/properties/UnitProperty.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.selector.properties; 20 | 21 | import arc.math.geom.Vec2; 22 | import arc.struct.ObjectSet; 23 | 24 | import mindustry.Vars; 25 | import mindustry.gen.Player; 26 | import mindustry.gen.Unit; 27 | import mindustry.type.UnitType; 28 | 29 | import fr.zetamap.morecommands.modules.selector.Selector; 30 | import fr.zetamap.morecommands.modules.selector.SelectorProperty; 31 | import fr.zetamap.morecommands.util.StringReader; 32 | 33 | 34 | /** 35 | * Accepted formats: {@code unit=}, {@code unit=!}, {@code unit=unit}, {@code unit=!unit}, 36 | * {@code unit=[unit1, ...]} or {@code unit=![unit1, ...]}. 37 | */ 38 | public class UnitProperty extends SelectorProperty { 39 | @Override 40 | public Parsed read(StringReader reader) { 41 | boolean negated = reader.isNegated(); 42 | return !reader.isArray() ? new Parsed(readUnit(reader, true), negated) : 43 | new Parsed(reader.readSet(this::readUnit, "unit name"), negated); 44 | } 45 | 46 | public UnitType readUnit(StringReader reader) { return readUnit(reader, false); } 47 | public UnitType readUnit(StringReader reader, boolean optional) { 48 | String name = reader.readString(); 49 | if (name == null) { 50 | if (optional) return null; 51 | throw reader.expected("a unit name"); 52 | } 53 | UnitType unit = Vars.content.unit(name); 54 | if (unit == null && !optional) throw reader.notFound("unit", name); 55 | return unit; 56 | } 57 | 58 | 59 | public class Parsed extends SelectorProperty.Parsed { 60 | /** Not {@code null} if multiple units are specified. */ 61 | public final ObjectSet units; 62 | /** {@code null} if no or multiple units are specified. */ 63 | public final UnitType unit; 64 | public final boolean negated; 65 | 66 | public Parsed(UnitType unit, boolean negated) { 67 | this.units = null; 68 | this.unit = unit; 69 | this.negated = negated; 70 | } 71 | 72 | public Parsed(ObjectSet units, boolean negated) { 73 | this.units = units; 74 | this.unit = null; 75 | this.negated = negated; 76 | } 77 | 78 | @Override 79 | public boolean passes(Selector selector, Player executor, Vec2 pos, Unit entity) { 80 | return negated ^ (units != null ? units.contains(entity.type) : 81 | unit != null ? unit == entity.type : 82 | // always false if the player is dead or if it's the console 83 | executor == null || executor.dead() ? negated : 84 | executor.unit().type == entity.type); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/server/PreConnect.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.server; 20 | 21 | import mindustry.core.Version; 22 | 23 | 24 | /** Try to anticipate a refused connection by using the last server discovery information. */ 25 | public class PreConnect { 26 | /** Check for connection availability using {@link Server#info} provided by the last {@link Server#ping()} call. */ 27 | public static Availability verify(boolean adminPlayer, Server server) { 28 | return 29 | server.info == null ? Availability.serverClosed : 30 | !adminPlayer && server.adminOnly ? Availability.adminOnly : 31 | server.info.playerLimit > 0 && server.info.players >= server.info.playerLimit && !adminPlayer ? Availability.playerLimit : 32 | !server.info.versionType.equals(Version.type) ? Availability.typeMismatch : 33 | Version.build == -1 && server.info.version != -1 ? Availability.customClient : 34 | server.info.version != Version.build && Version.build != -1 && server.info.version != -1 ? 35 | Version.build > server.info.version ? Availability.serverOutdated : Availability.clientOutdated : 36 | Availability.ok; 37 | } 38 | 39 | 40 | /** A part come from {@link mindustry.net.Packets.KickReason}. */ 41 | public static enum Availability { 42 | ok, serverOutdated, clientOutdated, typeMismatch, customClient, playerLimit, adminOnly, serverClosed; 43 | 44 | public boolean ok() { 45 | return this == ok; 46 | } 47 | 48 | public String toReason(Server server) { 49 | switch (this) { 50 | case ok: return "[green]Connection success."; 51 | case adminOnly: return "This server is only for admins."; 52 | case serverOutdated: return "The server is in a lower version of Mindustry. \n" 53 | + "[gray]Current: [lightgray]v" + Version.build + "[], Required: [lightgray]v" 54 | + server.info.version; 55 | case clientOutdated: return "The server is in a newer version of Mindustry. \n" 56 | + "[gray]Current: [lightgray]v" + Version.build + "[], Required: [lightgray]v" 57 | + server.info.version; 58 | case typeMismatch: return "The server is not compatible with this Mindustry version. \n" 59 | + "[gray]Current: [lightgray]" + Version.type + "[], Required: [lightgray]" 60 | + server.info.versionType; 61 | case customClient: return "The server does not accept custom Mindustry versions."; 62 | case playerLimit: return "Server full. [gray]([lightgray]" + server.info.players + "[]/[lightgray]" 63 | + server.info.playerLimit + "[])"; 64 | case serverClosed: return "The server is not responding. [gray]([lightgray]timeout[])"; 65 | default: return "[lightgray][]"; 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/help/HelpModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.help; 20 | 21 | import arc.math.Mathf; 22 | import arc.struct.Seq; 23 | import arc.util.CommandHandler.Command; 24 | 25 | import fr.zetamap.morecommands.command.*; 26 | import fr.zetamap.morecommands.misc.Players; 27 | import fr.zetamap.morecommands.module.AbstractModule; 28 | import fr.zetamap.morecommands.util.Strings; 29 | 30 | 31 | public class HelpModule extends AbstractModule { 32 | @Override 33 | public void registerServerCommands(ServerCommandHandler handler) { 34 | 35 | } 36 | 37 | @Override 38 | public void registerClientCommands(ClientCommandHandler handler) { 39 | handler.add("help", "[command|page|selectors]", "Lists all commands.", (args, player) -> { 40 | String command; 41 | int perPage = 8, page = 1, pages; 42 | 43 | if (args.length == 1) { 44 | if (args[0].equals("selectors")) { 45 | mindustry.gen.Call.openURI(player.player.con, 46 | "https://github.com/ZetaMap/MoreCommands/blob/main/README.md#selectors"); 47 | return; 48 | } 49 | 50 | page = Strings.parseInt(args[0]); 51 | 52 | if (page == Integer.MIN_VALUE) command = args[0]; 53 | else command = null; 54 | } else command = null; 55 | 56 | if (command != null) { 57 | Command c = handler.handler.getCommandList().find(cc -> cc.text.equals(command)); 58 | 59 | // Act like the command is not found when the player is not an administrator 60 | if (c == null || (!player.admin() && handler.isAdmin(c.text))) 61 | Players.err(player, "No command named '[orange]@[]' found.", command); 62 | else Players.info(player, "[orange]@@[white] @ [lightgray]- @", handler.handler.prefix, c.text, c.paramText, 63 | c.description); 64 | return; 65 | } 66 | 67 | Seq commands = handler.handler.getCommandList(); 68 | if (!player.admin()) commands = commands.select(c -> !handler.isAdmin(c.text)); 69 | pages = Mathf.ceil((float)commands.size / perPage); 70 | 71 | if (page < 1 || page > pages) { 72 | Players.err(player, "'[orange]page[]' must be a number between [orange]1[] and [orange]@[].", pages); 73 | return; 74 | } 75 | 76 | Players.info(player, "[orange]-- Commands page [lightgray]@[gray]/[]@ [gray]([]@[gray])[][] --", page, pages, 77 | commands.size); 78 | StringBuilder builder = new StringBuilder(); 79 | for(int i=perPage*(page-1); i. 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.selector.properties; 20 | 21 | import arc.math.geom.Vec2; 22 | import arc.struct.ObjectSet; 23 | import arc.util.Structs; 24 | 25 | import mindustry.game.Team; 26 | import mindustry.gen.Player; 27 | import mindustry.gen.Unit; 28 | 29 | import fr.zetamap.morecommands.modules.selector.Selector; 30 | import fr.zetamap.morecommands.modules.selector.SelectorProperty; 31 | import fr.zetamap.morecommands.util.StringReader; 32 | 33 | 34 | /** 35 | * Accepted formats: {@code team=}, {@code team=!}, {@code team=team}, {@code team=!team}, 36 | * {@code team=[team1, ...]} or {@code team=![team1, ...]}.
37 | * {@code team} value can be a named team from {@link Team#baseTeams} (e.g. {@code sharded}, {@code crux}), 38 | * an unnamed team from {@link Team#all} {@code team#7-255} (e.g. {@code team#7}, {@code team#46}) 39 | * or a team id from {@link Team#all} {@code 0 -> 255} (e.g. {@code #1}, {@code #64}) 40 | */ 41 | public class TeamProperty extends SelectorProperty { 42 | @Override 43 | public Parsed read(StringReader reader) { 44 | boolean negated = reader.isNegated(); 45 | return !reader.isArray() ? new Parsed(readTeam(reader, true), negated) : 46 | new Parsed(reader.readSet(this::readTeam, "team name"), negated); 47 | } 48 | 49 | public Team readTeam(StringReader reader) { return readTeam(reader, false); } 50 | public Team readTeam(StringReader reader, boolean optional) { 51 | if (reader.peekNext() == '#') { 52 | reader.readNext(); 53 | int id = reader.readNumeric(true); 54 | if (id < 0 || id > Team.all.length-1) throw reader.error("Invalid team id (range 0-255)"); 55 | return Team.get(id); 56 | } 57 | String name = reader.readString(); 58 | if (name == null) { 59 | if (optional) return null; 60 | throw reader.expected("a team name"); 61 | } 62 | Team team = Structs.find(Team.all, t -> t.name.equals(name)); 63 | if (team == null && !optional) throw reader.notFound("team", name); 64 | return team; 65 | } 66 | 67 | 68 | public class Parsed extends SelectorProperty.Parsed { 69 | /** Not {@code null} if multiple teams are specified. */ 70 | public final ObjectSet teams; 71 | /** {@code null} if no or multiple teams are specified. */ 72 | public final Team team; 73 | public final boolean negated; 74 | 75 | public Parsed(Team team, boolean negated) { 76 | this.teams = null; 77 | this.team = team; 78 | this.negated = negated; 79 | } 80 | 81 | public Parsed(ObjectSet teams, boolean negated) { 82 | this.teams = teams; 83 | this.team = null; 84 | this.negated = negated; 85 | } 86 | 87 | @Override 88 | public boolean passes(Selector selector, Player executor, Vec2 pos, Unit entity) { 89 | return negated ^ (teams != null ? teams.contains(entity.team) : 90 | team != null ? team == entity.team : 91 | executor == null || executor.team() == entity.team); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/selector/Selectors.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2021-2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.selector; 20 | 21 | import arc.func.Cons; 22 | import arc.struct.ObjectMap; 23 | import arc.struct.Seq; 24 | 25 | import mindustry.gen.Groups; 26 | 27 | 28 | public class Selectors { 29 | public static final char prefix = '@'; 30 | private static final ObjectMap all = new ObjectMap<>(); 31 | 32 | /** Default selectors. */ 33 | public static Selector 34 | // Player related 35 | nearestPlayer, randomPlayer, allPlayers, teamPlayers, self, 36 | // Unit related 37 | nearestUnit, randomUnit, allUnits, teamUnits, 38 | // Both related 39 | nearestEntity, randomEntity, allEntities, teamEntities; 40 | 41 | 42 | public static void init() { 43 | nearestPlayer = new Selector("p", 1, Sorting.nearest, Selector.playerExtractor); 44 | randomPlayer = new Selector("r", 1, Sorting.random, Selector.playerExtractor); 45 | allPlayers = new Selector("a", Selector.playerExtractor); 46 | teamPlayers = new Selector("t", (l, e) -> Groups.player.each(p -> e == null || p.team() == e.team(), p -> l.add(p.unit()))); 47 | self = new Selector("s", 1, null, (l, e) -> { 48 | if (e == null) throw new IllegalArgumentException("Unavailable selector"); 49 | l.add(e.unit()); 50 | }); 51 | 52 | nearestUnit = new Selector("nu", 1, Sorting.nearest, Selector.unitExtractor); 53 | randomUnit = new Selector("ru", 1, Sorting.random, Selector.unitExtractor); 54 | allUnits = new Selector("u", Selector.unitExtractor); 55 | teamUnits = new Selector("tu", (l, e) -> Groups.unit.each(p -> !p.isPlayer() && e == null || p.team() == e.team(), l::add)); 56 | 57 | nearestEntity = new Selector("n", 1, Sorting.nearest, Selector.entityExtractor); 58 | randomEntity = new Selector("re", 1, Sorting.random, Selector.entityExtractor); 59 | allEntities = new Selector("e", Selector.entityExtractor); 60 | teamEntities = new Selector("te", (l, e) -> Groups.unit.each(p -> e == null || p.team() == e.team(), l::add)); 61 | } 62 | 63 | public static Selector get(String name) { 64 | return name.isEmpty() ? null : all.get(name.charAt(0) == prefix ? name.substring(1) : name); 65 | } 66 | 67 | public static boolean isSelector(String arg) { 68 | return !arg.isEmpty() && arg.charAt(0) == prefix; 69 | } 70 | 71 | /** @throws IllegalArgumentException if another selector is registered with the same name. */ 72 | public static int add(Selector selector) { 73 | if (all.containsKey(selector.name)) 74 | throw new IllegalArgumentException("another selector is named '"+selector.name+"'"); 75 | // Validate the name depending on what StringReader can accept 76 | if (fr.zetamap.morecommands.util.Strings.contains(selector.name, c -> 77 | !fr.zetamap.morecommands.util.StringReader.isAlpha(c))) 78 | throw new IllegalArgumentException("invalid selector name"); 79 | all.put(selector.name, selector); 80 | return all.size-1; 81 | } 82 | 83 | public static void each(Cons consumer) { 84 | all.each((n, s) -> consumer.get(s)); 85 | } 86 | 87 | public static Seq all() { 88 | return all.values().toSeq(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/security/Punishment.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2021-2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.security; 20 | 21 | import arc.util.Nullable; 22 | import arc.util.Time; 23 | 24 | 25 | public class Punishment { 26 | static int lastId; 27 | 28 | public final int id; 29 | public @Nullable String author; 30 | public final String target; 31 | public @Nullable String address; 32 | public final Type type; 33 | public final long creation; 34 | public long expire; 35 | public @Nullable String reason; 36 | public @Nullable Pardon pardon; 37 | 38 | Punishment(int id, String author, String target, String address, Type type, long creation, long expire, String reason, 39 | Pardon pardon) { 40 | this.id = id; 41 | this.author = author; 42 | this.target = target; 43 | this.address = address; 44 | this.type = type; 45 | this.creation = creation; 46 | this.expire = Math.max(expire, -1); 47 | this.reason = reason; 48 | this.pardon = pardon; 49 | } 50 | 51 | /** A negative {@code duration} can be used to specify a forever punishment. */ 52 | public Punishment(String author, String target, String address, Type type, long duration, String reason) { 53 | this.id = lastId++; 54 | this.author = author; 55 | this.target = target; 56 | this.address = address; 57 | this.type = type; 58 | this.creation = Time.millis(); 59 | this.expire = duration < 0 ? -1 : creation + duration; 60 | this.reason = reason; 61 | } 62 | 63 | public boolean expired() { 64 | return pardoned() || !permanant() && Time.millis() > expire; 65 | } 66 | 67 | public boolean permanant() { 68 | return expire < 0; 69 | } 70 | 71 | public boolean pardoned() { 72 | return pardon != null; 73 | } 74 | 75 | public long duration() { 76 | return expire - creation; 77 | } 78 | 79 | public long remaining() { 80 | return permanant() ? Long.MAX_VALUE : Time.millis() - expire; 81 | } 82 | 83 | public void setDuration(long duration) { 84 | expire = creation + duration; 85 | } 86 | 87 | 88 | public static enum Type { 89 | ban("banned", true, PunishmentDuration.oneMonth), 90 | kick("kicked", true, PunishmentDuration.thirtyMinutes), 91 | votekick("vote kicked", true, PunishmentDuration.oneHour), 92 | warn("warned", false, PunishmentDuration.permanant), 93 | mute("muted", false, PunishmentDuration.threeHours), 94 | freeze("frozen", false, PunishmentDuration.oneHour); 95 | 96 | public static final Type[] all = values(); 97 | 98 | public final String verb; 99 | /** Whether this kind of punishment implies a kick of the target. */ 100 | public final boolean impliesKick; 101 | public final PunishmentDuration defaultDuration; 102 | 103 | Type(String verb, boolean impliesKick, PunishmentDuration defaultDuration) { 104 | this.verb = verb; 105 | this.impliesKick = impliesKick; 106 | this.defaultDuration = defaultDuration; 107 | } 108 | } 109 | 110 | public static class Pardon { 111 | public @Nullable String author; 112 | public final long when; 113 | public @Nullable String reason; 114 | 115 | public Pardon() { this(null, null); } 116 | public Pardon(String author) { this(author, null); } 117 | public Pardon(String author, String reason) { this(author, Time.millis(), reason); } 118 | public Pardon(String author, long when, String reason) { 119 | this.author = author; 120 | this.when = when; 121 | this.reason = reason; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/Main.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2021-2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands; 20 | 21 | import arc.util.CommandHandler; 22 | import arc.util.Reflect; 23 | import arc.util.Time; 24 | 25 | import mindustry.Vars; 26 | import mindustry.mod.Mods; 27 | import mindustry.net.Administration.Config; 28 | 29 | import fr.zetamap.morecommands.migration.SettingsMigrator; 30 | import fr.zetamap.morecommands.misc.Gatekeeper; 31 | import fr.zetamap.morecommands.module.ModuleFactory; 32 | import fr.zetamap.morecommands.util.Autosaver; 33 | import fr.zetamap.morecommands.util.Logger; 34 | import fr.zetamap.morecommands.util.VersionChecker; 35 | 36 | 37 | public class Main extends mindustry.mod.Plugin { 38 | private static final Logger logger = new Logger(true); 39 | 40 | { 41 | ModuleFactory.configFile = getConfig(); 42 | ModuleFactory.preInit(); 43 | Modules.register(this); 44 | } 45 | 46 | @Override 47 | public void init() { 48 | // TODO: should start after the server, because of possible added modules 49 | long time = Time.nanos(); 50 | 51 | Logger.init(this); 52 | logger.info("&lc>>>&fr MoreCommands plugin is loading...\n"); 53 | 54 | VersionChecker.checkFor(this); 55 | 56 | PlayerData.init(); 57 | Gatekeeper.init(); 58 | if (check(ModuleFactory::init)) return; 59 | SettingsMigrator.enabled = false; //TODO: debug 60 | if (check(SettingsMigrator::migrateAllTheShittySettingsIMade)) return; 61 | 62 | Autosaver.spacing(Config.autosaveSpacing.num()); 63 | Autosaver.start(); 64 | Autosaver.save(); // just to be sure 65 | 66 | // Use reflection to follow the server autosave spacing 67 | Reflect.set(Config.autosaveSpacing, "changed", (Runnable)() -> Autosaver.spacing(Config.autosaveSpacing.num())); 68 | 69 | logger.info("\n&lc>>>&fr MoreCommands plugin loaded in @ seconds! enjoy the fun =)", 70 | Time.timeSinceNanos(time) / 1_000_000_000f); 71 | } 72 | 73 | @Override 74 | public void registerServerCommands(CommandHandler handler) { 75 | ModuleFactory.registerServerCommands(handler); 76 | } 77 | 78 | @Override 79 | public void registerClientCommands(CommandHandler handler) { 80 | ModuleFactory.registerClientCommands(handler); 81 | } 82 | 83 | private static Mods.ModMeta meta; 84 | public static Mods.ModMeta getMeta() { 85 | if (meta != null) return meta; 86 | Mods.LoadedMod load = Vars.mods.getMod(Main.class); 87 | if(load == null) throw new IllegalArgumentException("Mod is not loaded yet (or missing)!"); 88 | return meta = load.meta; 89 | } 90 | 91 | private static boolean check(arc.util.UnsafeRunnable run) { 92 | try { 93 | run.run(); 94 | return false; 95 | } catch (Throwable t) { 96 | logger.err(t); 97 | error(); 98 | return true; 99 | } 100 | } 101 | 102 | private static boolean check(arc.func.Boolp run) { 103 | try { 104 | if (run.get()) return false; 105 | } catch (Throwable t) { logger.err(t); } 106 | error(); 107 | return true; 108 | } 109 | 110 | private static void error() { 111 | logger.err("\n########################################\n\n" 112 | + "&lc>>>&fr MoreCommands plugin failed to initialize due to previous error(s)!\n" 113 | + "&lc>>>&fr Most often, this is caused by an invalid or corrupted configuration files.\n" 114 | + "&lc>>>&fr If this is not the case and this/these error(s) are recurring, please report them here:\n" 115 | + "&lc>>>&fr @", "https://github.com/ZetaMap/MoreCommands/issues/new"); 116 | ModuleFactory.setError(); 117 | ModuleFactory.dispose(); // "Uninitialize" MoreCommands. At least, removes the commands and stops the automated things. 118 | logger.err("&lc>>>&fr\n" 119 | + "&lc>>>&fr MoreCommands has been uninitialized to avoid further issues.\n" 120 | + "\n########################################\n"); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/Modules.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands; 20 | 21 | import mindustry.mod.Mod; 22 | 23 | import fr.zetamap.morecommands.module.ModuleFactory; 24 | import fr.zetamap.morecommands.modules.command.CommandsModule; 25 | import fr.zetamap.morecommands.modules.effect.EffectsModule; 26 | import fr.zetamap.morecommands.modules.godmode.GodmodeModule; 27 | import fr.zetamap.morecommands.modules.help.HelpModule; 28 | import fr.zetamap.morecommands.modules.manager.ManagerModule; 29 | import fr.zetamap.morecommands.modules.messaging.MessagingModule; 30 | import fr.zetamap.morecommands.modules.misc.MiscModule; 31 | import fr.zetamap.morecommands.modules.security.AdminUsidModule; 32 | import fr.zetamap.morecommands.modules.security.AntiEvadeModule; 33 | import fr.zetamap.morecommands.modules.security.CrackedClientsModule; 34 | import fr.zetamap.morecommands.modules.security.ModerationModule; 35 | import fr.zetamap.morecommands.modules.security.PunishmentsModule; 36 | import fr.zetamap.morecommands.modules.security.ReservedNamesModule; 37 | import fr.zetamap.morecommands.modules.selector.SelectorModule; 38 | import fr.zetamap.morecommands.modules.server.SwitchModule; 39 | import fr.zetamap.morecommands.modules.tag.TagsModule; 40 | import fr.zetamap.morecommands.modules.team.TeamingModule; 41 | import fr.zetamap.morecommands.modules.tp.TeleportModule; 42 | import fr.zetamap.morecommands.modules.voting.VotingModule; 43 | import fr.zetamap.morecommands.modules.world.WorldEditModule; 44 | 45 | 46 | /** Static access to the MoreCommands modules. */ 47 | public class Modules { 48 | public static ManagerModule manager; 49 | public static CommandsModule commands; 50 | public static HelpModule help; 51 | public static AdminUsidModule usid; 52 | public static MessagingModule messaging; 53 | public static VotingModule voting; 54 | public static TagsModule tags; 55 | public static EffectsModule effects; 56 | public static SwitchModule switcher; 57 | public static TeamingModule team; 58 | public static TeleportModule teleport; 59 | public static WorldEditModule worldEdit; 60 | public static MiscModule misc; 61 | public static GodmodeModule godmode; 62 | public static PunishmentsModule punishments; 63 | public static ModerationModule moderation; 64 | public static SelectorModule selector; 65 | public static CrackedClientsModule cracked; 66 | public static ReservedNamesModule reserved; 67 | public static AntiEvadeModule antiEvade; 68 | 69 | /** Creates and registers the MoreCommands modules. */ 70 | public static void register(Mod context) { 71 | manager = new ManagerModule(); ModuleFactory.add(context, manager); 72 | commands = new CommandsModule(); ModuleFactory.add(context, commands); 73 | help = new HelpModule(); ModuleFactory.add(context, help); 74 | usid = new AdminUsidModule(); ModuleFactory.add(context, usid); 75 | messaging = new MessagingModule(); ModuleFactory.add(context, messaging); 76 | voting = new VotingModule(); ModuleFactory.add(context, voting); 77 | tags = new TagsModule(); ModuleFactory.add(context, tags); 78 | effects = new EffectsModule(); ModuleFactory.add(context, effects); 79 | switcher = new SwitchModule(); ModuleFactory.add(context, switcher); 80 | team = new TeamingModule(); ModuleFactory.add(context, team); 81 | teleport = new TeleportModule(); ModuleFactory.add(context, teleport); 82 | worldEdit = new WorldEditModule(); ModuleFactory.add(context, worldEdit); 83 | misc = new MiscModule(); ModuleFactory.add(context, misc); 84 | godmode = new GodmodeModule(); ModuleFactory.add(context, godmode); 85 | punishments = new PunishmentsModule(); ModuleFactory.add(context, punishments); 86 | moderation = new ModerationModule(); ModuleFactory.add(context, moderation); 87 | selector = new SelectorModule(); ModuleFactory.add(context, selector); 88 | cracked = new CrackedClientsModule(); ModuleFactory.add(context, cracked); 89 | reserved = new ReservedNamesModule(); ModuleFactory.add(context, reserved); 90 | antiEvade = new AntiEvadeModule(); ModuleFactory.add(context, antiEvade); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/server/Server.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2021-2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.server; 20 | 21 | import arc.Core; 22 | import arc.func.Cons; 23 | import arc.util.Nullable; 24 | 25 | import mindustry.Vars; 26 | import mindustry.gen.Call; 27 | import mindustry.gen.Player; 28 | import mindustry.net.Host; 29 | 30 | import fr.zetamap.morecommands.util.Strings; 31 | 32 | 33 | public class Server { 34 | //protected SwitchModule module; 35 | /** Internal server name, in kebab case without any colors and glyphs. */ 36 | public final String name; 37 | protected String alias, displayName, ip; 38 | protected int port; 39 | protected boolean adminOnly, pingScheduled; 40 | public Host info; 41 | 42 | public Server(String name, String address) throws IllegalArgumentException { 43 | this(name, null, null, address, false); 44 | } 45 | 46 | public Server(String name, @Nullable String alias, @Nullable String displayName, String address, boolean admin) 47 | throws IllegalArgumentException { 48 | this.name = name; 49 | this.alias = alias; 50 | this.displayName = displayName; 51 | this.adminOnly = admin; 52 | setAddress(address); 53 | } 54 | 55 | public Server(String name, String ip, int port) { 56 | this(name, null, null, ip, port, false); 57 | } 58 | 59 | public Server(String name, @Nullable String alias, @Nullable String displayName, String ip, int port, boolean admin) { 60 | this.name = name; 61 | this.alias = alias; 62 | this.displayName = displayName; 63 | this.ip = ip; 64 | this.port = port; 65 | this.adminOnly = admin; 66 | } 67 | 68 | public String alias() { return alias; } 69 | public String displayName() { return displayName; } 70 | public String ip() { return ip; } 71 | public int port() { return port; } 72 | public boolean adminOnly() { return adminOnly; } 73 | 74 | /** Parses ip:port format. */ 75 | protected void setAddress(String address) { 76 | // From {@link mindustry.ui.dialogs.JoinDialog.Server#setIP(String)}. 77 | String i = address; 78 | int p = Vars.port; 79 | boolean isIpv6 = Strings.count(address, ':') > 1; 80 | 81 | if (isIpv6 && address.lastIndexOf("]:") != -1 && address.lastIndexOf("]:") != address.length()-1) { 82 | int idx = address.indexOf("]:"); 83 | i = address.substring(1, idx); 84 | p = Strings.parseInt(address.substring(idx + 2)); 85 | } else if (!isIpv6 && address.lastIndexOf(':') != -1 && address.lastIndexOf(':') != address.length()-1){ 86 | int idx = address.lastIndexOf(':'); 87 | i = address.substring(0, idx); 88 | p = Strings.parseInt(address.substring(idx+1)); 89 | } else { 90 | i = address; 91 | p = Vars.port; 92 | } 93 | 94 | if (p == Integer.MIN_VALUE) { 95 | i = address; 96 | p = Vars.port; 97 | } 98 | 99 | this.ip = i; 100 | this.port = p; 101 | } 102 | 103 | /** @return {@link #ip}{@code :}{@link #port}. */ 104 | public String address() { 105 | // From {@link mindustry.ui.dialogs.JoinDialog.Server#displayIP()}. 106 | return ip.indexOf(':') != -1 ? port != Vars.port ? '[' + ip + "]:" + port : ip : 107 | port != Vars.port ? ip + ':' + port : ip; 108 | } 109 | 110 | public void ping() { 111 | ping(null, null); 112 | } 113 | 114 | public synchronized void ping(Cons valid, Cons failed) { 115 | // Ignore if a ping is already scheduled 116 | if (pingScheduled) return; 117 | pingScheduled = true; 118 | 119 | Vars.net.pingHost(ip, port, h -> Core.app.post(() -> { 120 | info = h; 121 | pingScheduled = false; 122 | if (valid != null) valid.get(h); 123 | }), e -> Core.app.post(() -> { 124 | info = null; 125 | pingScheduled = false; 126 | if (failed != null) failed.get(e); 127 | })); 128 | } 129 | 130 | public void connect(Player player, Cons status) { 131 | PreConnect.Availability pre = PreConnect.verify(player.admin, this); 132 | status.get(pre); 133 | if (pre.ok()) { 134 | // Prefer information from ping when possible 135 | if (info == null) Call.connect(player.con, ip, port); 136 | else Call.connect(player.con, info.address, info.port); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/command/ClientCommandHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.command; 20 | 21 | import arc.struct.ObjectSet; 22 | import arc.struct.Seq; 23 | import arc.util.CommandHandler; 24 | import arc.util.CommandHandler.*; 25 | 26 | import mindustry.gen.Player; 27 | 28 | import fr.zetamap.morecommands.PlayerData; 29 | import fr.zetamap.morecommands.misc.Players; 30 | import fr.zetamap.morecommands.util.Logger; 31 | 32 | 33 | public class ClientCommandHandler { 34 | private static final Logger logger = new Logger("Client Commands"); 35 | public final CommandHandler handler; 36 | public final Seq all = new Seq<>(), admin = new Seq<>(); 37 | /** 38 | * Non-exhaustive list of potential admin commands from mindustry server or some other plugins.
39 | * More Commands admin commands will be added to this list. 40 | */ 41 | public final ObjectSet defaultAdminCommands = ObjectSet.with( 42 | "a", "js", "vanish", "killall", "pause", "rollback", "saves", "restart", "blacklist", "whitelist" 43 | ); 44 | 45 | public ClientCommandHandler(CommandHandler handler) { 46 | this.handler = handler; 47 | } 48 | 49 | public void add(String name, String desc, CommandRunner runner) { 50 | add(name, "", desc, runner); 51 | } 52 | 53 | public void add(String name, String params, String desc, CommandRunner runner) { 54 | // If the command already exist, try to place the new at the same position 55 | int index = handler.getCommandList().indexOf(c -> c.text.equals(name)); 56 | all.add(handler.register(name, params, desc, (args, player) -> { 57 | PlayerData p = getcheck(player); 58 | if (p == null) return; 59 | 60 | try { runner.accept(args, p); } 61 | catch (Exception e) { 62 | logger.err("Error while running command '@' for player '@'", e, name, player.uuid()); 63 | Players.err(player, "Error while running the command. Please report this error."); 64 | } 65 | })); 66 | if (index != -1) handler.getCommandList().insert(index, handler.getCommandList().pop()); 67 | } 68 | 69 | public void addAdmin(String name, String desc, CommandRunner runner) { 70 | addAdmin(name, "", desc, runner); 71 | } 72 | 73 | public void addAdmin(String name, String params, String desc, CommandRunner runner) { 74 | // If the command already exist, try to place the new at the same position 75 | int index = handler.getCommandList().indexOf(c -> c.text.equals(name)); 76 | defaultAdminCommands.add(admin.add(all.add(handler.register(name, params, desc, (args, player) -> { 77 | if (!player.admin) { 78 | Players.errCommandUseDenied(player); 79 | return; 80 | } 81 | 82 | PlayerData p = getcheck(player); 83 | if (p == null) return; 84 | 85 | try { runner.accept(args, p); } 86 | catch (Exception e) { 87 | logger.err("Error while running command '@' for admin player '@'", e, name, player.uuid()); 88 | Players.err(player, "Error while running the command: @", e.toString()); 89 | } 90 | })).peek()).peek().text); 91 | if (index != -1) handler.getCommandList().insert(index, handler.getCommandList().pop()); 92 | } 93 | 94 | private PlayerData getcheck(Player player) { 95 | PlayerData p = PlayerData.get(player); 96 | // Should never happen 97 | if (p == null) { 98 | logger.err("FATAL: Player '@' is not in PlayerData! Please report this error at: @.", player.uuid(), 99 | "https://github.com/ZetaMap/MoreCommands/issues/new"); 100 | Players.err(p, "FATAL: Operation not permitted! Please report this error."); 101 | } 102 | return p; 103 | } 104 | 105 | public boolean isAdmin(String name) { 106 | return defaultAdminCommands.contains(name); 107 | } 108 | 109 | public Command get(String name) { 110 | return handler.getCommandList().find(c -> c.text.equals(name)); 111 | } 112 | 113 | public void remove(String name) { 114 | handler.removeCommand(name); 115 | all.remove(c -> c.text.equals(name)); 116 | admin.remove(c -> c.text.equals(name)); 117 | } 118 | 119 | public void clear() { 120 | all.each(c -> handler.removeCommand(c.text)); 121 | all.clear(); 122 | admin.clear(); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/misc/MCEvents.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.misc; 20 | 21 | import arc.util.Nullable; 22 | 23 | import fr.zetamap.morecommands.PlayerData; 24 | import fr.zetamap.morecommands.modules.security.Punishment; 25 | import fr.zetamap.morecommands.modules.server.Server; 26 | import fr.zetamap.morecommands.modules.voting.PlayerVoteSession; 27 | import fr.zetamap.morecommands.modules.voting.VoteSession.VoteType; 28 | 29 | 30 | public class MCEvents { 31 | public static class PunishmentEvent { 32 | public final @Nullable PlayerData author, target; 33 | public final Punishment punishment; 34 | 35 | public PunishmentEvent(PlayerData author, PlayerData target, Punishment punishment) { 36 | this.author = author; 37 | this.target = target; 38 | this.punishment = punishment; 39 | } 40 | } 41 | public static class PunishmentPardonedEvent { 42 | public final @Nullable PlayerData author; 43 | public final Punishment punishment; 44 | public final Punishment.Pardon pardon; 45 | 46 | public PunishmentPardonedEvent(PlayerData author, Punishment punishment, Punishment.Pardon pardon) { 47 | this.author = author; 48 | this.punishment = punishment; 49 | this.pardon = pardon; 50 | } 51 | } 52 | /* 53 | /** Only fired for online players. *\/ 54 | public static class PunishmentExpiredEvent { 55 | public final PlayerData player; 56 | public final Punishment punishment; 57 | 58 | public PunishmentExpiredEvent(PlayerData player, Punishment punishment) { 59 | this.player = player; 60 | this.punishment = punishment; 61 | } 62 | } 63 | */ 64 | 65 | public static class GatekeeperProcessStartedEvent { 66 | public final Gatekeeper.Context context; 67 | 68 | public GatekeeperProcessStartedEvent(Gatekeeper.Context context) { 69 | this.context = context; 70 | } 71 | } 72 | /** Called after a processor finished. Instance is reused while processing a client, do not nest! */ 73 | public static class GatekeeperProcessedEvent { 74 | public String name; 75 | public Gatekeeper.Priority priority; 76 | public Gatekeeper.Result result; 77 | public final Gatekeeper.Context context; 78 | 79 | public GatekeeperProcessedEvent(Gatekeeper.Context context) { 80 | this.context = context; 81 | } 82 | 83 | public GatekeeperProcessedEvent set(String name, Gatekeeper.Priority priority, Gatekeeper.Result result) { 84 | this.name = name; 85 | this.priority = priority; 86 | this.result = result; 87 | return this; 88 | } 89 | } 90 | 91 | public static class PlayerSwitchedEvent { 92 | public final PlayerData player; 93 | public final Server server; 94 | 95 | public PlayerSwitchedEvent(PlayerData player, Server server) { 96 | this.player = player; 97 | this.server = server; 98 | } 99 | } 100 | 101 | public static class VoteSessionStartedEvent { 102 | public final PlayerVoteSession session; 103 | public final PlayerData author; 104 | 105 | public VoteSessionStartedEvent(PlayerVoteSession session, PlayerData author) { 106 | this.session = session; 107 | this.author = author; 108 | } 109 | } 110 | public static class VoteSessionVotedEvent { 111 | public final PlayerVoteSession session; 112 | public final PlayerVoteSession.VoteType type; 113 | public final PlayerData player; 114 | 115 | public VoteSessionVotedEvent(PlayerVoteSession session, VoteType type, PlayerData player) { 116 | this.session = session; 117 | this.type = type; 118 | this.player = player; 119 | } 120 | } 121 | public static class VoteSessionClosedEvent { 122 | public final PlayerVoteSession session; 123 | public final @Nullable PlayerData author; 124 | public final boolean passed; 125 | 126 | public VoteSessionClosedEvent(PlayerVoteSession session, PlayerData author, boolean passed) { 127 | this.session = session; 128 | this.author = author; 129 | this.passed = passed; 130 | } 131 | 132 | public boolean passed() { return passed && author == null; } 133 | public boolean forced() { return passed && author != null; } 134 | public boolean failed() { return !passed && author == null; } 135 | public boolean canceled() { return !passed && author != null; } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/util/VersionChecker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Anti-VPN-Service (AVS). The plugin securing your server against VPNs. 3 | * 4 | * MIT License 5 | * 6 | * Copyright (c) 2024-2025 Xpdustry 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in all 16 | * copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | * SOFTWARE. 25 | */ 26 | 27 | package fr.zetamap.morecommands.util; 28 | 29 | import arc.util.Http; 30 | 31 | import mindustry.Vars; 32 | import mindustry.mod.Mod; 33 | import mindustry.mod.Mods; 34 | 35 | 36 | public class VersionChecker { 37 | private static final Logger logger = new Logger("Updater"); 38 | 39 | public static String keyToFind = "tag_name"; 40 | public static String repoLinkFormat = "https://github.com/@/releases/latest"; 41 | public static String repoApiLinkFormat = mindustry.Vars.ghApi + "/repos/@/releases/latest"; 42 | 43 | public static UpdateState checkFor(T mod) { return checkFor(mod, true); } 44 | 45 | public static UpdateState checkFor(T mod, boolean promptStatus) { 46 | Mods.LoadedMod load = Vars.mods.getMod(mod.getClass()); 47 | if(load == null) throw new IllegalArgumentException("Mod is not loaded yet (or missing)!"); 48 | return checkFor(load.meta); 49 | } 50 | 51 | public static UpdateState checkFor(Mods.ModMeta mod) { return checkFor(mod, true); } 52 | /** 53 | * Check for update using the "version" and "repo" properties 54 | * in the mod/plugin definition (<plugin/mod>.[h]json). 55 | *

56 | * The github repo must be formatted like that "{@code /}".
57 | * The version must be formatted like that "{@code 146.2}" and can starts with "{@code v}", 58 | * but must not contains letters, like "{@code beta}" or "{@code -dev}". 59 | * 60 | * @return the update state of the mod 61 | */ 62 | public static UpdateState checkFor(Mods.ModMeta mod, boolean promptStatus) { 63 | if (promptStatus) logger.info("Checking for updates..."); 64 | 65 | if (mod.repo == null || mod.repo.isEmpty() || mod.repo.indexOf('/') == -1) { 66 | if (promptStatus) logger.warn("No repo found for an update."); 67 | return UpdateState.missing; 68 | } else if (mod.version == null || mod.version.isEmpty()) { 69 | if (promptStatus) logger.warn("No current version found for an update."); 70 | return UpdateState.missing; 71 | } 72 | 73 | UpdateState[] status = {UpdateState.error}; 74 | Http.get(Strings.format(repoApiLinkFormat, mod.repo)) 75 | .timeout(5000) 76 | .error(failure -> { 77 | if (promptStatus) logger.err("Unable to check for updates: @", failure.getLocalizedMessage()); 78 | }).block(success -> { 79 | String content = success.getResultAsString(); 80 | if (content.isBlank()) { 81 | if (promptStatus) logger.err("Unable to check for updates: no content received."); 82 | return; 83 | } 84 | 85 | // Extract the version 86 | String tagName; 87 | try { tagName = new arc.util.serialization.JsonReader().parse(content).getString(keyToFind); } 88 | catch (Exception e) { 89 | if (promptStatus) { 90 | logger.err("Unable to check for updates: invalid Json or missing key 'tag_name'."); 91 | logger.err("Error: @", e.getLocalizedMessage()); 92 | } 93 | return; 94 | } 95 | 96 | // Compare the version 97 | if (promptStatus) logger.info("Found version: @. Current version: @", tagName, mod.version); 98 | if (Strings.isVersionAtLeast(mod.version, tagName)) { 99 | if (promptStatus) logger.info("Check out this link to upgrade @: @", mod.displayName, 100 | Strings.format(repoLinkFormat, mod.repo)); 101 | status[0] = UpdateState.outdated; 102 | } else { 103 | if (promptStatus) logger.info("Already up-to-date, no need to update."); 104 | status[0] = UpdateState.uptodate; 105 | } 106 | }); 107 | 108 | return status[0]; 109 | } 110 | 111 | 112 | public static enum UpdateState { 113 | /** "version" or/and "repo" properties are missing in the mod/plugin definition. */ 114 | missing, 115 | /** Error while checking for updates. */ 116 | error, 117 | /** No new updates found, it's the latest version. */ 118 | uptodate, 119 | /** An update was found, the mod/plugin needs to be upgraded. */ 120 | outdated 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/selector/Selector.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.selector; 20 | 21 | import arc.math.geom.Vec2; 22 | import arc.struct.Seq; 23 | 24 | import mindustry.gen.Groups; 25 | import mindustry.gen.Player; 26 | import mindustry.gen.Unit; 27 | 28 | 29 | public class Selector { 30 | /** Common extractors. */ 31 | public static final Extractor 32 | /** Only players. */ 33 | playerExtractor = (l, e) -> Groups.player.each(p -> l.add(p.unit())), 34 | /** Only units. */ 35 | unitExtractor = (l, e) -> Groups.unit.each(u -> !u.isPlayer(), l::add), 36 | /** Unit and players. */ 37 | entityExtractor = (l, e) -> Groups.unit.copy(l); 38 | 39 | public final String name; 40 | /** Less than {@code 0} means no limit. */ 41 | public final int limit; 42 | public final Sorting sorting; 43 | public final Extractor extractor; 44 | public final int id; 45 | 46 | public Selector(String name, Extractor extractor) { this(name, 0, Sorting.arbitrary, extractor); } 47 | public Selector(String name, int limit, Extractor extractor) { this(name, limit, Sorting.arbitrary, extractor); } 48 | public Selector(String name, Sorting sorting, Extractor extractor) { this(name, 0, sorting, extractor); } 49 | /** 50 | * @param name the selector's name/alias. 51 | * @param one whether this selector will select only one entity. 52 | * @param sorting sorting to do after selected the units. {@code null} for no sorting. 53 | * @param extractor the entity extractor 54 | */ 55 | public Selector(String name, int limit, Sorting sorting, Extractor extractor) { 56 | this.name = name; 57 | this.limit = limit; 58 | this.sorting = sorting; 59 | this.extractor = extractor; 60 | this.id = Selectors.add(this); 61 | } 62 | 63 | public Seq select(Player executor) { return select(executor, null); } 64 | /** 65 | * If {@code executor} is {@code null} or {@link Player#dead()}, a zero position is used.
66 | * And, in this case, if {@code bounding} properties are presents, {@code positioning} properties must also be presents, 67 | * else an {@link IllegalArgumentException} is thrown. 68 | */ 69 | public Seq select(Player executor, Seq properties) { 70 | Seq selected = new Seq<>(false); // First unordered for optimization purposes 71 | Vec2 pos = new Vec2(); 72 | boolean hasProps = properties != null && properties.any(), 73 | needSorting = sorting != null, 74 | needLimiting = limit > 0; 75 | 76 | if (hasProps) properties.sort(p -> p.property.category.ordinal()); 77 | 78 | if (executor != null && !executor.dead()) pos.set(executor); 79 | else if (hasProps && 80 | properties.contains(p -> p.property.category == SelectorProperty.Category.bounding) && 81 | !properties.contains(p -> p.property.category == SelectorProperty.Category.positioning)) 82 | throw new IllegalArgumentException("Unable to find executor position. Please add positioning properties."); 83 | 84 | extractor.select(selected, executor); 85 | // Removes null units. Can happen when a selector target dead players 86 | selected.removeAll(u -> u == null); 87 | 88 | if (hasProps) { 89 | int resume = -1; 90 | // Apply properties updates and stop at sorting and limiting properties 91 | for (int i=0; i SelectorProperty.Category.other.ordinal()) { 94 | resume = i; 95 | break; 96 | } 97 | p.update(this, selected, executor, pos); 98 | } 99 | // Apply filtering 100 | selected.removeAll(e -> !properties.allMatch(p -> p.passes(this, executor, pos, e))); 101 | // Resume iteration for sorting and limiting properties 102 | for (int i=resume; i>=0 && i out, Player executor); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/util/Autosaver.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.util; 20 | 21 | import arc.ApplicationListener; 22 | import arc.Core; 23 | import arc.func.Cons2; 24 | import arc.struct.Seq; 25 | import arc.util.Interval; 26 | 27 | 28 | /** Save periodically the registered {@link Saveable}s and also when the application exit. */ 29 | public class Autosaver { 30 | protected final static Logger logger = new Logger(Autosaver.class); 31 | protected static ApplicationListener listener; 32 | protected static int spacing = 360 * 60; // in ticks 33 | /** 34 | * Doesn't log a message when starting/stopping the {@link Autosaver} or when running an auto save.
35 | * Errors will always be logged. 36 | */ 37 | public static boolean silent; 38 | /** Called when a save fail. */ 39 | public static Cons2 errorHandler; 40 | 41 | 42 | /** Adds to the {@code normal} priority. */ 43 | public static void add(Saveable saveable) { 44 | add(saveable, SavePriority.normal); 45 | } 46 | 47 | public static void add(Saveable saveable, SavePriority priority) { 48 | remove(saveable); 49 | priority.saves.add(saveable); 50 | } 51 | 52 | public static void remove(Saveable saveable) { 53 | for (SavePriority p : SavePriority.all) { 54 | if (p.saves.remove(saveable)) break; 55 | } 56 | } 57 | 58 | public static void clear() { 59 | for (SavePriority p : SavePriority.all) clear(p); 60 | } 61 | 62 | public static void clear(SavePriority priority) { 63 | priority.saves.clear(); 64 | } 65 | 66 | public static boolean has(Saveable saveable) { 67 | return priorityOf(saveable) != null; 68 | } 69 | 70 | public static boolean has(Saveable saveable, SavePriority priority) { 71 | return priority.saves.contains(saveable); 72 | } 73 | 74 | public static SavePriority priorityOf(Saveable saveable) { 75 | for (SavePriority p : SavePriority.all) { 76 | if (has(saveable, p)) return p; 77 | } 78 | return null; 79 | } 80 | 81 | public static boolean saveNeeded() { 82 | for (SavePriority p : SavePriority.all) { 83 | if (p.saves.contains(Saveable::modified)) return true; 84 | } 85 | return false; 86 | } 87 | 88 | /** Save all registered things now (only if modified). Errors are ignored and just printed. */ 89 | public static boolean save() { 90 | if (!saveNeeded()) return false; 91 | if (!silent) logger.info("Running autosave..."); 92 | for (SavePriority p : SavePriority.all) { 93 | p.saves.each(Saveable::modified, s -> { 94 | if (!silent) logger.debug("Saving @.", s.name()); 95 | try { s.save(); } 96 | catch (Throwable t) { 97 | logger.err("Failed to save @", t, s.name()); 98 | if (errorHandler != null) errorHandler.get(s, t); 99 | } 100 | }); 101 | } 102 | if (!silent) logger.info("Autosave completed."); 103 | return true; 104 | } 105 | 106 | /** Start the auto saver, will also save on application exit. */ 107 | public static boolean start() { 108 | if (isStarted()) return false; 109 | Core.app.addListener(listener = new ApplicationListener() { 110 | Interval timer = new Interval(); 111 | 112 | @Override 113 | public void update() { 114 | if (timer.get(spacing)) save(); 115 | } 116 | 117 | @Override 118 | public void dispose() { 119 | save(); 120 | stop(); 121 | } 122 | }); 123 | if (!silent) logger.info("Autosaver started!"); 124 | return true; 125 | } 126 | 127 | public static boolean stop() { 128 | if (!isStarted()) return false; 129 | Core.app.removeListener(listener); 130 | listener = null; 131 | if (!silent) logger.info("Autosaver stopped!"); 132 | return true; 133 | } 134 | 135 | public static boolean isStarted() { 136 | return listener != null; 137 | } 138 | 139 | public static int spacing() { 140 | return spacing / 60; 141 | } 142 | 143 | public static void spacing(int spacing) { 144 | if (spacing < 1) throw new IllegalArgumentException("spacing must be greater than 1 second"); 145 | Autosaver.spacing = spacing * 60; 146 | } 147 | 148 | 149 | /** Defines a things that can be saved by the {@link Autosaver}. */ 150 | public static interface Saveable { 151 | /** Used for logging. */ 152 | String name(); 153 | boolean modified(); 154 | void save(); 155 | } 156 | 157 | 158 | /** Defines the order to save things. */ 159 | public static enum SavePriority { 160 | high, normal, low; 161 | 162 | static final SavePriority[] all = values(); 163 | // More simple to store the saveable things here. 164 | // Because the priority should not be modified after registration. 165 | final Seq saves = new Seq<>(); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/misc/Range.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | 20 | package fr.zetamap.morecommands.misc; 21 | 22 | 23 | public interface Range> { 24 | T min(); 25 | T max(); 26 | 27 | default boolean contains(T value) { 28 | return value != null 29 | && (min() == null || value.compareTo(min()) >= 0) 30 | && (max() == null || value.compareTo(max()) <= 0); 31 | } 32 | 33 | /** @return {@link #min()}{@code ..}{@link #max()} or {@link #min()} if they are equals. */ 34 | default String asString() { 35 | T min = min(), max = max(); 36 | return min.equals(max) ? String.valueOf(min) : min + ".." + max; 37 | } 38 | 39 | 40 | public static interface Fixed> extends Range { 41 | T value(); 42 | 43 | default T min() { return value(); } 44 | default T max() { return value(); } 45 | default boolean contains(T v) { return v != null && (value() == null || v.compareTo(value()) == 0); } 46 | default String asString() { return String.valueOf(value()); } 47 | } 48 | 49 | 50 | /** A {@code null} {@code min} or {@code max} means no limit for the side (or both). */ 51 | public static Range of(Integer min, Integer max) { 52 | if (min != null && max != null && min > max) throw new IllegalArgumentException("min is greater than max"); 53 | return new Range() { 54 | public Integer min() { return min; } 55 | public Integer max() { return max; } 56 | public boolean contains(Integer value) { 57 | return value != null && (min == null || value >= min) && (max == null || value <= max); 58 | } 59 | public String toString() { return asString(); } 60 | }; 61 | } 62 | 63 | public static Range.Fixed fixed(int value) { 64 | return new Range.Fixed() { 65 | public Integer value() { return value; } 66 | public boolean contains(Integer v) { return v != null && v == value; } 67 | public String toString() { return asString(); } 68 | }; 69 | } 70 | 71 | /** A {@code null} {@code min} or {@code max} means no limit for the side (or both). */ 72 | public static Range of(Long min, Long max) { 73 | if (min != null && max != null && min > max) throw new IllegalArgumentException("min is greater than max"); 74 | return new Range() { 75 | public Long min() { return min; } 76 | public Long max() { return max; } 77 | public boolean contains(Long value) { 78 | return value != null && (min == null || value >= min) && (max == null || value <= max); 79 | } 80 | public String toString() { return asString(); } 81 | }; 82 | } 83 | 84 | public static Range.Fixed fixed(long value) { 85 | return new Range.Fixed() { 86 | public Long value() { return value; } 87 | public boolean contains(Long v) { return v != null && v == value; } 88 | public String toString() { return asString(); } 89 | }; 90 | } 91 | 92 | /** A {@code null} {@code min} or {@code max} means no limit for the side (or both). */ 93 | public static Range of(Float min, Float max) { 94 | if (min != null && max != null && min > max) throw new IllegalArgumentException("min is greater than max"); 95 | return new Range() { 96 | public Float min() { return min; } 97 | public Float max() { return max; } 98 | public boolean contains(Float value) { 99 | return value != null && (min == null || value >= min) && (max == null || value <= max); 100 | } 101 | public String toString() { return asString(); } 102 | }; 103 | } 104 | 105 | public static Range.Fixed fixed(float value) { 106 | return new Range.Fixed() { 107 | public Float value() { return value; } 108 | public boolean contains(Float v) { return v != null && v == value; } 109 | public String toString() { return asString(); } 110 | }; 111 | } 112 | 113 | /** A {@code null} {@code min} or {@code max} means no limit for the side (or both). */ 114 | public static Range of(Double min, Double max) { 115 | if (min != null && max != null && min > max) throw new IllegalArgumentException("min is greater than max"); 116 | return new Range() { 117 | public Double min() { return min; } 118 | public Double max() { return max; } 119 | public boolean contains(Double value) { 120 | return value != null && (min == null || value >= min) && (max == null || value <= max); 121 | } 122 | public String toString() { return asString(); } 123 | }; 124 | } 125 | 126 | public static Range.Fixed fixed(double value) { 127 | return new Range.Fixed() { 128 | public Double value() { return value; } 129 | public boolean contains(Double v) { return v != null && v == value; } 130 | public String toString() { return asString(); } 131 | }; 132 | } 133 | } -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/misc/CoordinatesParser.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2021-2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.misc; 20 | 21 | import java.util.regex.Pattern; 22 | 23 | import arc.math.geom.Vec2; 24 | 25 | import fr.zetamap.morecommands.PlayerData; 26 | import fr.zetamap.morecommands.util.Strings; 27 | 28 | 29 | /** 30 | * Minecraft-like coordinates parser, but coordinates are separated with a comma instead of a space, 31 | * and a single {@code '~'} can be used to specify both x and y axis. 32 | */ 33 | public class CoordinatesParser { 34 | private static final Pattern quotes = Pattern.compile("'(.*?)'"); 35 | public static final char worldRelativePrefix = '~', separator = ','; 36 | 37 | public final PlayerData target; 38 | public final Vec2 pos = new Vec2(); 39 | public final boolean byCoordinates; 40 | public final String[] rest; 41 | 42 | public CoordinatesParser(PlayerData executor, String[] args) throws IllegalArgumentException { 43 | this(executor, args, 0, args.length); 44 | } 45 | 46 | public CoordinatesParser(PlayerData executor, String[] args, int from, int to) throws IllegalArgumentException { 47 | if (args.length == 0 || from < 0 || to > args.length || from >= to || args[from].isEmpty()) 48 | throw new IllegalArgumentException("Missing coordinates or player name/uuid"); 49 | 50 | String coor = args[from]; 51 | int length = coor.length(); 52 | 53 | // Avoid to search a player if it's looks like coordinates 54 | if (!isRelativeCoordinate(coor, 0, length)) { 55 | Players.SearchResult result = Players.find(args, from, to); 56 | if (result.found) { 57 | if (result.player.player.dead()) 58 | throw new IllegalArgumentException("Unable to find target player position"); 59 | target = result.player; 60 | pos.set(result.player.player); 61 | byCoordinates = false; 62 | rest = result.rest; 63 | return; 64 | } 65 | } 66 | 67 | int comma = coor.indexOf(separator); 68 | float x, y; 69 | 70 | if (comma != -1) { 71 | x = parseCoordinate(executor, coor, 0, comma == -1 ? length : comma, executor.player.x); 72 | if (comma == length-1) throw new IllegalArgumentException("Missing 'y' axis after comma"); 73 | y = parseCoordinate(executor, coor, comma+1, length, executor.player.y); 74 | } else if (isRelativeCoordinate(coor, 0, length)) { 75 | if (executor.player.dead()) throw new IllegalArgumentException("Unable to find player position"); 76 | x = y = parseWorldCoordinate(coor, 1, length); 77 | x += executor.player.x; 78 | y += executor.player.y; 79 | } else { 80 | if (Strings.parseInt(coor, 10, Integer.MIN_VALUE, 0, comma) != Integer.MIN_VALUE) 81 | throw new IllegalArgumentException("Missing 'y' axis"); 82 | else throw new IllegalArgumentException("Invalid coordinates or player not found"); 83 | } 84 | 85 | 86 | target = executor; 87 | pos.set(x, y); 88 | byCoordinates = true; 89 | rest = java.util.Arrays.copyOfRange(args, from+1, to); 90 | } 91 | 92 | private static boolean isRelativeCoordinate(String arg, int from, int to) { 93 | return arg.charAt(from) == worldRelativePrefix; 94 | } 95 | 96 | private static float parseCoordinate(PlayerData executor, String arg, int from, int to, float base) { 97 | if (to <= from) throw new IllegalArgumentException("Invalid coordinates or player not found"); 98 | if (isRelativeCoordinate(arg, from, to)) { 99 | // Check whether the player is dead, because relative coordinates will be wrong 100 | if (executor.player.dead()) throw new IllegalArgumentException("Unable to find player position"); 101 | return base + parseWorldCoordinate(arg, from+1, to); 102 | } 103 | return parseWorldCoordinate(arg, from, to); 104 | } 105 | 106 | private static float parseWorldCoordinate(String arg, int from, int to) { 107 | if (to <= from) return 0f; 108 | int offset = Strings.parseInt(arg, 10, Integer.MIN_VALUE, from, to); 109 | if (offset == Integer.MIN_VALUE) throw new IllegalArgumentException("Invalid coordinates or player not found"); 110 | return offset * mindustry.Vars.tilesize; // scale 111 | } 112 | 113 | public static CoordinatesParser parse(PlayerData executor, String[] args) { return parse(executor, args, 0, args.length); } 114 | public static CoordinatesParser parse(PlayerData executor, String[] args, int from, int to) { 115 | try { return new CoordinatesParser(executor, args, from, to); } 116 | catch (Exception e) { 117 | String message = e.getMessage(); 118 | if (message == null || message.isEmpty()) message = e.getClass().getSimpleName(); 119 | else { 120 | if (message.charAt(message.length()-1) != '.') message += '.'; 121 | message = quotes.matcher(message).replaceAll("'[orange]$1[]'"); 122 | } 123 | Players.err(executor, message); 124 | } 125 | return null; 126 | } 127 | } -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/security/ReservedNamesModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.security; 20 | 21 | import arc.struct.Seq; 22 | 23 | import mindustry.Vars; 24 | import mindustry.net.Packets.KickReason; 25 | 26 | import fr.zetamap.morecommands.command.ServerCommandHandler; 27 | import fr.zetamap.morecommands.misc.Gatekeeper; 28 | import fr.zetamap.morecommands.module.AbstractSaveableModule; 29 | import fr.zetamap.morecommands.util.JsonSettings; 30 | import fr.zetamap.morecommands.util.Strings; 31 | 32 | 33 | public class ReservedNamesModule extends AbstractSaveableModule { 34 | private boolean enabled; 35 | private String message; 36 | 37 | public final Seq names = new Seq<>(); 38 | 39 | public boolean add(String name) { 40 | setModified(); 41 | return names.addUnique(clean(name)); 42 | } 43 | 44 | public boolean remove(String name) { 45 | setModified(); 46 | return names.remove(clean(name)); 47 | } 48 | 49 | public boolean isReserved(String name) { 50 | name = clean(name); 51 | return names.contains(name::contains); 52 | } 53 | 54 | /** Removes colors and glyphs, and lower case the name. */ 55 | public String clean(String name) { 56 | return Strings.normalize(name).toLowerCase(); 57 | } 58 | 59 | public boolean enabled() { 60 | return enabled; 61 | } 62 | 63 | public void enable() { 64 | enabled = true; 65 | setModified(); 66 | } 67 | 68 | public void disable() { 69 | enabled = false; 70 | setModified(); 71 | } 72 | 73 | public String kickMessage() { 74 | return message; 75 | } 76 | 77 | public void kickMessage(String message) { 78 | this.message = message; 79 | setModified(); 80 | } 81 | 82 | @Override 83 | protected void initImpl() { 84 | Gatekeeper.add(internalName(), Gatekeeper.Priority.low, ctx -> 85 | enabled() && !Vars.netServer.admins.isAdmin(ctx.uuid, ctx.usid) && isReserved(ctx.strippedName) ? 86 | kickMessage() == null ? Gatekeeper.reject(KickReason.nameInUse) : Gatekeeper.reject(kickMessage()) : 87 | Gatekeeper.accept()); 88 | } 89 | 90 | @SuppressWarnings("unchecked") 91 | @Override 92 | protected void loadImpl(JsonSettings settings) { 93 | enabled = settings.getBool("enabled", true); 94 | message = settings.getString("message", "This nickname (or a part) is reserved, please choose another one."); 95 | names.clear(); 96 | names.addAll(settings.getOrPut("names", Seq.class, String.class, Seq::new)); 97 | } 98 | 99 | @Override 100 | protected void saveImpl(JsonSettings settings) { 101 | settings.put("enabled", enabled); 102 | settings.put("message", message); 103 | settings.put("names", String.class, names); 104 | } 105 | 106 | public void registerServerCommands(ServerCommandHandler handler) { 107 | handler.add("reserved-names", "[on|off|add|remove|message] [name|text...]", "Reserved nicknames can only be used by admins.", 108 | args -> { 109 | if (args.length == 0) { 110 | logger.info("&fiNote: The case, colors and glyphs are ignored and removed during the nickname check."); 111 | logger.info("Kick message: @", message == null ? "&fi(empty)" : message); 112 | if (names.isEmpty()) 113 | logger.info("Reserved Nicknames: [@, @]", enabled ? "&fb&lgenabled&fr" : "&fb&lrdisabled&fr", "empty"); 114 | else { 115 | logger.info("Reserved Nicknames: [@, total: @]", enabled ? "&fb&lgenabled&fr" : "&fb&lrdisabled&fr", names.size); 116 | names.each(n -> logger.info("&lk|&fr @", n)); 117 | } 118 | 119 | } else if (args[0].equals("add")) { 120 | if (args.length == 1) logger.err("Missing 'name' argument."); 121 | else if (add(args[1])) logger.info("Nickname added to the list."); 122 | else logger.info("Nickname already in the list."); 123 | 124 | } else if (args[0].equals("remove")) { 125 | if (args.length == 1) logger.err("Missing 'name' argument."); 126 | else if (remove(args[1])) logger.info("Nickname removed from the list."); 127 | else logger.info("Nickname not in the list."); 128 | 129 | } else if (args[0].equals("message")) { 130 | if (args.length == 1) logger.err("Missing 'text' argument."); 131 | else if (args[1].equals("\"\"")) { 132 | kickMessage(null); 133 | logger.info("Kick message removed."); 134 | } else { 135 | kickMessage(args[1]); 136 | logger.info("Kick message modified."); 137 | } 138 | 139 | } else if (Strings.isTrue(args[0])) { 140 | enable(); 141 | logger.info("Reserved nicknames list enabled."); 142 | 143 | } else if (Strings.isFalse(args[0])) { 144 | disable(); 145 | logger.info("Reserved nicknames list disabled."); 146 | 147 | } else logger.err("Invalid argument! Must be 'on', 'off', 'add', 'remove' or 'message'."); 148 | }); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/security/AntiEvadeModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.security; 20 | 21 | import arc.Events; 22 | import arc.struct.ObjectMap; 23 | import arc.util.Time; 24 | import arc.util.Timer; 25 | import arc.util.pooling.Pool; 26 | import arc.util.pooling.Pools; 27 | 28 | import mindustry.game.EventType; 29 | 30 | import fr.zetamap.morecommands.PlayerData; 31 | import fr.zetamap.morecommands.command.ServerCommandHandler; 32 | import fr.zetamap.morecommands.misc.Players; 33 | import fr.zetamap.morecommands.module.AbstractSaveableModule; 34 | import fr.zetamap.morecommands.util.DurationFormatter; 35 | import fr.zetamap.morecommands.util.JsonSettings; 36 | import fr.zetamap.morecommands.util.Strings; 37 | 38 | 39 | public class AntiEvadeModule extends AbstractSaveableModule { 40 | private Timer.Task cleaner; 41 | private float retainTime = 1 * 60; // in minutes 42 | 43 | public final ObjectMap quits = new ObjectMap<>(); 44 | 45 | /** Clear the cache. */ 46 | public void clearCache() { 47 | quits.clear(); 48 | } 49 | 50 | /** Removes entries older than {@link #retainTime}. */ 51 | public void cleanupCache() { 52 | ObjectMap.Values v = quits.values(); 53 | long now = Time.millis(), retain = retainMillis(); 54 | while (v.hasNext()) { 55 | CacheEntry e = v.next(); 56 | if (now - e.time >= retain) v.remove(); 57 | } 58 | } 59 | 60 | /** @return cache retain in minutes. */ 61 | public float retain() { 62 | return retainTime; 63 | } 64 | 65 | public long retainMillis() { 66 | return (long)(retainTime * 60 * 1000); 67 | } 68 | 69 | public void setRetain(float minutes) { 70 | retainTime = Math.max(minutes, 1); 71 | setModified(); 72 | } 73 | 74 | @Override 75 | protected void initImpl() { 76 | Events.on(EventType.PlayerJoin.class, e -> { 77 | if (isDisposed()) return; 78 | CacheEntry entry = quits.get(e.player.uuid()); 79 | if (entry == null) return; 80 | String name = entry.name; 81 | Pools.free(quits.remove(e.player.uuid())); 82 | if (e.player.admin) return; 83 | PlayerData player = PlayerData.get(e.player); 84 | if (player.stripedName.equals(Strings.normalize(name))) return; 85 | PlayerData.each(p -> p != player, p -> 86 | Players.warn(p, "[scarlet]Warning[]: the player @[orange] has changed his name. He was @[orange].", 87 | player.getName(), name)); 88 | }); 89 | 90 | Events.on(EventType.PlayerLeave.class, e -> { 91 | if (isDisposed() || e.player.admin) return; // ignore admins 92 | CacheEntry entry = Pools.obtain(CacheEntry.class, CacheEntry::new) 93 | .set(e.player.uuid(), PlayerData.get(e.player).getName(), Time.millis()); 94 | quits.put(e.player.uuid(), entry); 95 | }); 96 | 97 | // Clean the cache every minutes 98 | cleaner = Timer.schedule(this::cleanupCache, 60, 60); 99 | } 100 | 101 | @Override 102 | protected void disposeImpl() { 103 | cleaner.cancel(); 104 | quits.clear(); 105 | } 106 | 107 | @Override 108 | protected void loadImpl(JsonSettings settings) { 109 | retainTime = settings.getFloat("retain", 1 * 60); 110 | } 111 | 112 | @Override 113 | protected void saveImpl(JsonSettings settings) { 114 | settings.put("retain", retainTime); 115 | } 116 | 117 | @Override 118 | public void registerServerCommands(ServerCommandHandler handler) { 119 | handler.add("anti-evade", "[clear|minutes]", "Control the anti evade system.", args -> { 120 | if (args.length == 0) { 121 | logger.info("Anti evade is a system that notify players when one quit and reconnect with another nickname."); 122 | logger.info("This system will ignore admin players and will retain nicknames @ after disconnection.", 123 | DurationFormatter.format(retainMillis())); 124 | } else if (args[0].equals("clear")) { 125 | clearCache(); 126 | logger.info("Cache cleared."); 127 | } else { 128 | int minutes = Strings.parseInt(args[0]); 129 | if (minutes == Integer.MIN_VALUE) { 130 | logger.err("Invalid argument! Must be 'clear' or a number of minutes."); 131 | return; 132 | } else if (minutes < 1) { 133 | logger.err("Should be greater than 1 minute."); 134 | return; 135 | } 136 | setRetain(minutes); 137 | logger.info("Nicknames will be retained for @ after disconnection.", DurationFormatter.format(retainMillis())); 138 | } 139 | }); 140 | } 141 | 142 | 143 | public static class CacheEntry implements Pool.Poolable { 144 | public String uuid, name; 145 | public long time; 146 | 147 | CacheEntry set(String uuid, String name, long time) { 148 | this.uuid = uuid; 149 | this.name = name; 150 | this.time = time; 151 | return this; 152 | } 153 | 154 | public void reset() { 155 | uuid = name = null; 156 | time = 0; 157 | } 158 | } 159 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/godmode/GodmodeModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.godmode; 20 | 21 | import arc.Events; 22 | 23 | import mindustry.Vars; 24 | import mindustry.game.EventType; 25 | import mindustry.gen.Call; 26 | import mindustry.gen.Unitc; 27 | import mindustry.net.Administration.ActionType; 28 | import mindustry.world.blocks.ConstructBlock; 29 | 30 | import fr.zetamap.morecommands.Modules; 31 | import fr.zetamap.morecommands.PlayerData; 32 | import fr.zetamap.morecommands.command.ClientCommandHandler; 33 | import fr.zetamap.morecommands.misc.Players; 34 | import fr.zetamap.morecommands.module.AbstractModule; 35 | import fr.zetamap.morecommands.modules.selector.SelectorParser; 36 | import fr.zetamap.morecommands.util.Strings; 37 | 38 | 39 | public class GodmodeModule extends AbstractModule { 40 | public void setGodmode(PlayerData player, boolean enabled) { 41 | player.inGodmode = enabled; 42 | if (player.player.dead()) return; 43 | player.player.unit().health = enabled ? Float.POSITIVE_INFINITY : player.player.unit().maxHealth; 44 | } 45 | 46 | /** @return the new godmode status. */ 47 | public boolean toggleGodmode(PlayerData player) { 48 | setGodmode(player, !player.inGodmode); 49 | return player.inGodmode; 50 | } 51 | 52 | @Override 53 | protected void initImpl() { 54 | // GodMode: instant build 55 | Vars.netServer.admins.addActionFilter(a -> { 56 | PlayerData p = PlayerData.get(a.player); 57 | if (p != null && p.inGodmode) { 58 | if (a.type == ActionType.placeBlock) 59 | ConstructBlock.constructed(a.tile, a.block, a.player.unit(), (byte)a.rotation, a.player.team(), a.config); 60 | else if (a.type == ActionType.breakBlock) 61 | Call.deconstructFinish(a.tile, a.block, a.player.unit()); 62 | } 63 | return true; 64 | }); 65 | 66 | // GodMode: infinite health 67 | Events.on(EventType.UnitChangeEvent.class, e -> { 68 | PlayerData player = PlayerData.get(e.player); 69 | if (player == null) return; 70 | if (player.lastUnit != null && player.lastUnit.health == Float.POSITIVE_INFINITY) player.lastUnit.clampHealth(); 71 | if (player.inGodmode && !player.player.dead()) e.unit.health = Float.POSITIVE_INFINITY; 72 | player.lastUnit = e.unit; 73 | }); 74 | 75 | // I need that to know when a player respawn to the core =/ 76 | Events.run(EventType.Trigger.beforeGameUpdate, () -> 77 | PlayerData.each(p -> p.inGodmode && !p.player.dead(), 78 | p -> p.player.unit().health = Float.POSITIVE_INFINITY) 79 | ); 80 | 81 | // GodMode: instant unit kill 82 | Events.on(EventType.UnitDamageEvent.class, e -> { 83 | if (e.unit.dead || !(e.bullet.owner instanceof Unitc) || e.bullet.owner == e.unit) return; 84 | PlayerData player = PlayerData.get(((Unitc)e.bullet.owner).getPlayer()); 85 | if (player == null || !player.inGodmode) return; 86 | e.unit.kill(); 87 | }); 88 | 89 | // GodMode: instant block destroy 90 | Events.on(EventType.BuildDamageEvent.class, e -> { 91 | if (e.build.dead || !(e.source.shooter instanceof Unitc)) return; 92 | PlayerData player = PlayerData.get(((Unitc)e.source.shooter).getPlayer()); 93 | if (player == null || !player.inGodmode) return; 94 | e.build.kill(); 95 | }); 96 | } 97 | 98 | @Override 99 | public void registerClientCommands(ClientCommandHandler handler) { 100 | handler.addAdmin("godmode", "[on|off] [player|selector...]", "[coral][[[scarlet]God[]]: [gold]I'm divine!", 101 | (args, player) -> { 102 | if (args.length == 0) { 103 | Players.info(player, "Godmode is currently [accent]@[].", player.inGodmode ? "enabled" : "disabled"); 104 | return; 105 | } 106 | 107 | boolean enable; 108 | if (Strings.isTrue(args[0])) enable = true; 109 | else if (Strings.isFalse(args[0])) enable = false; 110 | else { 111 | Players.err(player, "Invalid argument! Must be 'on' or 'off'."); 112 | return; 113 | } 114 | 115 | if (args.length == 1) { 116 | if ((enable && player.inGodmode) || (!enable && !player.inGodmode)) 117 | Players.err(player, "Godmode already [orange]@[].", enable ? "enabled" : "disabled"); 118 | else { 119 | setGodmode(player, enable); 120 | Players.ok(player, "Godmode [accent]@[].", enable ? "enabled" : "disabled"); 121 | } 122 | return; 123 | } 124 | 125 | SelectorParser selector = Modules.selector.parse(player, args, 1, args.length, true); 126 | if (selector == null) return; 127 | int[] count = {0}; 128 | selector.execute((p, u) -> { 129 | if ((enable && p.inGodmode) || (!enable && !p.inGodmode)) 130 | Players.warn(player, "Godmode already [accent]@[] for @[orange].", enable ? "enabled" : "disabled", p.getName()); 131 | else { 132 | count[0]++; 133 | setGodmode(p, enable); 134 | if (p == player) return; 135 | Players.warn(p, "Godmode [accent]@[] by @[orange].", enable ? "enabled" : "disabled", player.getName()); 136 | } 137 | }); 138 | Players.ok(player, "@ godmode for [accent]@[] players.", enable ? "Enabled" : "Disabled", count[0]); 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/tp/TeleportModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.tp; 20 | 21 | import arc.math.geom.Position; 22 | 23 | import mindustry.core.World; 24 | import mindustry.gen.Call; 25 | import mindustry.gen.Unit; 26 | 27 | import fr.zetamap.morecommands.Modules; 28 | import fr.zetamap.morecommands.PlayerData; 29 | import fr.zetamap.morecommands.command.ClientCommandHandler; 30 | import fr.zetamap.morecommands.misc.CoordinatesParser; 31 | import fr.zetamap.morecommands.misc.Players; 32 | import fr.zetamap.morecommands.module.AbstractModule; 33 | import fr.zetamap.morecommands.modules.selector.SelectorParser; 34 | import fr.zetamap.morecommands.modules.selector.Selectors; 35 | 36 | 37 | public class TeleportModule extends AbstractModule { 38 | public void teleport(PlayerData player, Position pos) { 39 | player.player.set(pos); 40 | player.player.snapInterpolation(); 41 | if (!player.player.dead()) { 42 | player.player.unit().set(pos); 43 | player.player.unit().snapInterpolation(); 44 | } 45 | Call.setPosition(player.player.con, pos.getX(), pos.getY()); 46 | } 47 | 48 | public void teleport(Unit unit, Position pos) { 49 | unit.set(pos); 50 | unit.snapInterpolation(); 51 | if (!unit.isPlayer()) return; 52 | unit.getPlayer().set(pos); 53 | unit.getPlayer().snapInterpolation(); 54 | Call.setPosition(unit.getPlayer().con, pos.getX(), pos.getY()); 55 | } 56 | 57 | @Override 58 | public void registerClientCommands(ClientCommandHandler handler) { 59 | handler.addAdmin("tp", " [player|dest-x,y...]", "Teleport to a location or player.", 60 | (args, player) -> { 61 | if (args[0].isEmpty()) Players.err(player, "Missing player, coordinates or selector."); 62 | 63 | else if (Selectors.isSelector(args[0])) { 64 | SelectorParser selector = Modules.selector.parse(player, args); 65 | if (selector == null) return; // Error already send to player 66 | CoordinatesParser dest = CoordinatesParser.parse(player, selector.rest); 67 | if (dest == null) return; // Error already send to player 68 | 69 | int tx = World.toTile(dest.pos.x), ty = World.toTile(dest.pos.y), x = (int)dest.pos.x, y = (int)dest.pos.y; 70 | selector.execute((p, u) -> { 71 | if (p != null) { 72 | teleport(p, dest.pos); 73 | if (p == player) return; 74 | if (dest.byCoordinates) 75 | Players.warn(p, "You have been teleported to [accent]@,@[] [gray]([lightgray]@[],[lightgray]@[])[] by @[orange].", 76 | tx, ty, x, y, player.getName()); 77 | else Players.warn(p, "You have been teleported to @[orange] by @[orange].", dest.target.getName(), player.getName()); 78 | } else teleport(u, dest.pos); 79 | }); 80 | if (dest.byCoordinates) 81 | Players.ok(player, "@[green] to [accent]@,@[] [gray]([lightgray]@[],[lightgray]@[])[].", 82 | selector.formatMessage("Teleported", true), tx, ty, x, y); 83 | else Players.ok(player, "@[green] to @[green].", selector.formatMessage("Teleported", true), dest.target.getName()); 84 | 85 | } else { 86 | CoordinatesParser src = CoordinatesParser.parse(player, args); 87 | if (src == null) return; // Error already send to player 88 | else if (src.byCoordinates && src.rest.length > 0) 89 | Players.err(player, "Too many arguments. " 90 | + "Usage: [orange]/tp [] or [orange]/tp []."); 91 | 92 | else if (src.rest.length > 0) { 93 | CoordinatesParser dest = CoordinatesParser.parse(player, src.rest); 94 | if (dest == null) return; // Error already send to player 95 | 96 | teleport(src.target, dest.pos); 97 | if (dest.byCoordinates) { 98 | int tx = World.toTile(dest.pos.x), ty = World.toTile(dest.pos.y), x = (int)dest.pos.x, y = (int)dest.pos.y; 99 | Players.ok(player, "Teleported @[green] to [accent]@,@[] [gray]([lightgray]@[],[lightgray]@[])[].", 100 | src.target.getName(), tx, ty, x, y); 101 | Players.warn(src.target, "You have been teleported to [accent]@,@[] [gray]([lightgray]@[],[lightgray]@[])[] by @[orange].", 102 | tx, ty, x, y, player.getName()); 103 | } else { 104 | Players.ok(player, "Teleported @[green] to @[green].", src.target.getName(), dest.target.getName()); 105 | Players.warn(src.target, "You have been teleported to @[orange] by @[orange].", dest.target.getName(), 106 | player.getName()); 107 | } 108 | 109 | } else if (player.player.dead()) { 110 | Players.err(player, "Unable to find player position."); 111 | } else { 112 | teleport(player, src.pos); 113 | if (src.byCoordinates) { 114 | int tx = World.toTile(src.pos.x), ty = World.toTile(src.pos.y), x = (int)src.pos.x, y = (int)src.pos.y; 115 | Players.ok(player, "You teleported to @,@ [gray]([lightgray]@[],[lightgray]@[])[].", tx, ty, x, y); 116 | } else Players.ok(player, "You teleported to @[green].", src.target.getName()); 117 | } 118 | } 119 | }); 120 | 121 | //IDEA: /tpa, /tpahere, /tpaccept, /tpdeny. 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Visitor Badge](https://visitor-badge.laobi.icu/badge?page_id=ZetaMap.moreCommands) ![Download](https://shields.io/github/downloads/ZetaMap/moreCommands/total) 2 | 3 | # More Commands plugin 4 | This plugin adds a bunch of commands (60+) to your server. 5 | 6 | > [!IMPORTANT] 7 | > This plugin requires Java 12 or higher.
8 | > To download Java 12, follow steps [here](https://www.oracle.com/fr/java/technologies/javase/jdk12-archive-downloads.html) or [here](https://www.oracle.com/java/technologies/downloads/) to download the latest version. 9 | 10 | 11 | ## Player commands *(total: 14)* 12 | * ``/help [command|page]`` - Lists all commands or selector examples. 13 | * ``/w `` - Whisper to a player. 14 | * ``/r `` - Reply to the last whispered message. 15 | * ``/votekick [player] [reason...]`` - Vote to kick a player with a valid reason. 16 | * ``/vote `` - Vote to kick the current player. Admins can cancel the vote with *'c'*. 17 | * ``/maps [page]`` - List all maps of the server. 18 | * ``/vnw [y|n|c|f|number]`` - Vote for sending a new wave. 19 | * ``/rtv [y|n|c|f|mapName...]`` - Vote to change the map. 20 | * ``/rainbow [on|off] [selector|player...]`` - RAINBOW!! 21 | * ``/effect [stop|list|search|name|id] [page|selector|player...]`` - Gives you a particle effect. 22 | * ``/hub`` - Connect you to the hub server. Shortcut of *'/switch hub'*. 23 | * ``/switch [name|alias...]`` - Connect you to another server. 24 | * ``/sync [selector|player...]`` - Re-synchronize world state of a player. 25 | * ``/pinfo [uuid|nickname...]`` - Get all player informations. 26 | 27 | ## Admin commands *(total: 25)* 28 | * ``/chat [on|off]`` - Toggle the chat. 29 | * ``/tag [page|set|remove] [UUID] [tag...]`` - Configure the tag system. 30 | * ``/team [teamName|vanish|~] [player|selector...]`` - Change team. 31 | * ``/tp [player|dest-x,y...]`` - Teleport to a location or player. 32 | * ``/place [player|x,y] [teamName|~] [buildData...]`` - Place a block. 33 | * ``/fill [teamName|~] [buildData...]`` - Fill a zone. 34 | * ``/core [player|x,y] [teamName|~...]`` - Build a core. 35 | * ``/spawn [count] [player|x,y] [teamName|~] [unitData...]`` - Spawn a unit. 36 | * ``/transform [player|selector] [unitData...]`` - Transform a player unit. 37 | * ``/kill [player|selector...]`` - Kill a player or a unit. 38 | * ``/clear-map [hard|y|n]`` - Kill all units and blocks, except cores, on the map. 39 | * ``/weather [clear|weatherName] [intensity] [inf|duration]`` - Control map weather. 40 | * ``/fillitems [team|all] [items...]`` - Fill the core of the specified or all teams, with the selected or all items. 41 | * ``/gamemode [name]`` - Change the current map gamemode. 42 | * ``/pause `` - Toggle the game state. 43 | * ``/godmode [on|off] [player|selector...]`` - **[God]:** I'm divine! 44 | * ``/ban [time] [reason...]`` - Ban a player. 45 | * ``/unban [reason...]`` - Unban a player. 46 | * ``/kick [time] [reason...]`` - Kick a player. 47 | * ``/warn `` - Warn a player. 48 | * ``/mute [time] [reason...]`` - Mute a player. 49 | * ``/unmute [reason...]`` - Unmute a player. 50 | * ``/freeze [time] [reason...]`` - Freeze a player. 51 | * ``/unfreeze [reason...]`` - Unfreeze a player. 52 | * ``/pardon [reason...]`` - Pardon a player or a punishment. 53 | 54 | ## Server commands *(total: 23)* 55 | * ``morecommands [on|off|reload|save] [moduleName]`` - Manage the MoreCommands modules. Requires a server restart to apply the changes. 56 | * ``commands [reset|on|off] [command] [now]`` - Toggle client/server commands. 57 | * ``chat [on|off]`` - Toggle the in-game chat. 58 | * ``tag [on|off|set|remove] [UUID] [tag...]`` - Configure the tag system. 59 | * ``effect [reset|id|name] [on|off|admins|everyone]`` - Manage the particles effects. 60 | * ``switch [help|arg0] [args...]`` - Configures the switch system. Use *'switch help'* for usage. 61 | * ``restart`` - Reconnects players and exits the server with code *'2'*, to ask a restart from the launcher script. 62 | * ``speed [value]`` - Control the game speed. **USE WITH CAUTION!** 63 | * ``fillitems [team|all] [items...]`` - Fill the core of the specified or all teams, with the selected or all items. 64 | * ``gamemode [name]`` - Change the current map gamemode. 65 | * ~~`blacklist [value...]` Players using a nickname or ip in the blacklist cannot connect.~~
66 | **Use [this plugin](https://github.com/xpdustry/simple-blacklist) instead. (the current configuration will be migrated automatically if the plugin is present)** 67 | * ~~`anti-vpn [on|off|token] [your_token]` Anti VPN service.~~
68 | **Use [this plugin](https://github.com/xpdustry/anti-vpn-service) instead. (the current configuration will be migrated automatically if the plugin is present)** 69 | * ``bans [all]`` - List all banned players and IPs. 70 | * ``ban [time] [reason...]`` - Ban a player. 71 | * ``unban [reason...]`` - Unban a player. 72 | * ``kick [time] [reason...]`` - Kick a player. 73 | * ``warn `` - Warn a player. 74 | * ``mute [time] [reason...]`` - Mute a player. 75 | * ``unmute [reason...]`` - Unmute a player. 76 | * ``freeze [time] [reason...]`` - Freeze a player. 77 | * ``unfreeze [reason...]`` - Unfreeze a player. 78 | * ``pardon [reason...]`` - Pardon a player or a punishment. 79 | * ``punishments [type|uuid|ip] [all|type]`` - View all or a player punishments. 80 | * ``reserved-names [on|off|add|remove|message] [name|text...]`` - Reserved nicknames can only be used by admins. 81 | * ``anti-evade [clear|minutes]`` - Control the anti evade system. 82 | 83 | 84 | ## Building 85 | Pre-build releases can be found [here](https://github.com/ZetaMap/moreCommands/releases).
86 | Or you can build it yourself by running ``./gradlew build``. The file will be named ``more-commands.jar`` 87 | 88 | 89 | ## Documentation 90 | ### Selectors 91 | **TODO** 92 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/voting/VoteNewWaveSession.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.voting; 20 | 21 | import mindustry.Vars; 22 | import mindustry.gen.Call; 23 | 24 | import fr.zetamap.morecommands.PlayerData; 25 | import fr.zetamap.morecommands.misc.Players; 26 | import fr.zetamap.morecommands.util.DurationFormatter; 27 | import fr.zetamap.morecommands.util.Strings; 28 | 29 | 30 | public class VoteNewWaveSession extends PlayerVoteSession { 31 | public VoteNewWaveSession() { 32 | super(1* 60, 2 * 60); 33 | } 34 | 35 | public boolean canStart(PlayerData player, Integer wave) { 36 | if (PlayerData.size() < 3 && !player.admin()) { 37 | Players.err(player, "At least 3 players are required to start a vote."); 38 | return false; 39 | } else if (started()) { 40 | Players.err(player, "A vote to run [orange]@[] is already in progress!\n" 41 | + "Type [orange]/vnw y[] or [orange]/vnw n[] to agree or not.", stringObjective()); 42 | return false; 43 | } else if (waitRemaining() > 0) { 44 | Players.err(player, "You must wait [orange]@[] before able to restart a vote.", 45 | DurationFormatter.format(waitRemaining())); 46 | return false; 47 | } else if (wave < 1) { 48 | Players.err(player, "Invalid number of wave. Must be greater than [orange]1[]."); 49 | return false; 50 | } 51 | return true; 52 | } 53 | 54 | /** Start a new vote session to skip one wave. */ 55 | public boolean start(PlayerData player) { 56 | return start(player, 1); 57 | } 58 | 59 | public boolean canVote(PlayerData player) { 60 | if (!started()) { 61 | Players.err(player, "No vote session in progress."); 62 | return false; 63 | } else if (voted(player) != null) { 64 | Players.info(player, "You already voted to run [accent]@[].", stringObjective()); 65 | return false; 66 | } 67 | return true; 68 | } 69 | 70 | public boolean canStop(PlayerData player) { 71 | if (!started()) { 72 | Players.err(player, "No vote session in progress."); 73 | return false; 74 | } else if (!player.admin()) { 75 | Players.errArgUseDenied(player); 76 | return false; 77 | } 78 | return true; 79 | } 80 | 81 | @Override 82 | public int required() { 83 | return PlayerData.size() / 2 + 1; 84 | } 85 | 86 | /** 87 | * Skip the waves.
88 | * Note that doesn't just increases the wave number, it runs them. 89 | */ 90 | public void skipWaves() { 91 | int waves = objective(); 92 | while (waves-- > 0) Vars.logic.skipWave(); 93 | } 94 | 95 | @Override 96 | protected void sessionStarted(PlayerData by) { 97 | int remaining = required() - votes(); 98 | String vote = remaining == 1 ? "vote is" : "votes are"; 99 | Call.sendMessage( 100 | Strings.format("[scarlet]VNW: @ [white] started a vote to run [accent]@[].\n" 101 | + "[scarlet]VNW: [accent]@[white] more @ required [gray]([lightgray]@[]/[lightgray]@[])[]. " 102 | + "Type [orange]/vnw y[] or [orange]/vnw n[] to agree or not.", 103 | by.getName(), stringObjective(), remaining, vote, votes(), required())); 104 | } 105 | 106 | @Override 107 | protected void sessionPassed() { 108 | Call.sendMessage(Strings.format("[scarlet]VNW:[green] Vote passed, [accent]@[] will start soon.", stringObjective())); 109 | skipWaves(); 110 | } 111 | 112 | @Override 113 | protected void sessionForced(PlayerData by) { 114 | Call.sendMessage(Strings.format("[scarlet]VNW:[green] Vote skipped by @[green], [accent]@[] will start soon.", 115 | by.getName(), stringObjective())); 116 | skipWaves(); 117 | } 118 | 119 | @Override 120 | protected void sessionFailed() { 121 | Call.sendMessage(Strings.format("[scarlet]VNW: Vote failed![] Not enough votes to run [accent]@[].", stringObjective())); 122 | } 123 | 124 | @Override 125 | protected void sessionCanceled(PlayerData by) { 126 | Call.sendMessage(Strings.format("[scarlet]VNW:[orange] Vote cancelled by @[orange].", by.getName())); 127 | } 128 | 129 | @Override 130 | protected void sessionVote(PlayerData who, VoteType type) { 131 | int remaining = required() - votes(); 132 | String vote = remaining == 1 ? "vote is" : "votes are"; 133 | Call.sendMessage( 134 | Strings.format("[scarlet]VNW: @ [white] voted to @run [accent]@[white].\n" 135 | + "[scarlet]VNW: [accent]@[white] more @ required [gray]([lightgray]@[]/[lightgray]@[])[]." 136 | + "Type [orange]/vnw y[] or [orange]/vnw n[] to agree or not with him.", 137 | who.getName(), type.yes() ? "" : "not ", stringObjective(), remaining, vote, votes(), required())); 138 | } 139 | 140 | @Override 141 | protected void sessionVoteRemoved(PlayerData who) { 142 | int remaining = required() - votes(); 143 | String vote = remaining == 1 ? "vote is" : "votes are"; 144 | Call.sendMessage(Strings.format("[scarlet]VNW: @ [orange]left the game, [accent]@[white] more @ now required " 145 | + "[gray]([lightgray]@[]/[lightgray]@[])[].", 146 | who.getName(), remaining, vote, votes(), required())); 147 | } 148 | 149 | protected String stringObjective() { 150 | int waves = objective(); 151 | return (waves == 1 ? "the" : waves+"") + ' ' + (waves == 1 ? "wave" : "waves"); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/modules/manager/ManagerModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of MoreCommands. The plugin that adds a bunch of commands to your server. 3 | * Copyright (c) 2025 ZetaMap 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package fr.zetamap.morecommands.modules.manager; 20 | 21 | import arc.struct.ObjectSet; 22 | import arc.struct.Seq; 23 | 24 | import fr.zetamap.morecommands.command.ServerCommandHandler; 25 | import fr.zetamap.morecommands.module.AbstractModule; 26 | import fr.zetamap.morecommands.module.Module; 27 | import fr.zetamap.morecommands.module.ModuleFactory; 28 | import fr.zetamap.morecommands.util.Autosaver; 29 | import fr.zetamap.morecommands.util.Strings; 30 | 31 | 32 | /** MoreCommands manager module. */ 33 | public class ManagerModule extends AbstractModule { 34 | public final ObjectSet protectedModules = ObjectSet.with("manager", "commands"); 35 | 36 | public boolean enabled(Module module) { 37 | return ModuleFactory.enabled(module); 38 | } 39 | 40 | public void enable(Module module) { 41 | if (isProtected(module)) return; 42 | ModuleFactory.enable(module); 43 | } 44 | 45 | public void disable(Module module) { 46 | if (isProtected(module)) return; 47 | ModuleFactory.disable(module); 48 | } 49 | 50 | public boolean isProtected(Module module) { 51 | return protectedModules.contains(module.internalName()); 52 | } 53 | 54 | @Override 55 | protected void initImpl() { 56 | //TODO: find a way to ensure that protected modules are not disabled 57 | protectedModules.each(n -> { 58 | Module m = ModuleFactory.get(n); 59 | if (m != null) ModuleFactory.enable(m); 60 | }); 61 | } 62 | 63 | @Override 64 | public void registerServerCommands(ServerCommandHandler handler) { 65 | handler.add("morecommands", "[on|off|reload|save] [moduleName]", 66 | "Manage the MoreCommands modules. Requires a server restart to apply the changes.", args -> { 67 | int action = 0; // 0: enabled, 1: disable, 2: reload 68 | if (args.length == 0) { 69 | logger.info("MoreCommands modules: [total: @, enabled: @, saveable: @]", ModuleFactory.size(), 70 | ModuleFactory.count(ModuleFactory::enabled), ModuleFactory.count(ModuleFactory::saveable)); 71 | Seq left = new Seq<>(ModuleFactory.size()), right = new Seq<>(ModuleFactory.size()); 72 | ModuleFactory.each(m -> left.add("&lk|&fr &fb&lb" + m.internalName() + "&fr (&fb&lb" + m.name() + "&fr):")); 73 | ModuleFactory.each(m -> right.add((protectedModules.contains(m.internalName()) ? "&lyprotected&fr" : 74 | ModuleFactory.enabled(m) ? "&lgenabled&fr" : "&lrdisabled&fr") + 75 | (ModuleFactory.saveable(m) ? ", &lbsaveable&fr" : ""))); 76 | Strings.sJust(Strings.lJust(left, Strings.maxLength(left) + 1), right, 0).each(logger::info); 77 | return; 78 | } 79 | else if (Strings.isTrue(args[0], false)) action = 0; 80 | else if (Strings.isFalse(args[0], false)) action = 1; 81 | else if (args[0].equals("reload")) action = 2; 82 | else if (args[0].equals("save")) { 83 | if (ModuleFactory.disposed()) logger.err("Cannot save: factory is disposed!"); 84 | else if (!ModuleFactory.initialized()) logger.err("Cannot save: factory is not initialized!"); 85 | else if (ModuleFactory.save()) { 86 | logger.info("MoreCommands modules saved."); 87 | Autosaver.save(); 88 | } 89 | // error already printed if error 90 | return; 91 | 92 | } else { 93 | logger.err("Invalid argument! Must be 'on', 'off', 'reload' or 'save'."); 94 | return; 95 | } 96 | 97 | if (args.length == 1) { 98 | if (action < 2) logger.err("Missing 'moduleName' argument."); 99 | else if (ModuleFactory.disposed()) logger.err("Cannot reload: factory is disposed!"); 100 | else if (!ModuleFactory.initialized()) logger.err("Cannot reload: factory is not initialized!"); 101 | else if (ModuleFactory.reload()) logger.info("MoreCommands modules reloaded."); 102 | // error already printed if error 103 | return; 104 | } 105 | 106 | Module module = ModuleFactory.get(args[1]); 107 | if (module == null) { 108 | logger.err("No module named '@' found.", args[1]); 109 | } else if (action == 0) { 110 | if (ModuleFactory.enabled(module)) logger.err("Module already enabled."); 111 | else { 112 | ModuleFactory.enable(module); 113 | logger.info("Module enabled."); 114 | logger.warn("A server restart is required to apply the change."); 115 | } 116 | } else if (action == 1) { 117 | if (isProtected(module)) logger.err("This module is protected, you cannot disable it."); 118 | else if (!ModuleFactory.enabled(module)) logger.err("Module already disabled."); 119 | else { 120 | ModuleFactory.disable(module); 121 | logger.info("Module disabled."); 122 | logger.warn("A server restart is required to apply the change."); 123 | } 124 | } 125 | else if (!ModuleFactory.saveable(module)) logger.warn("This module has no settings support."); 126 | else if (!ModuleFactory.enabled(module)) logger.warn("The module is disabled. Please enable it before reloading."); 127 | else if (ModuleFactory.disposed()) logger.err("Cannot reload module @: factory is disposed!", module.internalName()); 128 | else if (!ModuleFactory.initialized()) 129 | logger.err("Cannot reload module @: factory is not initialized!", module.internalName()); 130 | else { 131 | try { 132 | ModuleFactory.reload(module); 133 | logger.info("Module settings reloaded."); 134 | } catch (Exception e) { logger.err("Failed to reload @ settings" , e, module.name()); } 135 | } 136 | }); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/fr/zetamap/morecommands/util/Logger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of Anti-VPN-Service (AVS). The plugin securing your server against VPNs. 3 | * 4 | * MIT License 5 | * 6 | * Copyright (c) 2024-2025 Xpdustry 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in all 16 | * copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | * SOFTWARE. 25 | */ 26 | 27 | package fr.zetamap.morecommands.util; 28 | 29 | import arc.util.Log; 30 | import arc.util.Log.LogLevel; 31 | 32 | import mindustry.Vars; 33 | import mindustry.mod.Mods; 34 | import mindustry.mod.Mod; 35 | 36 | 37 | /** Log messages to console with topics */ 38 | public class Logger { 39 | protected static final Object[] empty = {}; 40 | /** Will use slf4j when slf4md plugin is present */ 41 | protected static boolean slf4mdPresent; 42 | protected static Object slf4jLogger; 43 | 44 | public static String mainTopic; 45 | public static String topicFormat = "&ly[@&ly]"; 46 | 47 | /** Sets the main topic using the mod. */ 48 | public static void init(Mod mod) { 49 | init(mod.getClass()); 50 | } 51 | 52 | /** Sets the main topic using the mod class. */ 53 | public static void init(Class mod) { 54 | Mods.LoadedMod load = Vars.mods.getMod(mod); 55 | if (load == null) throw new IllegalArgumentException("Mod is not loaded yet (or missing)!"); 56 | init(load.meta.displayName); 57 | } 58 | 59 | /** Sets the main topic */ 60 | public static void init(String mainTopic) { 61 | Logger.mainTopic = "&lc[" + mainTopic + "&lc]"; 62 | slf4mdPresent = Vars.mods.locateMod("slf4md") != null; 63 | if (slf4mdPresent) slf4jLogger = org.slf4j.LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); 64 | } 65 | 66 | 67 | /** If {@code true}, will not print the plugin and logger topics. */ 68 | public boolean noTopic = false; 69 | protected String topic, ftopic; 70 | 71 | /** Will only use the {@link #mainTopic}. */ 72 | public Logger() { 73 | this((String)null); 74 | } 75 | 76 | public Logger(boolean noTopic) { 77 | this.noTopic = noTopic; 78 | } 79 | 80 | public Logger(Class clazz) { 81 | this(Strings.insertSpaces(clazz.getSimpleName())); 82 | } 83 | 84 | public Logger(String topic) { 85 | if (topic != null && !(topic = topic.trim()).isEmpty()) 86 | topic(topic); 87 | } 88 | 89 | public String topic() { 90 | return topic; 91 | } 92 | 93 | public void topic(String topic) { 94 | this.topic = topic; 95 | this.ftopic = topic == null ? null : Strings.format(topicFormat, topic); 96 | } 97 | 98 | public synchronized void log(LogLevel level, String text, Throwable th, Object... args) { 99 | if (Log.level.ordinal() > level.ordinal()) return; 100 | 101 | String tag = noTopic ? "" : Log.format((mainTopic != null ? mainTopic + " " : "") + 102 | (ftopic != null ? ftopic + "&fr " : "&fr"), empty); 103 | 104 | if (text != null) { 105 | text = Log.format(text, args); 106 | if (th != null) text += ": " + Strings.getStackTrace(th); 107 | } else if (th != null) text = Strings.getStackTrace(th); 108 | 109 | if (slf4mdPresent && slf4jLogger != null) { 110 | synchronized (slf4jLogger) { 111 | org.slf4j.Logger l = (org.slf4j.Logger)slf4jLogger; 112 | arc.func.Cons printer; 113 | 114 | switch (level) { 115 | case debug: printer = l::debug; break; 116 | case info: printer = l::info; break; 117 | case warn: printer = l::warn; break; 118 | case err: printer = l::error; break; 119 | default: return; 120 | } 121 | 122 | if (text == null || text.isEmpty()) { 123 | printer.get(tag); 124 | return; 125 | } 126 | 127 | int i = 0, nl = text.indexOf('\n'); 128 | while (nl >= 0) { 129 | printer.get(tag + text.substring(i, nl)); 130 | i = nl + 1; 131 | nl = text.indexOf('\n', i); 132 | } 133 | printer.get(tag + (i == 0 ? text : text.substring(i))); 134 | } 135 | 136 | } else if (text == null || text.isEmpty()) { 137 | synchronized (Log.logger) { 138 | Log.logger.log(level, tag); 139 | } 140 | 141 | } else { 142 | synchronized (Log.logger) { 143 | int i = 0, nl = text.indexOf('\n'); 144 | while (nl >= 0) { 145 | Log.logger.log(level, tag + text.substring(i, nl)); 146 | i = nl + 1; 147 | nl = text.indexOf('\n', i); 148 | } 149 | Log.logger.log(level, tag + (i == 0 ? text : text.substring(i))); 150 | } 151 | } 152 | } 153 | public void log(LogLevel level, String text, Object... args) { log(level, text, null, args); } 154 | public void log(LogLevel level, String text) { log(level, text, empty); } 155 | 156 | public void debug(String text, Object... args) { log(LogLevel.debug, text, args); } 157 | public void debug(Object object) { debug(String.valueOf(object), empty); } 158 | 159 | public void info(String text, Object... args) { log(LogLevel.info, text, args); } 160 | public void info(Object object) { info(String.valueOf(object), empty); } 161 | 162 | public void warn(String text, Object... args) { log(LogLevel.warn, text, args); } 163 | public void warn(String text) { warn(text, empty); } 164 | 165 | public void err(String text, Throwable th, Object... args) { log(LogLevel.err, text, th, args); } 166 | public void err(String text, Object... args) { err(text, null, args); } 167 | public void err(String text, Throwable th) { err(text, th, empty); } 168 | public void err(String text) { err(text, null, empty); } 169 | public void err(Throwable th) { err(null, th, empty); } 170 | 171 | /** Log an empty "info" line. */ 172 | public void ln() { log(LogLevel.info, null, empty); } 173 | } 174 | --------------------------------------------------------------------------------