diff --git a/distributor-build-logic/src/main/kotlin/distributor.base-conventions.gradle.kts b/distributor-build-logic/src/main/kotlin/distributor.base-conventions.gradle.kts index c4ab2183..30f41445 100644 --- a/distributor-build-logic/src/main/kotlin/distributor.base-conventions.gradle.kts +++ b/distributor-build-logic/src/main/kotlin/distributor.base-conventions.gradle.kts @@ -18,6 +18,10 @@ repositories { name = "xpdustry-mindustry" mavenContent { releasesOnly() } } + maven("https://jitpack.io") { + name = "jitpack" + mavenContent { releasesOnly() } + } } dependencies { diff --git a/distributor-command-lamp/build.gradle.kts b/distributor-command-lamp/build.gradle.kts new file mode 100644 index 00000000..7dc5307e --- /dev/null +++ b/distributor-command-lamp/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id("distributor.base-conventions") + id("distributor.publish-conventions") +} + +dependencies { + api(libs.lamp.common) + compileOnlyApi(projects.distributorCommonApi) + compileOnlyApi(libs.bundles.mindustry) + compileOnlyApi(libs.immutables.annotations) + compileOnly(libs.jetbrains.annotations) + annotationProcessor(libs.immutables.processor) +} diff --git a/distributor-command-lamp/src/main/java/com/xpdustry/distributor/command/lamp/LampCommandFacade.java b/distributor-command-lamp/src/main/java/com/xpdustry/distributor/command/lamp/LampCommandFacade.java new file mode 100644 index 00000000..676da51b --- /dev/null +++ b/distributor-command-lamp/src/main/java/com/xpdustry/distributor/command/lamp/LampCommandFacade.java @@ -0,0 +1,130 @@ +/* + * Distributor, a feature-rich framework for Mindustry plugins. + * + * Copyright (C) 2024 Xpdustry + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.xpdustry.distributor.command.lamp; + +import arc.util.CommandHandler; +import com.xpdustry.distributor.command.CommandFacade; +import com.xpdustry.distributor.command.CommandHelp; +import com.xpdustry.distributor.command.CommandSender; +import com.xpdustry.distributor.command.DescriptionFacade; +import com.xpdustry.distributor.plugin.MindustryPlugin; +import java.util.Map; +import mindustry.gen.Player; +import org.jspecify.annotations.Nullable; +import revxrsal.commands.core.CommandPath; + +final class LampCommandFacade extends CommandHandler.Command implements CommandFacade { + + final MindustryCommandHandler handler; + private final String name; + private final DescriptionFacade description; + private final boolean alias; + private final boolean prefixed; + + LampCommandFacade( + final MindustryCommandHandler handler, + final String name, + final DescriptionFacade description, + final boolean alias, + final boolean prefixed) { + super( + prefixed ? handler.getPlugin().getMetadata().getName() + ":" + name : name, + "[args...]", + description.getText(), + new LampCommandRunner(name, handler)); + this.handler = handler; + this.name = name; + this.description = description; + this.alias = alias; + this.prefixed = prefixed; + } + + @Override + public String getRealName() { + return this.name; + } + + @Override + public String getName() { + return this.text; + } + + @Override + public DescriptionFacade getDescription() { + return this.description; + } + + @Override + public boolean isAlias() { + return this.alias; + } + + @Override + public boolean isPrefixed() { + return this.prefixed; + } + + @Override + public boolean isVisible(final CommandSender sender) { + final var actor = this.handler.wrap(sender); + final var root = CommandPath.get(this.name); + return this.handler.getCommands().entrySet().stream() + .filter(entry -> entry.getKey().isChildOf(root)) + .map(Map.Entry::getValue) + .anyMatch(command -> command.hasPermission(actor)); + } + + @Override + public CommandHelp getHelp(final CommandSender sender, final String query) { + return CommandHelp.Empty.getInstance(); + } + + @Override + public MindustryPlugin getPlugin() { + return this.handler.getPlugin(); + } + + @SuppressWarnings("ClassCanBeRecord") + private static class LampCommandRunner implements CommandHandler.CommandRunner { + + private final String name; + private final MindustryCommandHandler handler; + + private LampCommandRunner(final String name, final MindustryCommandHandler handler) { + this.name = name; + this.handler = handler; + } + + @Override + public void accept(final String[] args, final @Nullable Player player) { + final var actor = this.handler.wrap(player != null ? CommandSender.player(player) : CommandSender.server()); + + final var input = new StringBuilder(this.name); + for (final var arg : args) { + input.append(' ').append(arg); + } + + try { + this.handler.dispatch(actor, input.toString()); + } catch (final Throwable throwable) { + this.handler.getExceptionHandler().handleException(throwable, actor); + } + } + } +} diff --git a/distributor-command-lamp/src/main/java/com/xpdustry/distributor/command/lamp/LampElement.java b/distributor-command-lamp/src/main/java/com/xpdustry/distributor/command/lamp/LampElement.java new file mode 100644 index 00000000..555db856 --- /dev/null +++ b/distributor-command-lamp/src/main/java/com/xpdustry/distributor/command/lamp/LampElement.java @@ -0,0 +1,96 @@ +/* + * Distributor, a feature-rich framework for Mindustry plugins. + * + * Copyright (C) 2024 Xpdustry + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.xpdustry.distributor.command.lamp; + +import com.xpdustry.distributor.internal.DistributorDataClass; +import java.util.Objects; +import org.immutables.value.Value; +import revxrsal.commands.command.CommandCategory; +import revxrsal.commands.command.CommandParameter; +import revxrsal.commands.command.ExecutableCommand; + +public sealed interface LampElement { + + String getName(); + + String getDescription(); + + @DistributorDataClass + @Value.Immutable + sealed interface Category extends LampElement permits CategoryImpl { + + static Category of(final CommandCategory category) { + return CategoryImpl.of(category); + } + + CommandCategory getCategory(); + + @Override + default String getName() { + return getCategory().getName(); + } + + @Override + default String getDescription() { + return ""; + } + } + + @DistributorDataClass + @Value.Immutable + sealed interface Command extends LampElement permits CommandImpl { + + static Command of(final ExecutableCommand command) { + return CommandImpl.of(command); + } + + ExecutableCommand getCommand(); + + @Override + default String getName() { + return getCommand().getName(); + } + + @Override + default String getDescription() { + return Objects.requireNonNullElse(getCommand().getDescription(), ""); + } + } + + @DistributorDataClass + @Value.Immutable + sealed interface Parameter extends LampElement permits ParameterImpl { + + static Parameter of(final CommandParameter parameter) { + return ParameterImpl.of(parameter); + } + + CommandParameter getParameter(); + + @Override + default String getName() { + return getParameter().getName(); + } + + @Override + default String getDescription() { + return Objects.requireNonNullElse(getParameter().getDescription(), ""); + } + } +} diff --git a/distributor-command-lamp/src/main/java/com/xpdustry/distributor/command/lamp/MindustryCommandActor.java b/distributor-command-lamp/src/main/java/com/xpdustry/distributor/command/lamp/MindustryCommandActor.java new file mode 100644 index 00000000..83bd40d2 --- /dev/null +++ b/distributor-command-lamp/src/main/java/com/xpdustry/distributor/command/lamp/MindustryCommandActor.java @@ -0,0 +1,27 @@ +/* + * Distributor, a feature-rich framework for Mindustry plugins. + * + * Copyright (C) 2024 Xpdustry + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.xpdustry.distributor.command.lamp; + +import com.xpdustry.distributor.command.CommandSender; +import revxrsal.commands.command.CommandActor; + +public interface MindustryCommandActor extends CommandActor { + + CommandSender getCommandSender(); +} diff --git a/distributor-command-lamp/src/main/java/com/xpdustry/distributor/command/lamp/MindustryCommandActorImpl.java b/distributor-command-lamp/src/main/java/com/xpdustry/distributor/command/lamp/MindustryCommandActorImpl.java new file mode 100644 index 00000000..3d62c38a --- /dev/null +++ b/distributor-command-lamp/src/main/java/com/xpdustry/distributor/command/lamp/MindustryCommandActorImpl.java @@ -0,0 +1,75 @@ +/* + * Distributor, a feature-rich framework for Mindustry plugins. + * + * Copyright (C) 2024 Xpdustry + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.xpdustry.distributor.command.lamp; + +import com.xpdustry.distributor.command.CommandSender; +import com.xpdustry.distributor.player.MUUID; +import java.util.Locale; +import java.util.UUID; +import revxrsal.commands.CommandHandler; + +final class MindustryCommandActorImpl implements MindustryCommandActor { + + private static final UUID CONSOLE_UUID = new UUID(0, 0); + + private final MindustryCommandHandler handler; + private final CommandSender sender; + + MindustryCommandActorImpl(final MindustryCommandHandler handler, final CommandSender sender) { + this.handler = handler; + this.sender = sender; + } + + @Override + public CommandSender getCommandSender() { + return this.sender; + } + + @Override + public String getName() { + return this.sender.getName(); + } + + @Override + public UUID getUniqueId() { + return this.sender.isServer() + ? CONSOLE_UUID + : MUUID.from(this.sender.getPlayer()).toRealUUID(); + } + + @Override + public void reply(final String message) { + this.sender.sendMessage(message); + } + + @Override + public void error(final String message) { + this.sender.sendWarning(message); + } + + @Override + public CommandHandler getCommandHandler() { + return this.handler; + } + + @Override + public Locale getLocale() { + return this.sender.getLocale(); + } +} diff --git a/distributor-command-lamp/src/main/java/com/xpdustry/distributor/command/lamp/MindustryCommandHandler.java b/distributor-command-lamp/src/main/java/com/xpdustry/distributor/command/lamp/MindustryCommandHandler.java new file mode 100644 index 00000000..a9a9f92d --- /dev/null +++ b/distributor-command-lamp/src/main/java/com/xpdustry/distributor/command/lamp/MindustryCommandHandler.java @@ -0,0 +1,37 @@ +/* + * Distributor, a feature-rich framework for Mindustry plugins. + * + * Copyright (C) 2024 Xpdustry + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.xpdustry.distributor.command.lamp; + +import com.xpdustry.distributor.command.CommandSender; +import com.xpdustry.distributor.command.DescriptionMapper; +import com.xpdustry.distributor.plugin.MindustryPlugin; +import com.xpdustry.distributor.plugin.PluginAware; +import revxrsal.commands.CommandHandler; + +public interface MindustryCommandHandler extends CommandHandler, PluginAware { + + static MindustryCommandHandler create( + final MindustryPlugin plugin, final DescriptionMapper descriptionMapper) { + return new MindustryCommandHandlerImpl(plugin, descriptionMapper); + } + + MindustryCommandActor wrap(final CommandSender sender); + + void initialize(final arc.util.CommandHandler handler); +} diff --git a/distributor-command-lamp/src/main/java/com/xpdustry/distributor/command/lamp/MindustryCommandHandlerImpl.java b/distributor-command-lamp/src/main/java/com/xpdustry/distributor/command/lamp/MindustryCommandHandlerImpl.java new file mode 100644 index 00000000..b32b9e24 --- /dev/null +++ b/distributor-command-lamp/src/main/java/com/xpdustry/distributor/command/lamp/MindustryCommandHandlerImpl.java @@ -0,0 +1,133 @@ +/* + * Distributor, a feature-rich framework for Mindustry plugins. + * + * Copyright (C) 2024 Xpdustry + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.xpdustry.distributor.command.lamp; + +import arc.struct.ObjectMap; +import arc.util.CommandHandler; +import com.xpdustry.distributor.collection.ArcCollections; +import com.xpdustry.distributor.command.CommandFacade; +import com.xpdustry.distributor.command.CommandSender; +import com.xpdustry.distributor.command.DescriptionFacade; +import com.xpdustry.distributor.command.DescriptionMapper; +import com.xpdustry.distributor.plugin.MindustryPlugin; +import java.lang.reflect.Field; +import java.util.Objects; +import org.jspecify.annotations.Nullable; +import revxrsal.commands.command.CommandCategory; +import revxrsal.commands.command.ExecutableCommand; +import revxrsal.commands.core.BaseCommandHandler; +import revxrsal.commands.core.CommandPath; + +@SuppressWarnings("UnstableApiUsage") +final class MindustryCommandHandlerImpl extends BaseCommandHandler implements MindustryCommandHandler { + + private static final Field COMMAND_MAP_ACCESSOR; + + static { + try { + COMMAND_MAP_ACCESSOR = CommandHandler.class.getDeclaredField("commands"); + COMMAND_MAP_ACCESSOR.setAccessible(true); + } catch (final Exception e) { + throw new RuntimeException("Unable to access CommandHandler#commands.", e); + } + } + + private final MindustryPlugin plugin; + final DescriptionMapper descriptionMapper; + private @Nullable CommandHandler handler = null; + + MindustryCommandHandlerImpl(final MindustryPlugin plugin, final DescriptionMapper descriptionMapper) { + this.plugin = plugin; + this.descriptionMapper = descriptionMapper; + } + + @Override + public MindustryCommandHandler register(final Object... commands) { + super.register(commands); + for (final ExecutableCommand command : this.executables.values()) { + if (command.getParent() != null) continue; + createArcCommand( + command.getName(), + descriptionMapper.map(LampElement.Command.of(command)), + this.executables.values().stream() + .anyMatch((ExecutableCommand cmd) -> cmd.getId() == command.getId())); + } + for (final CommandCategory category : this.categories.values()) { + if (category.getParent() != null) continue; + createArcCommand(category.getName(), this.descriptionMapper.map(LampElement.Category.of(category)), false); + } + return this; + } + + @Override + public boolean unregister(final CommandPath path) { + if (path.isRoot()) { + for (final var command : + ArcCollections.immutableList(getArcHandler().getCommandList())) { + if (CommandFacade.from(command) instanceof LampCommandFacade facade + && facade.handler == this + && facade.getRealName().equalsIgnoreCase(path.getName())) { + getArcHandler().removeCommand(command.text); + } + } + } + return super.unregister(path); + } + + @Override + public MindustryPlugin getPlugin() { + return this.plugin; + } + + @Override + public MindustryCommandActor wrap(final CommandSender sender) { + return new MindustryCommandActorImpl(this, sender); + } + + @Override + public void initialize(final CommandHandler handler) { + if (this.handler != null) { + throw new IllegalStateException("This handler is already initialized."); + } + this.handler = handler; + } + + @SuppressWarnings("unchecked") + private ObjectMap getArcHandlerInternalMap() { + try { + return (ObjectMap) COMMAND_MAP_ACCESSOR.get(getArcHandler()); + } catch (final IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + private void createArcCommand(final String name, final DescriptionFacade description, final boolean alias) { + addCommand(new LampCommandFacade( + this, name, description, alias, getArcHandlerInternalMap().containsKey(name))); + } + + private arc.util.CommandHandler getArcHandler() { + return Objects.requireNonNull(this.handler, "This lamp command handler is not initialized yet."); + } + + private void addCommand(final LampCommandFacade command) { + getArcHandlerInternalMap().put(command.text, command); + getArcHandler().getCommandList().add(command); + } +} diff --git a/distributor-command-lamp/src/main/java/com/xpdustry/distributor/command/lamp/package-info.java b/distributor-command-lamp/src/main/java/com/xpdustry/distributor/command/lamp/package-info.java new file mode 100644 index 00000000..242a068e --- /dev/null +++ b/distributor-command-lamp/src/main/java/com/xpdustry/distributor/command/lamp/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package com.xpdustry.distributor.command.lamp; + +import org.jspecify.annotations.NullMarked; diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a0f60b65..8d69b635 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,6 +12,7 @@ slf4j = "2.0.12" # command cloud = "2.0.0-beta.4" +lamp = "3.2.1" # utilities immutables = "2.10.1" @@ -26,6 +27,7 @@ jspecify = "0.3.0" errorprone-gradle = "3.1.0" errorprone-core = "2.26.1" nullaway = "0.10.24" +jetbrains-annotations = "24.1.0" # linting palantir = "2.41.0" @@ -51,6 +53,7 @@ slf4j-from-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" } # command cloud-core = { module = "org.incendo:cloud-core", version.ref = "cloud" } +lamp-common = { module = "com.github.Revxrsal.Lamp:common", version.ref = "lamp" } # utilities immutables-processor = { module = "org.immutables:value-processor", version.ref = "immutables" } @@ -68,6 +71,7 @@ jspecify = { module = "org.jspecify:jspecify", version.ref = "jspecify" } errorprone-gradle = { module = "net.ltgt.gradle:gradle-errorprone-plugin", version.ref = "errorprone-gradle" } errorprone-core = { module = "com.google.errorprone:error_prone_core", version.ref = "errorprone-core" } nullaway = { module = "com.uber.nullaway:nullaway", version.ref = "nullaway" } +jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains-annotations" } # gradle toxopid = { module = "fr.xpdustry:toxopid", version.ref = "toxopid" } diff --git a/settings.gradle.kts b/settings.gradle.kts index e74af619..6aff44bf 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,4 +7,5 @@ include(":distributor-logging-simple") include(":distributor-common-api") include(":distributor-common") include(":distributor-command-cloud") +include(":distributor-command-lamp") include(":distributor-permission-rank")