diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4ced302..206c654 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,32 +11,25 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Set up JDK 21 - uses: actions/setup-java@v4 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - name: Set up JDK 25 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: 'corretto' - java-version: '21' - - name: Cache Gradle dependencies - uses: actions/cache@v4 + java-version: '25' + - name: Set up Gradle + uses: gradle/actions/setup-gradle@5e2ebd065dc2488b7a6ad670704656cbbe1e8f60 # v6.1.1 with: - path: ~/.gradle/caches - key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - gradle-${{ runner.os }}- - - name: Cache Gradle wrapper - uses: actions/cache@v4 - with: - path: ~/.gradle/wrapper - key: gradle-wrapper-${{ runner.os }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }} - restore-keys: | - gradle-wrapper-${{ runner.os }}- + validate-wrappers: true + cache-read-only: ${{ github.ref != format('refs/heads/{0}', github.event.repository.default_branch) }} - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build with Gradle run: ./gradlew build --full-stacktrace + - name: Create Release Artifact + run: ./gradlew createReleaseJar - name: Upload Artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: Artifacts - path: build/libs/*-release.jar + path: build/libs/*-release.jar \ No newline at end of file diff --git a/.gitignore b/.gitignore index 47d9f66..69ec47e 100644 --- a/.gitignore +++ b/.gitignore @@ -73,6 +73,8 @@ run/ # LabyGradle | Addon Plugin build-data.txt .assetsroot +resources_index.json +access_widener_index.json # Don't ignore libraries !libs/*.jar \ No newline at end of file diff --git a/api/src/main/java/com/rappytv/autocommand/api/AutoCommandMeta.java b/api/src/main/java/com/rappytv/autocommand/api/AutoCommandMeta.java new file mode 100644 index 0000000..868ec96 --- /dev/null +++ b/api/src/main/java/com/rappytv/autocommand/api/AutoCommandMeta.java @@ -0,0 +1,227 @@ +package com.rappytv.autocommand.api; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import net.labymod.api.Laby; +import net.labymod.api.event.client.world.WorldEnterEvent; +import net.labymod.api.util.concurrent.task.Task; +import org.jetbrains.annotations.NotNull; + +public class AutoCommandMeta { + + private static final Map tasks = new HashMap<>(); + + private UUID uuid; + private boolean enabled; + private String name; + private String command; + private float delay; + private List contexts; + + private AutoCommandMeta(UUID uuid, boolean enabled, String name, String command, float delay, + List contexts) { + this.uuid = uuid; + this.enabled = enabled; + this.name = name; + this.command = command; + this.delay = delay; + this.contexts = contexts; + } + + /** + * Get the auto command uuid + * + * @return The auto command uuid + */ + public UUID getUuid() { + return this.uuid; + } + + public void setUuid(UUID uuid) { + this.uuid = uuid; + } + + /** + * Check if the auto command is enabled + * + * @return If the auto command is enabled + */ + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + /** + * Get the auto command name + * + * @return The auto command name + */ + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCommand() { + return this.command; + } + + public void setCommand(String command) { + this.command = command; + } + + /** + * Get the delay after which the command gets executed + * + * @return The delay after which the command gets executed + */ + public float getDelay() { + return this.delay; + } + + public void setDelay(float delay) { + this.delay = delay; + } + + /** + * Get the command contexts + * + * @return The command contexts + */ + public List getContexts() { + return this.contexts; + } + + public void setContexts(List contexts) { + this.contexts = contexts; + } + + /** + * Get the {@link Task} which runs the auto command + * + * @return The {@link Task} which runs the auto command + */ + @NotNull + public Task getTask() { + return tasks.computeIfAbsent(this.uuid, (uuid) -> this.buildTask()); + } + + private Task buildTask() { + return Task.builder(() -> Laby.references().chatExecutor().chat(this.command)) + .delay((long) this.delay, TimeUnit.SECONDS) + .build(); + } + + public boolean isValid() { + return !this.name.isBlank() && !this.command.isBlank(); + } + + /** + * Get a {@link AutoCommandMeta.Builder} instance + * + * @return A builder instance + */ + public static Builder builder() { + return new Builder(); + } + + public record Context(String name, Type type) { + + /** + * Get the name of the server or singleplayer world + * + * @return The name of the server or singleplayer world + */ + @Override + public String name() { + return this.name; + } + + /** + * Get the context type + * + * @return The context type + */ + @Override + public Type type() { + return this.type; + } + + public enum Type { + SINGLEPLAYER(WorldEnterEvent.Type.SINGLEPLAYER), + MULTIPLAYER(WorldEnterEvent.Type.MULTIPLAYER); + + private final WorldEnterEvent.Type worldEnterType; + + Type(WorldEnterEvent.Type type) { + this.worldEnterType = type; + } + + public WorldEnterEvent.Type getWorldEnterType() { + return this.worldEnterType; + } + } + } + + public static class Builder { + + private UUID uuid = UUID.randomUUID(); + private boolean enabled = true; + private String name = ""; + private String command = ""; + private float delay = 0; + private List contexts = new ArrayList<>(); + + private Builder() { + } + + public Builder uuid(@NotNull UUID uuid) { + this.uuid = uuid; + return this; + } + + public Builder enabled(boolean enabled) { + this.enabled = enabled; + return this; + } + + public Builder name(@NotNull String name) { + this.name = name; + return this; + } + + public Builder command(@NotNull String command) { + this.command = command; + return this; + } + + public Builder delay(float delay) { + this.delay = delay; + return this; + } + + public Builder contexts(@NotNull List contexts) { + this.contexts = contexts; + return this; + } + + public AutoCommandMeta build() { + Objects.requireNonNull(this.uuid); + Objects.requireNonNull(this.name); + Objects.requireNonNull(this.command); + Objects.requireNonNull(this.contexts); + return new AutoCommandMeta(this.uuid, this.enabled, this.name, this.command, this.delay, + this.contexts); + } + } +} diff --git a/api/src/main/java/com/rappytv/autocommand/api/AutoCommandTextures.java b/api/src/main/java/com/rappytv/autocommand/api/AutoCommandTextures.java new file mode 100644 index 0000000..9570875 --- /dev/null +++ b/api/src/main/java/com/rappytv/autocommand/api/AutoCommandTextures.java @@ -0,0 +1,10 @@ +package com.rappytv.autocommand.api; + +import net.labymod.api.client.gui.icon.Icon; +import net.labymod.api.client.resources.ResourceLocation; + +public class AutoCommandTextures { + + public static final Icon COMMAND_BLOCK = Icon.texture( + ResourceLocation.create("autocommand", "textures/command_block.png")); +} diff --git a/build.gradle.kts b/build.gradle.kts index da5e473..59282ca 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,27 +9,26 @@ group = "org.example" version = providers.environmentVariable("VERSION").getOrElse("1.0.0") labyMod { - defaultPackageName = "org.example" //change this to your main package name (used by all modules) + defaultPackageName = "com.rappytv.autocommand" + + addonInfo { + namespace = "autocommand" + displayName = "AutoCommand" + author = "RappyTV" + description = "Lets you automatically send commands when joining on servers" + minecraftVersion = "*" + version = rootProject.version.toString() + } minecraft { registerVersion(versions.toTypedArray()) { runs { getByName("client") { - // When the property is set to true, you can log in with a Minecraft account - // devLogin = true + devLogin = true } } } } - - addonInfo { - namespace = "example" - displayName = "ExampleAddon" - author = "Example Author" - description = "Example Description" - minecraftVersion = "*" - version = rootProject.version.toString() - } } subprojects { @@ -38,4 +37,9 @@ subprojects { group = rootProject.group version = rootProject.version + + extensions.findByType(JavaPluginExtension::class.java)?.apply { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + } } \ No newline at end of file diff --git a/core/src/main/java/com/rappytv/autocommand/core/AutoCommandAddon.java b/core/src/main/java/com/rappytv/autocommand/core/AutoCommandAddon.java new file mode 100644 index 0000000..d0ee1d3 --- /dev/null +++ b/core/src/main/java/com/rappytv/autocommand/core/AutoCommandAddon.java @@ -0,0 +1,49 @@ +package com.rappytv.autocommand.core; + +import com.rappytv.autocommand.core.config.AutoCommandAddonConfig; +import com.rappytv.autocommand.core.config.CommandConfig; +import com.rappytv.autocommand.core.listener.WorldEnterListener; +import net.labymod.api.Laby; +import net.labymod.api.addon.LabyAddon; +import net.labymod.api.models.addon.annotation.AddonMain; +import net.labymod.api.revision.SimpleRevision; +import net.labymod.api.util.version.SemanticVersion; + +@AddonMain +public class AutoCommandAddon extends LabyAddon { + + private static AutoCommandAddon INSTANCE; + private CommandConfig commandConfig; + + @Override + protected void preConfigurationLoad() { + Laby.references().revisionRegistry().register(new SimpleRevision( + "autocommand", + new SemanticVersion(1, 0, 0), + "2026-07-23" + )); + } + + @Override + protected void enable() { + INSTANCE = this; + + this.registerSettingCategory(); + this.registerListener(new WorldEnterListener()); + + this.commandConfig = this.addCustomConfiguration(CommandConfig.class); + } + + @Override + protected Class configurationClass() { + return AutoCommandAddonConfig.class; + } + + public static CommandConfig getCommandConfig() { + return INSTANCE.commandConfig; + } + + public static void saveCommandConfig() { + INSTANCE.saveCustomConfiguration(CommandConfig.class); + } +} diff --git a/core/src/main/java/com/rappytv/autocommand/core/config/AutoCommandAddonConfig.java b/core/src/main/java/com/rappytv/autocommand/core/config/AutoCommandAddonConfig.java new file mode 100644 index 0000000..6de3381 --- /dev/null +++ b/core/src/main/java/com/rappytv/autocommand/core/config/AutoCommandAddonConfig.java @@ -0,0 +1,35 @@ +package com.rappytv.autocommand.core.config; + +import com.rappytv.autocommand.core.AutoCommandAddon; +import com.rappytv.autocommand.core.ui.activity.CommandManagerActivity; +import net.labymod.api.addon.AddonConfig; +import net.labymod.api.client.gui.screen.activity.Activity; +import net.labymod.api.client.gui.screen.widget.widgets.activity.settings.ActivitySettingWidget.ActivitySetting; +import net.labymod.api.client.gui.screen.widget.widgets.input.SwitchWidget.SwitchSetting; +import net.labymod.api.configuration.loader.annotation.IntroducedIn; +import net.labymod.api.configuration.loader.annotation.SpriteSlot; +import net.labymod.api.configuration.loader.annotation.SpriteTexture; +import net.labymod.api.configuration.loader.property.ConfigProperty; +import net.labymod.api.util.MethodOrder; + +@SpriteTexture("settings") +public class AutoCommandAddonConfig extends AddonConfig { + + @IntroducedIn(namespace = "autocommand", value = "1.0.0") + @SpriteSlot + @SwitchSetting + private final ConfigProperty enabled = new ConfigProperty<>(true); + + @IntroducedIn(namespace = "autocommand", value = "1.0.0") + @SpriteSlot(x = 1, size = 32) + @MethodOrder(after = "enabled") + @ActivitySetting + public Activity commandManager() { + return new CommandManagerActivity(AutoCommandAddon.getCommandConfig()); + } + + @Override + public ConfigProperty enabled() { + return this.enabled; + } +} diff --git a/core/src/main/java/com/rappytv/autocommand/core/config/CommandConfig.java b/core/src/main/java/com/rappytv/autocommand/core/config/CommandConfig.java new file mode 100644 index 0000000..c2e6cb5 --- /dev/null +++ b/core/src/main/java/com/rappytv/autocommand/core/config/CommandConfig.java @@ -0,0 +1,49 @@ +package com.rappytv.autocommand.core.config; + +import com.rappytv.autocommand.api.AutoCommandMeta; +import com.rappytv.autocommand.core.AutoCommandAddon; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.function.Predicate; +import net.labymod.api.configuration.loader.Config; +import net.labymod.api.configuration.loader.annotation.ConfigName; +import net.labymod.api.configuration.loader.annotation.Exclude; +import org.jetbrains.annotations.NotNull; + +@ConfigName("autocommands") +public class CommandConfig extends Config { + + @Exclude + private final Map autoCommands = new HashMap<>(); + + public void setAutoCommand(@NotNull AutoCommandMeta meta) { + Objects.requireNonNull(meta); + this.autoCommands.put(meta.getUuid(), meta); + AutoCommandAddon.saveCommandConfig(); + } + + public void removeAutoCommand(@NotNull UUID uuid) { + this.autoCommands.remove(uuid); + AutoCommandAddon.saveCommandConfig(); + } + + public Map getAutoCommands() { + return Collections.unmodifiableMap(this.autoCommands); + } + + public List getAutoCommands(Predicate filter) { + List autoCommands = new ArrayList<>(); + for (AutoCommandMeta autoCommandMeta : this.autoCommands.values()) { + if (filter.test(autoCommandMeta)) { + autoCommands.add(autoCommandMeta); + } + } + return Collections.unmodifiableList(autoCommands); + } + +} diff --git a/core/src/main/java/com/rappytv/autocommand/core/listener/WorldEnterListener.java b/core/src/main/java/com/rappytv/autocommand/core/listener/WorldEnterListener.java new file mode 100644 index 0000000..0984dc2 --- /dev/null +++ b/core/src/main/java/com/rappytv/autocommand/core/listener/WorldEnterListener.java @@ -0,0 +1,61 @@ +package com.rappytv.autocommand.core.listener; + +import com.rappytv.autocommand.api.AutoCommandMeta; +import com.rappytv.autocommand.api.AutoCommandMeta.Context; +import com.rappytv.autocommand.core.AutoCommandAddon; +import java.util.List; +import net.labymod.api.Laby; +import net.labymod.api.client.network.server.ServerData; +import net.labymod.api.event.Subscribe; +import net.labymod.api.event.client.world.WorldEnterEvent; +import net.labymod.api.server.LocalWorld; +import org.jetbrains.annotations.Nullable; + +public class WorldEnterListener { + + @Subscribe + public void onWorldEnter(WorldEnterEvent event) { + List commands = AutoCommandAddon.getCommandConfig().getAutoCommands((meta) -> { + if(!meta.isEnabled()) { + return false; + } + List contexts = meta.getContexts(); + + if(contexts.isEmpty()) { + return true; + } + String name = this.getContextName(event.type()); + + for (Context context : contexts) { + boolean isCorrectContext = context.type().getWorldEnterType() == event.type(); + + if (isCorrectContext && context.name().equals(name)) { + return true; + } + } + + return false; + }); + + for (AutoCommandMeta command : commands) { + command.getTask().execute(); + } + } + + @Nullable + private String getContextName(WorldEnterEvent.Type type) { + return switch (type) { + case SINGLEPLAYER -> { + LocalWorld world = Laby.references().integratedServer().getLocalWorld(); + yield world != null ? world.folderName() : null; + } + case MULTIPLAYER -> { + ServerData server = Laby.labyAPI().serverController().getCurrentServerData(); + if (server == null || server.address().getHost().isEmpty()) { + yield null; + } + yield server.address().getHost(); + } + }; + } +} diff --git a/core/src/main/java/com/rappytv/autocommand/core/ui/activity/CommandContextManagerActivity.java b/core/src/main/java/com/rappytv/autocommand/core/ui/activity/CommandContextManagerActivity.java new file mode 100644 index 0000000..1da0f2b --- /dev/null +++ b/core/src/main/java/com/rappytv/autocommand/core/ui/activity/CommandContextManagerActivity.java @@ -0,0 +1,155 @@ +package com.rappytv.autocommand.core.ui.activity; + +import com.rappytv.autocommand.api.AutoCommandMeta; +import com.rappytv.autocommand.api.AutoCommandMeta.Context; +import com.rappytv.autocommand.api.AutoCommandMeta.Context.Type; +import com.rappytv.autocommand.core.AutoCommandAddon; +import com.rappytv.autocommand.core.ui.widget.AutoCommandContextWidget; +import com.rappytv.autocommand.core.ui.widget.AutoCommandWidget; +import java.util.ArrayList; +import java.util.List; +import net.labymod.api.Laby; +import net.labymod.api.Textures.SpriteCommon; +import net.labymod.api.client.component.Component; +import net.labymod.api.client.gui.screen.Parent; +import net.labymod.api.client.gui.screen.activity.Activity; +import net.labymod.api.client.gui.screen.activity.AutoActivity; +import net.labymod.api.client.gui.screen.activity.Link; +import net.labymod.api.client.gui.screen.activity.Links; +import net.labymod.api.client.gui.screen.widget.widgets.ComponentWidget; +import net.labymod.api.client.gui.screen.widget.widgets.input.ButtonWidget; +import net.labymod.api.client.gui.screen.widget.widgets.layout.FlexibleContentWidget; +import net.labymod.api.client.gui.screen.widget.widgets.layout.ScrollWidget; +import net.labymod.api.client.gui.screen.widget.widgets.layout.list.HorizontalListWidget; +import net.labymod.api.client.gui.screen.widget.widgets.layout.list.VerticalListWidget; +import net.labymod.api.client.network.server.ServerData; +import net.labymod.api.server.LocalWorld; +import org.jetbrains.annotations.Nullable; + +@AutoActivity +@Links({@Link("context-manager.lss"), @Link("command-meta.lss")}) +public class CommandContextManagerActivity extends Activity { + + private static final String I18N_PREFIX = "autocommand.activity.context."; + + private final AutoCommandMeta meta; + private final List contextWidgets = new ArrayList<>(); + private final VerticalListWidget contextList = new VerticalListWidget<>(); + + private final Context currentContext; + + public CommandContextManagerActivity(AutoCommandMeta meta) { + this.meta = meta; + this.loadContexts(); + this.contextList.addId("context-list"); + this.currentContext = this.buildCurrentContext(); + } + + @Override + public void initialize(Parent parent) { + super.initialize(parent); + FlexibleContentWidget content = new FlexibleContentWidget(); + content.addId("context-manager"); + + for (AutoCommandContextWidget widget : this.contextWidgets) { + this.contextList.addChild(widget); + } + + AutoCommandWidget commandWidget = new AutoCommandWidget(this.meta); + commandWidget.addId("selected-command"); + + boolean validContext = this.currentContext != null; + boolean duplicateContext = this.hasCurrentContext(); + + ButtonWidget addButton = ButtonWidget.i18n(I18N_PREFIX + "add", this::addCurrentContext); + addButton.addId("add-button"); + addButton.setEnabled(validContext && !duplicateContext); + + if(!validContext) { + addButton.setHoverComponent(Component.translatable(I18N_PREFIX + "invalid")); + } else if(duplicateContext) { + addButton.setHoverComponent(Component.translatable(I18N_PREFIX + "duplicate")); + } + + content.addContent(this.buildBackContainer()); + content.addContent(commandWidget); + content.addContent(addButton); + content.addFlexibleContent(new ScrollWidget(this.contextList)); + + this.document.addChild(content); + } + + @Override + public void reload() { + this.loadContexts(); + super.reload(); + } + + private void loadContexts() { + this.contextWidgets.clear(); + for (AutoCommandMeta.Context context : this.meta.getContexts()) { + this.contextWidgets.add(new AutoCommandContextWidget(context, () -> { + this.meta.getContexts().removeIf(context::equals); + AutoCommandAddon.getCommandConfig().setAutoCommand(this.meta); + this.reload(); + })); + } + } + + private HorizontalListWidget buildBackContainer() { + HorizontalListWidget backContainer = new HorizontalListWidget(); + backContainer.addId("back-container"); + + ButtonWidget backButton = ButtonWidget.icon(SpriteCommon.BACK_BUTTON, + this::displayPreviousScreen // TODO: this doesn't work ingame + ); + backButton.addId("back-button"); + + ComponentWidget backComponent = ComponentWidget.i18n(I18N_PREFIX + "back"); + backComponent.addId("back-component"); + + backContainer.addEntry(backButton); + backContainer.addEntry(backComponent); + + return backContainer; + } + + @Nullable + private AutoCommandMeta.Context buildCurrentContext() { + if(!Laby.labyAPI().minecraft().isIngame()) { + return null; + } + + String name; + Type type; + + ServerData server = Laby.labyAPI().serverController().getCurrentServerData(); + if (server != null && !server.address().getHost().isEmpty()) { + name = server.address().getHost(); + type = Type.MULTIPLAYER; + } else { + LocalWorld world = Laby.references().integratedServer().getLocalWorld(); + if(world == null) { + return null; + } + + name = world.folderName(); + type = Type.SINGLEPLAYER; + } + + return new Context(name, type); + } + + private void addCurrentContext() { + if(this.currentContext == null || this.hasCurrentContext()) { + return; + } + this.meta.getContexts().add(this.currentContext); + AutoCommandAddon.getCommandConfig().setAutoCommand(this.meta); + this.reload(); + } + + private boolean hasCurrentContext() { + return this.currentContext != null && this.meta.getContexts().contains(this.currentContext); + } +} diff --git a/core/src/main/java/com/rappytv/autocommand/core/ui/activity/CommandManagerActivity.java b/core/src/main/java/com/rappytv/autocommand/core/ui/activity/CommandManagerActivity.java new file mode 100644 index 0000000..58c1032 --- /dev/null +++ b/core/src/main/java/com/rappytv/autocommand/core/ui/activity/CommandManagerActivity.java @@ -0,0 +1,186 @@ +package com.rappytv.autocommand.core.ui.activity; + +import com.rappytv.autocommand.api.AutoCommandMeta; +import com.rappytv.autocommand.core.config.CommandConfig; +import com.rappytv.autocommand.core.ui.popup.EditAutoCommandPopup; +import com.rappytv.autocommand.core.ui.widget.AutoCommandWidget; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.UUID; +import java.util.function.Consumer; +import net.labymod.api.client.component.Component; +import net.labymod.api.client.component.format.NamedTextColor; +import net.labymod.api.client.gui.screen.Parent; +import net.labymod.api.client.gui.screen.activity.Activity; +import net.labymod.api.client.gui.screen.activity.AutoActivity; +import net.labymod.api.client.gui.screen.activity.Link; +import net.labymod.api.client.gui.screen.activity.Links; +import net.labymod.api.client.gui.screen.key.Key; +import net.labymod.api.client.gui.screen.widget.context.ContextMenu; +import net.labymod.api.client.gui.screen.widget.context.ContextMenuEntry; +import net.labymod.api.client.gui.screen.widget.widgets.ComponentWidget; +import net.labymod.api.client.gui.screen.widget.widgets.DivWidget; +import net.labymod.api.client.gui.screen.widget.widgets.input.ButtonWidget; +import net.labymod.api.client.gui.screen.widget.widgets.layout.FlexibleContentWidget; +import net.labymod.api.client.gui.screen.widget.widgets.layout.ScrollWidget; +import net.labymod.api.client.gui.screen.widget.widgets.layout.list.HorizontalListWidget; +import net.labymod.api.client.gui.screen.widget.widgets.layout.list.VerticalListWidget; +import net.labymod.api.client.gui.screen.widget.widgets.popup.AdvancedPopup; +import net.labymod.api.client.gui.screen.widget.widgets.popup.SimpleAdvancedPopup; +import net.labymod.api.client.gui.screen.widget.widgets.popup.SimpleAdvancedPopup.SimplePopupButton; + +@AutoActivity +@Links({@Link("command-manager.lss"), @Link("command-meta.lss")}) +public class CommandManagerActivity extends Activity { + + private static final String I18N_PREFIX = "autocommand.activity.command."; + + private final CommandConfig config; + private final Map commandWidgets = new HashMap<>(); + private final VerticalListWidget commandList = new VerticalListWidget<>(); + + private AutoCommandMeta selectedCommand; + + private ButtonWidget editButton; + private ButtonWidget removeButton; + + public CommandManagerActivity(CommandConfig config) { + this.config = config; + this.loadCommands(); + this.commandList.addId("command-list"); + this.commandList.setSelectCallback(selected -> { + this.selectedCommand = selected.getMeta(); + this.updateButtonVisibility(true); + }); + this.commandList.setDoubleClickCallback(ignored -> this.performAction(Action.EDIT)); + } + + @Override + public void initialize(Parent parent) { + super.initialize(parent); + + AutoCommandWidget selectedEntry = this.commandList.listSession().getSelectedEntry(); + if (selectedEntry != null) { + this.selectedCommand = selectedEntry.getMeta(); + } else { + this.selectedCommand = null; + } + + FlexibleContentWidget container = new FlexibleContentWidget(); + container.addId("command-container"); + for (AutoCommandWidget commandWidget : this.commandWidgets.values()) { + this.commandList.addChild(commandWidget); + } + + ComponentWidget hint = ComponentWidget.i18n(I18N_PREFIX + "hint"); + hint.addId("hint-text"); + + HorizontalListWidget buttons = new HorizontalListWidget(); + buttons.addId("buttons"); + + ButtonWidget addButton = ButtonWidget.i18n( + "labymod.ui.button.add", + () -> this.performAction(Action.ADD) + ); + + this.editButton = ButtonWidget.i18n( + "labymod.ui.button.edit", + () -> this.performAction(Action.EDIT) + ); + + this.removeButton = ButtonWidget.i18n( + "labymod.ui.button.remove", + () -> this.performAction(Action.REMOVE) + ); + this.updateButtonVisibility(selectedEntry != null); + + buttons.addEntry(addButton); + buttons.addEntry(this.editButton); + buttons.addEntry(this.removeButton); + + DivWidget content = new DivWidget().addId("content"); + if (this.commandWidgets.isEmpty()) { + ComponentWidget errorComponent = ComponentWidget.i18n(I18N_PREFIX + "empty"); + errorComponent.addId("error-component"); + + content.addChild(errorComponent); + } else { + content.addChild(new ScrollWidget(this.commandList)); + } + container.addFlexibleContent(content); + container.addContent(buttons); + container.addContent(hint); + + this.document.addChild(container); + } + + @Override + public void reload() { + this.loadCommands(); + super.reload(); + } + + private void loadCommands() { + this.commandWidgets.clear(); + for (Entry command : this.config.getAutoCommands().entrySet()) { + ContextMenuEntry entry = ContextMenuEntry.builder() + .text(Component.translatable(I18N_PREFIX + "manageContexts")) + .clickHandler(ignored -> { + this.redirectScreen(new CommandContextManagerActivity(command.getValue())); + return true; + }) + .build(); + + ContextMenu menu = new ContextMenu(); + menu.addEntry(entry); + + AutoCommandWidget widget = new AutoCommandWidget(command.getValue()); + widget.setContextMenu(menu); + this.commandWidgets.put(command.getKey(), widget); + } + } + + private void updateButtonVisibility(boolean visible) { + this.editButton.setEnabled(visible); + this.removeButton.setEnabled(visible); + } + + private void performAction(Action action) { + if(action == Action.EDIT && (Key.L_SHIFT.isPressed() || Key.L_CONTROL.isPressed())) { + this.redirectScreen(new CommandContextManagerActivity(this.selectedCommand)); + return; + } + Consumer callback = (command) -> { + this.config.setAutoCommand(command); + this.selectedCommand = command; + this.reload(); + }; + + AdvancedPopup popup = switch (action) { + case ADD -> new EditAutoCommandPopup(null, callback); + case EDIT -> new EditAutoCommandPopup(this.selectedCommand, callback); + case REMOVE -> SimpleAdvancedPopup.builder() + .title(Component.translatable(I18N_PREFIX + "remove.title")) + .description(Component.translatable( + I18N_PREFIX + "remove.description", + Component.text(this.selectedCommand.getName(), NamedTextColor.AQUA) + )) + .addButton(SimplePopupButton.confirm(simplePopupButton -> { + this.config.removeAutoCommand(this.selectedCommand.getUuid()); + this.commandList.listSession().setSelectedEntry(null); + this.reload(); + })) + .addButton(SimplePopupButton.cancel()) + .build(); + }; + + popup.displayInOverlay(); + } + + private enum Action { + ADD, + EDIT, + REMOVE + } +} diff --git a/core/src/main/java/com/rappytv/autocommand/core/ui/popup/EditAutoCommandPopup.java b/core/src/main/java/com/rappytv/autocommand/core/ui/popup/EditAutoCommandPopup.java new file mode 100644 index 0000000..b26e917 --- /dev/null +++ b/core/src/main/java/com/rappytv/autocommand/core/ui/popup/EditAutoCommandPopup.java @@ -0,0 +1,142 @@ +package com.rappytv.autocommand.core.ui.popup; + +import com.rappytv.autocommand.api.AutoCommandMeta; +import java.util.ArrayList; +import java.util.function.Consumer; +import net.labymod.api.client.component.Component; +import net.labymod.api.client.component.TranslatableComponent; +import net.labymod.api.client.gui.screen.activity.Link; +import net.labymod.api.client.gui.screen.widget.Widget; +import net.labymod.api.client.gui.screen.widget.widgets.ComponentWidget; +import net.labymod.api.client.gui.screen.widget.widgets.input.CheckBoxWidget; +import net.labymod.api.client.gui.screen.widget.widgets.input.CheckBoxWidget.State; +import net.labymod.api.client.gui.screen.widget.widgets.input.SliderWidget; +import net.labymod.api.client.gui.screen.widget.widgets.input.TextFieldWidget; +import net.labymod.api.client.gui.screen.widget.widgets.layout.FlexibleContentWidget; +import net.labymod.api.client.gui.screen.widget.widgets.layout.list.VerticalListWidget; +import net.labymod.api.client.gui.screen.widget.widgets.popup.SimpleAdvancedPopup; + +@Link("command-popup.lss") +public class EditAutoCommandPopup extends SimpleAdvancedPopup { + + private static final String I18N_FORMAT = "autocommand.popup.%s.%s"; + + private final AutoCommandMeta meta; + private final SimplePopupButton confirmButton; + + private CheckBoxWidget enabledCheckbox; + private TextFieldWidget nameTextField; + private TextFieldWidget commandTextField; + private SliderWidget delaySlider; + + public EditAutoCommandPopup(AutoCommandMeta meta, Consumer onSubmit) { + boolean create = meta == null; + this.meta = create ? AutoCommandMeta.builder().build() : meta; + + TranslatableComponent title = Component.translatable( + "autocommand.popup.title." + (create ? "add" : "edit") + ); + if(!create) { + title.argument(Component.text(this.meta.getName())); + } + super.title = title; + super.buttons = new ArrayList<>(); + super.buttons.add(this.confirmButton = SimplePopupButton.confirm((ignored) -> { + this.meta.setEnabled(this.enabledCheckbox.state() == State.CHECKED); + this.meta.setName(this.nameTextField.getText()); + this.meta.setCommand(this.commandTextField.getText()); + this.meta.setDelay(this.delaySlider.getValue()); + onSubmit.accept(this.meta); + })); + super.buttons.add(SimplePopupButton.cancel()); + super.widgetFunction = document -> + document.addChild(this.buildOptionArea()); + } + + private VerticalListWidget buildOptionArea() { + VerticalListWidget options = new VerticalListWidget<>(); + options.addId("options"); + + options.addChild(this.buildEnabledCheckbox()); + options.addChild(this.buildNameTextField()); + options.addChild(this.buildCommandTextField()); + options.addChild(this.buildDelaySlider()); + this.updateConfirmButton(); + + return options; + } + + private FlexibleContentWidget buildEnabledCheckbox() { + this.enabledCheckbox = new CheckBoxWidget(); + this.enabledCheckbox.addId("enabled-checkbox"); + this.enabledCheckbox.setState(this.meta.isEnabled() ? State.CHECKED : State.UNCHECKED); + + return this.buildOptionArea("enabled", this.enabledCheckbox); + } + + private FlexibleContentWidget buildNameTextField() { + this.nameTextField = new TextFieldWidget(); + this.nameTextField.addId("name-textfield"); + this.nameTextField.maximalLength(48); + this.nameTextField.setText(this.meta.getName()); + this.nameTextField.placeholder(Component.translatable(String.format( + I18N_FORMAT, + "name", + "placeholder" + ))); + + return this.buildOptionArea("name", this.nameTextField); + } + + private FlexibleContentWidget buildCommandTextField() { + this.commandTextField = new TextFieldWidget(); + this.commandTextField.addId("command-textfield"); + this.commandTextField.maximalLength(1024); + this.commandTextField.setText(this.meta.getCommand()); + this.commandTextField.placeholder(Component.translatable(String.format( + I18N_FORMAT, + "command", + "placeholder" + ))); + + return this.buildOptionArea("command", this.commandTextField); + } + + private FlexibleContentWidget buildDelaySlider() { + this.delaySlider = new SliderWidget((value) -> this.updateConfirmButton()); + this.delaySlider.addId("delay-slider"); + this.delaySlider.range(0, 30); + this.delaySlider.setValue(this.meta.getDelay()); + + return this.buildOptionArea("delay", this.delaySlider); + } + + private FlexibleContentWidget buildOptionArea(String id, Widget widget) { + ComponentWidget labelComponent = ComponentWidget.i18n(String.format( + I18N_FORMAT, + id, + "name" + )); + labelComponent.addId("option-label"); + + FlexibleContentWidget area = new FlexibleContentWidget(); + area.addId("option-area"); + area.addId("option-area-" + id); + area.addContent(labelComponent); + area.addContent(widget); + widget.setActionListener(this::updateConfirmButton); + + return area; + } + + private void updateConfirmButton() { + boolean valid = !this.nameTextField.getText().isBlank() && !this.commandTextField.getText().isBlank(); + boolean enabledChanged = this.enabledCheckbox.state() == (this.meta.isEnabled() ? State.UNCHECKED : State.CHECKED); + boolean changed = enabledChanged + || !this.nameTextField.getText().equals(this.meta.getName()) + || !this.commandTextField.getText().equals(this.meta.getCommand()) + || this.delaySlider.getValue() != this.meta.getDelay(); + + this.confirmButton.enabled(valid && changed); + } +} diff --git a/core/src/main/java/com/rappytv/autocommand/core/ui/widget/AutoCommandContextWidget.java b/core/src/main/java/com/rappytv/autocommand/core/ui/widget/AutoCommandContextWidget.java new file mode 100644 index 0000000..106f124 --- /dev/null +++ b/core/src/main/java/com/rappytv/autocommand/core/ui/widget/AutoCommandContextWidget.java @@ -0,0 +1,63 @@ +package com.rappytv.autocommand.core.ui.widget; + +import com.rappytv.autocommand.api.AutoCommandMeta; +import com.rappytv.autocommand.api.AutoCommandMeta.Context.Type; +import net.labymod.api.Textures.SpriteCommon; +import net.labymod.api.client.gui.lss.property.annotation.AutoWidget; +import net.labymod.api.client.gui.screen.Parent; +import net.labymod.api.client.gui.screen.widget.action.Pressable; +import net.labymod.api.client.gui.screen.widget.widgets.ComponentWidget; +import net.labymod.api.client.gui.screen.widget.widgets.input.ButtonWidget; +import net.labymod.api.client.gui.screen.widget.widgets.layout.list.HorizontalListWidget; +import net.labymod.api.client.gui.screen.widget.widgets.layout.list.VerticalListWidget; +import net.labymod.api.client.gui.screen.widget.widgets.renderer.IconWidget; + +@AutoWidget +public class AutoCommandContextWidget extends HorizontalListWidget { + + private final AutoCommandMeta.Context context; + private final Pressable onDelete; + + public AutoCommandContextWidget(AutoCommandMeta.Context context, Pressable onDelete) { + this.context = context; + this.onDelete = onDelete; + } + + @Override + public void initialize(Parent parent) { + super.initialize(parent); + + IconWidget icon = new IconWidget( + this.context.type() == Type.SINGLEPLAYER + ? SpriteCommon.SINGLEPLAYER + : SpriteCommon.MULTIPLAYER + ); + icon.addId("context-icon"); + + VerticalListWidget componentGroup = new VerticalListWidget<>(); + componentGroup.addId("component-group"); + + ComponentWidget nameComponent = ComponentWidget.text(this.context.name()); + nameComponent.addId("name-label"); + + ComponentWidget typeComponent = ComponentWidget.i18n(this.formatContextType()); + typeComponent.addId("type-label"); + + componentGroup.addChild(nameComponent); + componentGroup.addChild(typeComponent); + + ButtonWidget removeButton = ButtonWidget.text("✘", this.onDelete); + removeButton.addId("remove-button"); + + this.addEntry(icon); + this.addEntry(componentGroup); + this.addEntry(removeButton); + } + + private String formatContextType() { + return String.format( + "autocommand.activity.context.type.%s", + this.context.type().name().toLowerCase() + ); + } +} diff --git a/core/src/main/java/com/rappytv/autocommand/core/ui/widget/AutoCommandWidget.java b/core/src/main/java/com/rappytv/autocommand/core/ui/widget/AutoCommandWidget.java new file mode 100644 index 0000000..43e0b34 --- /dev/null +++ b/core/src/main/java/com/rappytv/autocommand/core/ui/widget/AutoCommandWidget.java @@ -0,0 +1,92 @@ +package com.rappytv.autocommand.core.ui.widget; + +import com.rappytv.autocommand.api.AutoCommandMeta; +import com.rappytv.autocommand.api.AutoCommandTextures; +import java.text.DecimalFormat; +import net.labymod.api.client.component.Component; +import net.labymod.api.client.component.format.NamedTextColor; +import net.labymod.api.client.gui.lss.property.annotation.AutoWidget; +import net.labymod.api.client.gui.screen.Parent; +import net.labymod.api.client.gui.screen.widget.Widget; +import net.labymod.api.client.gui.screen.widget.widgets.ComponentWidget; +import net.labymod.api.client.gui.screen.widget.widgets.layout.list.HorizontalListWidget; +import net.labymod.api.client.gui.screen.widget.widgets.layout.list.VerticalListWidget; +import net.labymod.api.client.gui.screen.widget.widgets.renderer.IconWidget; + +@AutoWidget +public class AutoCommandWidget extends HorizontalListWidget { + + private static final DecimalFormat FORMAT = new DecimalFormat("#.##"); + + private final AutoCommandMeta meta; + + public AutoCommandWidget(AutoCommandMeta meta) { + this.meta = meta; + } + + @Override + public void initialize(Parent parent) { + super.initialize(parent); + + if(this.meta.isEnabled()) { + this.removeId("disabled"); + } else { + this.addId("disabled"); + } + + IconWidget icon = new IconWidget(AutoCommandTextures.COMMAND_BLOCK); + icon.addId("command-icon"); + + VerticalListWidget componentGroup = new VerticalListWidget<>(); + componentGroup.addId("component-group"); + + HorizontalListWidget firstLine = new HorizontalListWidget(); + firstLine.addId("first-line"); + + ComponentWidget nameComponent = ComponentWidget.text(this.meta.getName()); + nameComponent.addId("name-label"); + + firstLine.addEntry(nameComponent); + + Component metaComponent = Component.empty(); + boolean addDelay = this.meta.getDelay() > 0f; + boolean addContext = !this.meta.getContexts().isEmpty(); + + if (addDelay) { + metaComponent.append(Component.text(this.formatDelay(), NamedTextColor.YELLOW)); + } + if(addContext) { + if(addDelay) { + metaComponent.append(Component.text(" | ", NamedTextColor.DARK_GRAY)); + } + metaComponent.append(Component.text(this.formatContexts(), NamedTextColor.AQUA)); + } + ComponentWidget metaComponentWidget = ComponentWidget.component(metaComponent); + metaComponentWidget.addId("meta-label"); + + if(addDelay || addContext) { + firstLine.addEntry(metaComponentWidget); + } + + ComponentWidget commandComponent = ComponentWidget.text(this.meta.getCommand()); + commandComponent.addId("command-label"); + + componentGroup.addChild(firstLine); + componentGroup.addChild(commandComponent); + + this.addEntry(icon); + this.addEntry(componentGroup); + } + + public AutoCommandMeta getMeta() { + return this.meta; + } + + private String formatDelay() { + return String.format("⌚ %ss", FORMAT.format(this.meta.getDelay())); + } + + private String formatContexts() { + return String.format("🌍 %s", this.meta.getContexts().size()); + } +} diff --git a/core/src/main/java/org/example/core/ExampleAddon.java b/core/src/main/java/org/example/core/ExampleAddon.java deleted file mode 100644 index 47a4563..0000000 --- a/core/src/main/java/org/example/core/ExampleAddon.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.example.core; - -import net.labymod.api.addon.LabyAddon; -import net.labymod.api.models.addon.annotation.AddonMain; -import org.example.core.commands.ExamplePingCommand; -import org.example.core.listener.ExampleGameTickListener; - -@AddonMain -public class ExampleAddon extends LabyAddon { - - @Override - protected void enable() { - this.registerSettingCategory(); - - this.registerListener(new ExampleGameTickListener(this)); - this.registerCommand(new ExamplePingCommand()); - - this.logger().info("Enabled the Addon"); - } - - @Override - protected Class configurationClass() { - return ExampleConfiguration.class; - } -} diff --git a/core/src/main/java/org/example/core/ExampleConfiguration.java b/core/src/main/java/org/example/core/ExampleConfiguration.java deleted file mode 100644 index 0faafb1..0000000 --- a/core/src/main/java/org/example/core/ExampleConfiguration.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.example.core; - -import net.labymod.api.addon.AddonConfig; -import net.labymod.api.client.gui.screen.widget.widgets.input.SwitchWidget.SwitchSetting; -import net.labymod.api.configuration.loader.annotation.ConfigName; -import net.labymod.api.configuration.loader.property.ConfigProperty; - -@ConfigName("settings") -public class ExampleConfiguration extends AddonConfig { - - @SwitchSetting - private final ConfigProperty enabled = new ConfigProperty<>(true); - - @Override - public ConfigProperty enabled() { - return this.enabled; - } -} diff --git a/core/src/main/java/org/example/core/commands/ExamplePingCommand.java b/core/src/main/java/org/example/core/commands/ExamplePingCommand.java deleted file mode 100644 index 5952915..0000000 --- a/core/src/main/java/org/example/core/commands/ExamplePingCommand.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.example.core.commands; - -import net.labymod.api.client.chat.command.Command; -import net.labymod.api.client.component.Component; -import net.labymod.api.client.component.format.NamedTextColor; - -public class ExamplePingCommand extends Command { - - public ExamplePingCommand() { - super("ping", "pong"); - - this.withSubCommand(new ExamplePingSubCommand()); - } - - @Override - public boolean execute(String prefix, String[] arguments) { - if (prefix.equalsIgnoreCase("ping")) { - this.displayMessage(Component.text("Ping!", NamedTextColor.AQUA)); - return false; - } - - this.displayMessage(Component.text("Pong!", NamedTextColor.GOLD)); - return true; - } -} diff --git a/core/src/main/java/org/example/core/commands/ExamplePingSubCommand.java b/core/src/main/java/org/example/core/commands/ExamplePingSubCommand.java deleted file mode 100644 index 8c6a981..0000000 --- a/core/src/main/java/org/example/core/commands/ExamplePingSubCommand.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.example.core.commands; - -import net.labymod.api.client.chat.command.SubCommand; -import net.labymod.api.client.component.Component; -import net.labymod.api.client.component.format.NamedTextColor; - -public class ExamplePingSubCommand extends SubCommand { - - protected ExamplePingSubCommand() { - super("pong"); - } - - @Override - public boolean execute(String prefix, String[] arguments) { - this.displayMessage(Component.text("Ping Pong!", NamedTextColor.GRAY)); - return true; - } -} diff --git a/core/src/main/java/org/example/core/listener/ExampleGameTickListener.java b/core/src/main/java/org/example/core/listener/ExampleGameTickListener.java deleted file mode 100644 index b631b85..0000000 --- a/core/src/main/java/org/example/core/listener/ExampleGameTickListener.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.example.core.listener; - -import net.labymod.api.event.Phase; -import net.labymod.api.event.Subscribe; -import net.labymod.api.event.client.lifecycle.GameTickEvent; -import org.example.core.ExampleAddon; - -public class ExampleGameTickListener { - - private final ExampleAddon addon; - - public ExampleGameTickListener(ExampleAddon addon) { - this.addon = addon; - } - - @Subscribe - public void onGameTick(GameTickEvent event) { - if (event.phase() != Phase.PRE) { - return; - } - - this.addon.logger().info(this.addon.configuration().enabled().get() ? "enabled" : "disabled"); - } -} diff --git a/core/src/main/resources/assets/autocommand/i18n/en_us.json b/core/src/main/resources/assets/autocommand/i18n/en_us.json new file mode 100644 index 0000000..c45b15d --- /dev/null +++ b/core/src/main/resources/assets/autocommand/i18n/en_us.json @@ -0,0 +1,54 @@ +{ + "autocommand": { + "settings": { + "enabled": { + "name": "Enabled" + }, + "commandManager": { + "name": "Manage automated commands" + } + }, + "activity": { + "command": { + "empty": "Nothing here yet.\nGo ahead and create an automated command!", + "hint": "Hint: Right-click to limit which worlds a command runs in.", + "manageContexts": "Manage contexts", + "remove": { + "title": "Are you sure?", + "description": "Do you want to delete your automated command %s?" + } + }, + "context": { + "back": "Leave context manager", + "add": "Add current world", + "invalid": "You're not in a valid world context.\n\nIf you believe this is an error please create a bug report.", + "duplicate": "This world context is already in the list.", + "type": { + "singleplayer": "Singleplayer World", + "multiplayer": "Multiplayer Server" + } + } + }, + "popup": { + "title": { + "add": "Create automated command", + "edit": "Edit \"%s\"", + "contexts": "Manage contexts of \"%s\"" + }, + "enabled": { + "name": "Enabled" + }, + "name": { + "name": "Name", + "placeholder": "Example name" + }, + "command": { + "name": "Command", + "placeholder": "/example" + }, + "delay": { + "name": "Delay (in seconds)" + } + } + } +} diff --git a/core/src/main/resources/assets/autocommand/textures/command_block.png b/core/src/main/resources/assets/autocommand/textures/command_block.png new file mode 100644 index 0000000..c3685c2 Binary files /dev/null and b/core/src/main/resources/assets/autocommand/textures/command_block.png differ diff --git a/core/src/main/resources/assets/autocommand/textures/icon.png b/core/src/main/resources/assets/autocommand/textures/icon.png new file mode 100644 index 0000000..838e7c2 Binary files /dev/null and b/core/src/main/resources/assets/autocommand/textures/icon.png differ diff --git a/core/src/main/resources/assets/autocommand/themes/vanilla/lss/command-manager.lss b/core/src/main/resources/assets/autocommand/themes/vanilla/lss/command-manager.lss new file mode 100644 index 0000000..9378332 --- /dev/null +++ b/core/src/main/resources/assets/autocommand/themes/vanilla/lss/command-manager.lss @@ -0,0 +1,44 @@ +.command-container { + width: 100%; + height: 100%; + + .content { + .error-component { + width: 80%; + text-color: blue; + alignment-x: center; + alignment-y: center; + } + + Scroll { + height: 100%; + width: 100%; + + .command-list { + height: fit-content; + space-between-entries: 2; + selectable: true; + } + + Scrollbar { + width: 5; + height: 100%; + margin-left: 5; + } + } + } + + .buttons { + height: fit-content; + width: 100%; + space-between-entries: 5; + layout: fill; + margin: 5 0 5 0; + } + + .hint-text { + text-color: yellow; + alignment-x: center; + margin-bottom: 5; + } +} \ No newline at end of file diff --git a/core/src/main/resources/assets/autocommand/themes/vanilla/lss/command-meta.lss b/core/src/main/resources/assets/autocommand/themes/vanilla/lss/command-meta.lss new file mode 100644 index 0000000..76add0f --- /dev/null +++ b/core/src/main/resources/assets/autocommand/themes/vanilla/lss/command-meta.lss @@ -0,0 +1,44 @@ +AutoCommand { + height: 28; + padding: 1; + space-between-entries: 5; + + .command-icon { + height: 16; + width: 16; + margin-left: 5; + } + + .component-group { + width: calc(100% - 30); + min-height: fit-content; + height: 70%; + + .first-line { + height: fit-content; + + .name-label { + text-color: white; + alignment-x: left; + } + + .meta-label { + alignment: right; + } + } + + .command-label { + text-color: dark_gray; + } + } + + &:selected { + padding: 0; + border: 1 gray; + background-color: black; + } + + &.disabled { + opacity: 0.6; + } +} \ No newline at end of file diff --git a/core/src/main/resources/assets/autocommand/themes/vanilla/lss/command-popup.lss b/core/src/main/resources/assets/autocommand/themes/vanilla/lss/command-popup.lss new file mode 100644 index 0000000..3038942 --- /dev/null +++ b/core/src/main/resources/assets/autocommand/themes/vanilla/lss/command-popup.lss @@ -0,0 +1,29 @@ +.options { + height: fit-content; + width: 150; + space-between-entries: 6; + + .option-area { + height: fit-content; + + .option-label { + font-size: 0.8; + alignment-x: center; + margin-bottom: 3; + } + + .enabled-checkbox { + alignment-x: center; + } + + TextField { + width: 80%; + } + + Slider { + width: 80%; + height: fit-content; + alignment-x: center; + } + } +} \ No newline at end of file diff --git a/core/src/main/resources/assets/autocommand/themes/vanilla/lss/context-manager.lss b/core/src/main/resources/assets/autocommand/themes/vanilla/lss/context-manager.lss new file mode 100644 index 0000000..e67ff57 --- /dev/null +++ b/core/src/main/resources/assets/autocommand/themes/vanilla/lss/context-manager.lss @@ -0,0 +1,92 @@ +.context-manager { + height: 100%; + width: 100%; + + .back-container { + height: fit-content; + width: 100%; + margin-bottom: 5; + space-between-entries: 3; + + .back-button { + padding: 0; + width: height; + + Icon { + height: 8; + width: height; + } + } + } + + .selected-command { + width: 100%; + padding: 0; + border: 1 gold; + } + + .add-button { + height: 16; + alignment-x: center; + margin-top: 3; + } + + Scroll { + margin-bottom: 5; + + .context-list { + height: 100%; + } + + Scrollbar { + width: 5; + height: 100%; + margin-left: 5; + } + } +} + +AutoCommandContext { + height: 28; + padding: 1; + space-between-entries: 5; + + .context-icon { + height: 16; + width: 16; + margin-left: 5; + } + + .component-group { + width: fit-content; + height: 70%; + + .name-label { + text-color: aqua; + } + + .type-label { + text-color: dark_gray; + } + } + + .remove-button { + alignment: right; + width: height; + padding: 0; + margin-right: 3; + + Component { + text-color: white !important; + } + + &:hover Component { + text-color: red !important; + } + } + + &:selected { + padding: 0; + border: 1 gray; + } +} \ No newline at end of file diff --git a/core/src/main/resources/assets/autocommand/themes/vanilla/textures/settings.png b/core/src/main/resources/assets/autocommand/themes/vanilla/textures/settings.png new file mode 100644 index 0000000..47735a6 Binary files /dev/null and b/core/src/main/resources/assets/autocommand/themes/vanilla/textures/settings.png differ diff --git a/core/src/main/resources/assets/example/i18n/en_us.json b/core/src/main/resources/assets/example/i18n/en_us.json deleted file mode 100644 index 0850d97..0000000 --- a/core/src/main/resources/assets/example/i18n/en_us.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "example": { - "settings": { - "enabled": { - "name": "Enabled" - } - } - } -} diff --git a/gradle.properties b/gradle.properties index 44dc10b..138ade0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ org.gradle.jvmargs=-Xmx4096m -net.labymod.minecraft-versions=1.8.9;1.12.2;1.16.5;1.17.1;1.18.2;1.19.2;1.19.3;1.19.4;1.20.1;1.20.2;1.20.4;1.20.5;1.20.6;1.21;1.21.1;1.21.3;1.21.4;1.21.5 \ No newline at end of file +net.labymod.minecraft-versions=1.8.9;1.12.2;1.16.5;1.17.1;1.18.2;1.19.4;1.20.1;1.20.4;1.20.6;1.21;1.21.1;1.21.3;1.21.4;1.21.5;1.21.8;1.21.10;1.21.11;26.1;26.1.1;26.1.2 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e1adfb4..6ba24c8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +distributionSha256Sum=bafc141b619ad6350fd975fc903156dd5c151998cc8b058e8c1044ab5f7b031f diff --git a/scripts/update-gradle-sha.sh b/scripts/update-gradle-sha.sh new file mode 100644 index 0000000..bcb5094 --- /dev/null +++ b/scripts/update-gradle-sha.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# Pins distributionSha256Sum in gradle-wrapper.properties to the official +# checksum published by Gradle for the current distributionUrl. +set -euo pipefail + +PROPS="$(dirname "$0")/../gradle/wrapper/gradle-wrapper.properties" + +url="$(grep -E '^distributionUrl=' "$PROPS" | cut -d= -f2- | sed 's|\\:|:|g')" +[ -n "$url" ] || { echo "distributionUrl not found in $PROPS" >&2; exit 1; } + +sha="$(curl -fsSL "${url}.sha256")" +[ -n "$sha" ] || { echo "failed to fetch ${url}.sha256" >&2; exit 1; } + +if grep -qE '^distributionSha256Sum=' "$PROPS"; then + sed -i.bak "s|^distributionSha256Sum=.*|distributionSha256Sum=${sha}|" "$PROPS" + rm -f "${PROPS}.bak" +else + printf 'distributionSha256Sum=%s\n' "$sha" >> "$PROPS" +fi + +echo "pinned $(basename "$url") -> $sha" \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 6a0b6df..eb0de36 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,10 +1,10 @@ -rootProject.name = "labymod4-addon-template" +rootProject.name = "autocommand" pluginManagement { - val labyGradlePluginVersion = "0.5.9" + val labyGradlePluginVersion = "0.8.1" buildscript { repositories { - maven("https://dist.labymod.net/api/v1/maven/release/") + maven("https://maven.laby.net/api/v1/maven/release/") maven("https://maven.neoforged.net/releases/") maven("https://maven.fabricmc.net/") gradlePluginPortal()