Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
1a884ab
chore: Setup workspace
RappyTV Nov 22, 2025
e45972e
feat: Implement basic functionality
RappyTV Nov 24, 2025
78ca1a5
chore: Keep template up-to-date
RappyTV Feb 2, 2026
28d57bc
chore: Make AutoCommandMeta constructor private rather than Internal
RappyTV Feb 2, 2026
d57a134
feat: Extend config functionality
RappyTV Feb 2, 2026
bb34d5b
fix: Use correct path for minecraft versions
RappyTV Feb 2, 2026
9e5a935
chore: Refine AutoCommand predicate
RappyTV Feb 2, 2026
a6f7af3
fix: Addon config fails to load
RappyTV Feb 7, 2026
d17842d
chore: Remove debugging method and command
RappyTV Feb 7, 2026
5a17ffc
chore: Add todo comment
RappyTV Feb 7, 2026
d247f64
sync with addon template
RappyTV Jun 12, 2026
6f784e0
chore: reformat command meta
RappyTV Jun 12, 2026
1071f37
feat: add autocommand list
RappyTV Jun 14, 2026
7f51bb6
feat: add manage popup
RappyTV Jul 8, 2026
cca3893
feat: implement management popup
RappyTV Jul 8, 2026
d95de9a
chore: add context count to AutoCommandWidget
RappyTV Jul 8, 2026
348c337
chore: add textfield placeholders
RappyTV Jul 8, 2026
0f0b128
feat: add context manager
RappyTV Jul 9, 2026
59f2efe
feat: add context manager activity instead of popup
RappyTV Jul 9, 2026
e5ad6fb
fix: disabled commands were not seethrough
RappyTV Jul 9, 2026
a563ca6
feat: move commands into separate config
RappyTV Jul 9, 2026
95b82b9
fix: edit/remove button disabled when returning to list
RappyTV Jul 9, 2026
967f363
chore: remove unused method
RappyTV Jul 9, 2026
bb879a0
chore: add texture helper class
RappyTV Jul 9, 2026
f228a2a
chore: add hint
RappyTV Jul 9, 2026
eceee84
feat: add icons
RappyTV Jul 23, 2026
8694a59
chore: improve hint readability
RappyTV Jul 23, 2026
9c04e8d
feat: add icon, compress images
RappyTV Jul 23, 2026
2bf17f0
feat: add config sprite, register revision
RappyTV Jul 23, 2026
4828617
Merge pull request #1 from RappyLabyAddons/feat/ui
RappyTV Jul 23, 2026
f310a17
fix: build always fails
RappyTV Jul 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 12 additions & 19 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
227 changes: 227 additions & 0 deletions api/src/main/java/com/rappytv/autocommand/api/AutoCommandMeta.java
Original file line number Diff line number Diff line change
@@ -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<UUID, Task> tasks = new HashMap<>();

private UUID uuid;
private boolean enabled;
private String name;
private String command;
private float delay;
private List<Context> contexts;

private AutoCommandMeta(UUID uuid, boolean enabled, String name, String command, float delay,
List<Context> 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<Context> getContexts() {
return this.contexts;
}

public void setContexts(List<Context> 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<Context> 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<Context> 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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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"));
}
28 changes: 16 additions & 12 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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<AutoCommandAddonConfig> {

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<? extends AutoCommandAddonConfig> configurationClass() {
return AutoCommandAddonConfig.class;
}

public static CommandConfig getCommandConfig() {
return INSTANCE.commandConfig;
}

public static void saveCommandConfig() {
INSTANCE.saveCustomConfiguration(CommandConfig.class);
}
}
Loading