- [FEAT] New APIs for Maintenance Mode and rewrite commands system

- [FEAT] Improved config system to fix old loading bugs and support JSON
- [FEAT] LuckPerms support for commands
This commit is contained in:
2024-08-10 14:13:51 +02:00
parent 2c13d507c3
commit 0dbf07de46
39 changed files with 729 additions and 237 deletions

View File

@@ -3,7 +3,7 @@ def projectIcon = "https://cdn.modrinth.com/data/Nn8Wasaq/a172c634683a11a2e9ae59
def JDK = "21";
def majorMc = "1.21";
def modLoaders = "neoforge|fabric|quilt";
def supportedMc = "1.21";
def supportedMc = "1.21|1.21.1";
def reltype = "port";
pipeline {

View File

@@ -1,53 +1,155 @@
package com.hypherionmc.craterlib.api.commands;
import com.hypherionmc.craterlib.compat.LuckPermsCompat;
import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment;
import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile;
import com.hypherionmc.craterlib.nojang.commands.BridgedCommandSourceStack;
import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer;
import com.hypherionmc.craterlib.utils.TriConsumer;
import com.mojang.brigadier.arguments.ArgumentType;
import lombok.Getter;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.BoolArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.GameProfileArgument;
import org.apache.commons.lang3.tuple.Pair;
import net.minecraft.world.entity.player.Player;
import org.jetbrains.annotations.ApiStatus;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.function.Consumer;
@Getter
public class CraterCommand {
private final HashMap<String, Pair<ArgumentType<?>, TriConsumer<?, ?, BridgedCommandSourceStack>>> arguments = new LinkedHashMap<>();
private Consumer<BridgedCommandSourceStack> executor;
private final LiteralArgumentBuilder<CommandSourceStack> mojangCommand;
private int permLevel = 4;
private String luckPermNode = "";
private final String commandName;
private int permissionLevel = 4;
CraterCommand(String commandName) {
this.commandName = commandName;
CraterCommand(LiteralArgumentBuilder<CommandSourceStack> cmd) {
this.mojangCommand = cmd;
}
public static CraterCommand literal(String commandName) {
return new CraterCommand(commandName);
return new CraterCommand(Commands.literal(commandName));
}
public CraterCommand requiresPermission(int perm) {
this.permissionLevel = perm;
this.permLevel = perm;
this.mojangCommand.requires(this::checkPermission);
return this;
}
public CraterCommand withGameProfileArgument(String key, TriConsumer<BridgedPlayer, List<BridgedGameProfile>, BridgedCommandSourceStack> executor) {
arguments.put(key, Pair.of(GameProfileArgument.gameProfile(), executor));
public CraterCommand withNode(String key) {
this.luckPermNode = key;
return this;
}
public CraterCommand then(CraterCommand child) {
this.mojangCommand.then(child.mojangCommand);
return this;
}
public CraterCommand withGameProfilesArgument(String key, CommandExecutorWithArgs<List<BridgedGameProfile>> executor) {
this.mojangCommand.then(Commands.argument(key, GameProfileArgument.gameProfile())
.executes(context -> executor.run(
BridgedPlayer.of(context.getSource().getPlayer()),
GameProfileArgument.getGameProfiles(context, key).stream().map(BridgedGameProfile::of).toList(),
BridgedCommandSourceStack.of(context.getSource()))
));
return this;
}
public CraterCommand withBoolArgument(String key, CommandExecutorWithArgs<Boolean> executor) {
this.mojangCommand.then(Commands.argument(key, BoolArgumentType.bool())
.executes(context -> executor.run(
BridgedPlayer.of(context.getSource().getPlayer()),
BoolArgumentType.getBool(context, key),
BridgedCommandSourceStack.of(context.getSource())
)));
return this;
}
public CraterCommand withWordArgument(String key, CommandExecutorWithArgs<String> executor) {
this.mojangCommand.then(Commands.argument(key, StringArgumentType.word())
.executes(context -> executor.run(
BridgedPlayer.of(context.getSource().getPlayer()),
StringArgumentType.getString(context, key),
BridgedCommandSourceStack.of(context.getSource())
)));
return this;
}
public CraterCommand withStringArgument(String key, CommandExecutorWithArgs<String> executor) {
this.mojangCommand.then(Commands.argument(key, StringArgumentType.string())
.executes(context -> executor.run(
BridgedPlayer.of(context.getSource().getPlayer()),
StringArgumentType.getString(context, key),
BridgedCommandSourceStack.of(context.getSource())
)));
return this;
}
public CraterCommand withPhraseArgument(String key, CommandExecutorWithArgs<String> executor) {
this.mojangCommand.then(Commands.argument(key, StringArgumentType.greedyString())
.executes(context -> executor.run(
BridgedPlayer.of(context.getSource().getPlayer()),
StringArgumentType.getString(context, key),
BridgedCommandSourceStack.of(context.getSource())
)));
return this;
}
public CraterCommand withIntegerArgument(String key, CommandExecutorWithArgs<Integer> executor) {
this.mojangCommand.then(Commands.argument(key, IntegerArgumentType.integer())
.executes(context -> executor.run(
BridgedPlayer.of(context.getSource().getPlayer()),
IntegerArgumentType.getInteger(context, key),
BridgedCommandSourceStack.of(context.getSource())
)));
return this;
}
public CraterCommand execute(SingleCommandExecutor<BridgedCommandSourceStack> executor) {
this.mojangCommand.executes(context -> executor.run(BridgedCommandSourceStack.of(context.getSource())));
return this;
}
@Deprecated(forRemoval = true)
public CraterCommand executes(Consumer<BridgedCommandSourceStack> ctx) {
executor = ctx;
return this;
return this.execute(stack -> {
ctx.accept(stack);
return 1;
});
}
public boolean hasArguments() {
return !arguments.isEmpty();
@Deprecated(forRemoval = true)
public CraterCommand withGameProfileArgument(String key, TriConsumer<BridgedPlayer, List<BridgedGameProfile>, BridgedCommandSourceStack> executor) {
return this.withGameProfilesArgument(key, (player, argument, stack) -> {
executor.accept(player, argument, stack);
return 1;
});
}
@ApiStatus.Internal
public void register(CommandDispatcher<CommandSourceStack> stack) {
stack.register(this.mojangCommand);
}
private boolean checkPermission(CommandSourceStack stack) {
if (!ModloaderEnvironment.INSTANCE.isModLoaded("luckperms") || !stack.isPlayer() || luckPermNode.isEmpty())
return stack.hasPermission(this.permLevel);
return LuckPermsCompat.INSTANCE.hasPermission(stack.getPlayer(), this.luckPermNode) || stack.hasPermission(this.permLevel);
}
@FunctionalInterface
public interface CommandExecutorWithArgs<S> {
int run(BridgedPlayer player, S argument, BridgedCommandSourceStack stack);
}
@FunctionalInterface
public interface SingleCommandExecutor<S> {
int run(S stack);
}
}

View File

@@ -4,7 +4,6 @@ import com.hypherionmc.craterlib.core.event.CraterEvent;
import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
@RequiredArgsConstructor
@Getter

View File

@@ -2,14 +2,17 @@ package com.hypherionmc.craterlib.api.events.server;
import com.hypherionmc.craterlib.api.commands.CraterCommand;
import com.hypherionmc.craterlib.core.event.CraterEvent;
import com.hypherionmc.craterlib.nojang.commands.CommandsRegistry;
import lombok.NoArgsConstructor;
import com.mojang.brigadier.CommandDispatcher;
import lombok.AllArgsConstructor;
import net.minecraft.commands.CommandSourceStack;
@NoArgsConstructor
@AllArgsConstructor
public class CraterRegisterCommandEvent extends CraterEvent {
private final CommandDispatcher<CommandSourceStack> stack;
public void registerCommand(CraterCommand cmd) {
CommandsRegistry.INSTANCE.registerCommand(cmd);
cmd.register(stack);
}
}

View File

@@ -0,0 +1,35 @@
package com.hypherionmc.craterlib.api.events.server;
import com.hypherionmc.craterlib.core.event.CraterEvent;
import com.hypherionmc.craterlib.nojang.network.protocol.status.WrappedServerStatus;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
public class ServerStatusEvent {
@RequiredArgsConstructor
@Getter
@Setter
public static class StatusRequestEvent extends CraterEvent {
private final Component status;
@Nullable
private Component newStatus = null;
}
@RequiredArgsConstructor
@Getter
@Setter
public static class FaviconRequestEvent extends CraterEvent {
private final Optional<WrappedServerStatus.WrappedFavicon> favicon;
private Optional<WrappedServerStatus.WrappedFavicon> newIcon = Optional.empty();
}
}

View File

@@ -2,7 +2,7 @@ package com.hypherionmc.craterlib.client.gui.config;
import com.hypherionmc.craterlib.CraterConstants;
import com.hypherionmc.craterlib.client.gui.config.widgets.*;
import com.hypherionmc.craterlib.core.config.ModuleConfig;
import com.hypherionmc.craterlib.core.config.AbstractConfig;
import com.hypherionmc.craterlib.core.config.annotations.HideFromScreen;
import com.hypherionmc.craterlib.core.config.annotations.SubConfig;
import com.hypherionmc.craterlib.core.config.annotations.Tooltip;
@@ -44,11 +44,11 @@ public class CraterConfigScreen extends Screen {
private static final int BOTTOM = 24;
private final Screen parent;
private final List<Option<?>> options = new ArrayList<>();
private final ModuleConfig config;
private final AbstractConfig config;
public double scrollerAmount;
private boolean dragging;
public CraterConfigScreen(ModuleConfig config, Screen parent, Object subConfig) {
public CraterConfigScreen(AbstractConfig config, Screen parent, Object subConfig) {
super(Component.translatable("cl." + config.getClass().getSimpleName().toLowerCase() + ".title"));
this.parent = parent;
this.config = config;
@@ -59,7 +59,7 @@ public class CraterConfigScreen extends Screen {
}
}
public CraterConfigScreen(ModuleConfig config, Screen parent) {
public CraterConfigScreen(AbstractConfig config, Screen parent) {
this(config, parent, null);
}

View File

@@ -1,7 +1,7 @@
package com.hypherionmc.craterlib.client.gui.config.widgets;
import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen;
import com.hypherionmc.craterlib.core.config.ModuleConfig;
import com.hypherionmc.craterlib.core.config.AbstractConfig;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
@@ -15,10 +15,10 @@ import net.minecraft.network.chat.Component;
public class SubConfigWidget<T> extends AbstractConfigWidget<T, Button> {
private final Object subConfig;
private final ModuleConfig config;
private final AbstractConfig config;
private final Screen screen;
public SubConfigWidget(ModuleConfig config, Screen screen, Object subConfig) {
public SubConfigWidget(AbstractConfig config, Screen screen, Object subConfig) {
this.config = config;
this.subConfig = subConfig;
this.screen = screen;

View File

@@ -0,0 +1,20 @@
package com.hypherionmc.craterlib.compat;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.model.user.User;
import net.minecraft.server.level.ServerPlayer;
public class LuckPermsCompat {
public static final LuckPermsCompat INSTANCE = new LuckPermsCompat();
private final LuckPerms luckPerms = LuckPermsProvider.get();
LuckPermsCompat() {}
public boolean hasPermission(ServerPlayer player, String perm) {
User luckPermsUser = luckPerms.getPlayerAdapter(ServerPlayer.class).getUser(player);
return luckPermsUser.getCachedData().getPermissionData().checkPermission(perm).asBoolean();
}
}

View File

@@ -0,0 +1,71 @@
package com.hypherionmc.craterlib.core.config;
import com.hypherionmc.craterlib.core.config.formats.AbstractConfigFormat;
import com.hypherionmc.craterlib.core.config.formats.JsonConfigFormat;
import com.hypherionmc.craterlib.core.config.formats.TomlConfigFormat;
import lombok.Getter;
import lombok.Setter;
import me.hypherionmc.moonconfig.core.Config;
import org.jetbrains.annotations.Nullable;
import java.io.File;
@Getter
public abstract class AbstractConfig<S> {
/* Final Variables */
private final transient File configPath;
private final transient String networkID;
private final transient String configName;
private final transient String modId;
private transient boolean wasSaveCalled = false;
@Setter
private transient AbstractConfigFormat<S> configFormat;
public AbstractConfig(String modId, String configName) {
this(modId, null, configName);
}
public AbstractConfig(String modId, @Nullable String subFolder, String configName) {
Config.setInsertionOrderPreserved(true);
if (!configName.endsWith(".toml") && !configName.endsWith(".json"))
configName = configName + ".toml";
File configDir = new File("config" + (subFolder == null ? "" : File.separator + subFolder));
configPath = new File(configDir, configName);
this.modId = modId;
this.networkID = modId + ":conf_" + configName.replace(".toml", "").replace(".json", "").replace("-", "_").toLowerCase();
this.configName = configName.replace(".toml", "").replace(".json", "");
configDir.mkdirs();
configFormat = configName.endsWith(".json") ? new JsonConfigFormat<>(configPath, this::onSave) : new TomlConfigFormat<>(configPath, this::onSave);
}
public void registerAndSetup(S config) {
configFormat.register(config);
ConfigController.register_config(this);
this.configReloaded();
}
public void saveConfig(S config) {
this.wasSaveCalled = true;
configFormat.saveConfig(config);
}
private void onSave() {
this.configReloaded();
this.wasSaveCalled = false;
}
public S readConfig(S config) {
return configFormat.readConfig(config);
}
public void migrateConfig(S config) {
configFormat.migrateConfig(config);
}
public abstract void configReloaded();
}

View File

@@ -3,6 +3,7 @@ package com.hypherionmc.craterlib.core.config;
import com.hypherionmc.craterlib.CraterConstants;
import lombok.Getter;
import me.hypherionmc.moonconfig.core.file.FileWatcher;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.ApiStatus;
import java.io.Serializable;
@@ -18,7 +19,7 @@ public final class ConfigController implements Serializable {
* Cache of registered configs
*/
@Getter
private static final HashMap<Object, FileWatcher> monitoredConfigs = new HashMap<>();
private static final HashMap<String, Pair<AbstractConfig, FileWatcher>> watchedConfigs = new HashMap<>();
/**
* INTERNAL METHOD - Register and watch the config
@@ -26,23 +27,34 @@ public final class ConfigController implements Serializable {
* @param config - The config class to register and watch
*/
@ApiStatus.Internal
@Deprecated
public static void register_config(ModuleConfig config) {
if (monitoredConfigs.containsKey(config)) {
CraterConstants.LOG.error("Failed to register " + config.getConfigPath().getName() + ". Config already registered");
register_config((AbstractConfig) config);
}
/**
* INTERNAL METHOD - Register and watch the config
*
* @param config - The config class to register and watch
*/
@ApiStatus.Internal
public static void register_config(AbstractConfig config) {
if (watchedConfigs.containsKey(config.getConfigPath().toString())) {
CraterConstants.LOG.error("Failed to register {}. Config already registered", config.getConfigPath().getName());
} else {
FileWatcher configWatcher = new FileWatcher();
try {
configWatcher.setWatch(config.getConfigPath(), () -> {
if (!config.isSaveCalled()) {
CraterConstants.LOG.info("Sending Reload Event for: " + config.getConfigPath().getName());
if (!config.isWasSaveCalled()) {
CraterConstants.LOG.info("Sending Reload Event for: {}", config.getConfigPath().getName());
config.configReloaded();
}
});
} catch (Exception e) {
CraterConstants.LOG.error("Failed to register " + config.getConfigPath().getName() + " for auto reloading. " + e.getMessage());
CraterConstants.LOG.error("Failed to register {} for auto reloading. {}", config.getConfigPath().getName(), e.getMessage());
}
monitoredConfigs.put(config, configWatcher);
CraterConstants.LOG.info("Registered " + config.getConfigPath().getName() + " successfully!");
watchedConfigs.put(config.getConfigPath().toString(), Pair.of(config, configWatcher));
CraterConstants.LOG.info("Registered {} successfully!", config.getConfigPath().getName());
}
}

View File

@@ -1,9 +1,7 @@
package com.hypherionmc.craterlib.core.config;
import com.hypherionmc.craterlib.core.config.formats.TomlConfigFormat;
import me.hypherionmc.moonconfig.core.CommentedConfig;
import me.hypherionmc.moonconfig.core.Config;
import me.hypherionmc.moonconfig.core.conversion.ObjectConverter;
import me.hypherionmc.moonconfig.core.file.CommentedFileConfig;
import java.io.File;
@@ -12,17 +10,8 @@ import java.io.File;
* Base Config class containing the save, upgrading and loading logic.
* All config classes must extend this class
*/
public class ModuleConfig {
/* Final Variables */
private final transient File configPath;
private final transient String networkID;
private final transient String configName;
private final transient String modId;
private transient boolean isSaveCalled = false;
@Deprecated
public class ModuleConfig extends AbstractConfig {
/**
* Set up the config
@@ -35,20 +24,7 @@ public class ModuleConfig {
}
public ModuleConfig(String modId, String subFolder, String configName) {
/* Preserve the order of the config values */
Config.setInsertionOrderPreserved(true);
/* Configure Paths and Network SYNC ID */
File configDir = new File("config" + (subFolder.isEmpty() ? "" : File.separator + subFolder));
configPath = new File(configDir + File.separator + configName + ".toml");
networkID = modId + ":conf_" + configName.replace("-", "_");
this.modId = modId;
this.configName = configName;
/* Check if the required directories exists, otherwise we create them */
if (!configDir.exists()) {
configDir.mkdirs();
}
super(modId, subFolder.isEmpty() ? null : subFolder, configName);
}
/**
@@ -57,14 +33,7 @@ public class ModuleConfig {
* @param config - The config class to use
*/
public void registerAndSetup(ModuleConfig config) {
if (!configPath.exists() || configPath.length() < 2) {
saveConfig(config);
} else {
migrateConfig(config);
}
/* Register the Config for Watching and events */
ConfigController.register_config(this);
this.configReloaded();
super.registerAndSetup(config);
}
/**
@@ -73,16 +42,7 @@ public class ModuleConfig {
* @param conf - The config class to serialize and save
*/
public void saveConfig(ModuleConfig conf) {
this.isSaveCalled = true;
/* Set up the Serializer and Config Object */
ObjectConverter converter = new ObjectConverter();
CommentedFileConfig config = CommentedFileConfig.builder(configPath).build();
/* Save the config and fire the reload events */
converter.toConfig(conf, config);
config.save();
configReloaded();
this.isSaveCalled = false;
super.registerAndSetup(conf);
}
/**
@@ -92,14 +52,7 @@ public class ModuleConfig {
* @return - Returns the loaded version of the class
*/
public <T> T loadConfig(Object conf) {
/* Set up the Serializer and Config Object */
ObjectConverter converter = new ObjectConverter();
CommentedFileConfig config = CommentedFileConfig.builder(configPath).build();
config.load();
/* Load the config and return the loaded config */
converter.toObject(config, conf);
return (T) conf;
return (T) super.readConfig(conf);
}
/**
@@ -108,31 +61,13 @@ public class ModuleConfig {
* @param conf - The config class to load
*/
public void migrateConfig(ModuleConfig conf) {
/* Set up the Serializer and Config Objects */
CommentedFileConfig config = CommentedFileConfig.builder(configPath).build();
CommentedFileConfig newConfig = CommentedFileConfig.builder(configPath).build();
config.load();
/* Upgrade the config */
new ObjectConverter().toConfig(conf, newConfig);
updateConfigValues(config, newConfig, newConfig, "");
newConfig.save();
config.close();
newConfig.close();
super.migrateConfig(conf);
}
public void updateConfigValues(CommentedConfig oldConfig, CommentedConfig newConfig, CommentedConfig outputConfig, String subKey) {
/* Loop over the config keys and check what has changed */
newConfig.valueMap().forEach((key, value) -> {
String finalKey = subKey + (subKey.isEmpty() ? "" : ".") + key;
if (value instanceof CommentedConfig commentedConfig) {
updateConfigValues(oldConfig, commentedConfig, outputConfig, finalKey);
} else {
outputConfig.set(finalKey,
oldConfig.contains(finalKey) ? oldConfig.get(finalKey) : value);
}
});
if (getConfigFormat() instanceof TomlConfigFormat<?> t) {
t.updateConfigValues(oldConfig, newConfig, outputConfig, subKey);
}
}
/**
@@ -141,7 +76,7 @@ public class ModuleConfig {
* @return - The FILE object containing the config file
*/
public File getConfigPath() {
return configPath;
return super.getConfigPath();
}
/**
@@ -150,12 +85,13 @@ public class ModuleConfig {
* @return - Returns the Sync ID in format modid:config_name
*/
public String getNetworkID() {
return networkID;
return super.getNetworkID();
}
/**
* Fired whenever changes to the config are detected
*/
@Override
public void configReloaded() {
}
@@ -166,7 +102,7 @@ public class ModuleConfig {
* @return
*/
public String getConfigName() {
return configName;
return super.getConfigName();
}
/**
@@ -175,10 +111,10 @@ public class ModuleConfig {
* @return
*/
public String getModId() {
return modId;
return super.getModId();
}
public boolean isSaveCalled() {
return isSaveCalled;
return super.isWasSaveCalled();
}
}

View File

@@ -0,0 +1,32 @@
package com.hypherionmc.craterlib.core.config.formats;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.hypherionmc.moonconfig.core.Config;
import java.io.File;
@RequiredArgsConstructor
@Getter
public abstract class AbstractConfigFormat<S> {
private final File configPath;
private final Runnable onSave;
public void register(S conf) {
if (!configPath.exists() || configPath.length() < 2) {
saveConfig(conf);
} else {
migrateConfig(conf);
}
}
public boolean wasConfigChanged(Config old, Config newConfig) {
return true;
}
public abstract void saveConfig(S config);
public abstract S readConfig(S config);
public abstract void migrateConfig(S config);
}

View File

@@ -0,0 +1,67 @@
package com.hypherionmc.craterlib.core.config.formats;
import me.hypherionmc.moonconfig.core.Config;
import me.hypherionmc.moonconfig.core.conversion.ObjectConverter;
import me.hypherionmc.moonconfig.core.file.FileConfig;
import java.io.File;
public class JsonConfigFormat<S> extends AbstractConfigFormat<S> {
public JsonConfigFormat(File configPath, Runnable afterSave) {
super(configPath, afterSave);
}
@Override
public void saveConfig(S conf) {
ObjectConverter converter = new ObjectConverter();
FileConfig config = FileConfig.builder(getConfigPath()).sync().build();
converter.toConfig(conf, config);
config.save();
getOnSave().run();
}
@Override
public S readConfig(S conf) {
/* Set up the Serializer and Config Object */
ObjectConverter converter = new ObjectConverter();
FileConfig config = FileConfig.builder(getConfigPath()).build();
config.load();
/* Load the config and return the loaded config */
converter.toObject(config, conf);
return conf;
}
@Override
public void migrateConfig(S conf) {
/* Set up the Serializer and Config Objects */
FileConfig config = FileConfig.builder(getConfigPath()).build();
FileConfig newConfig = FileConfig.builder(getConfigPath()).sync().build();
config.load();
/* Upgrade the config */
if (wasConfigChanged(config, newConfig)) {
new ObjectConverter().toConfig(conf, newConfig);
updateConfigValues(config, newConfig, newConfig, "");
newConfig.save();
}
config.close();
newConfig.close();
}
public void updateConfigValues(Config oldConfig, Config newConfig, Config outputConfig, String subKey) {
/* Loop over the config keys and check what has changed */
newConfig.valueMap().forEach((key, value) -> {
String finalKey = subKey + (subKey.isEmpty() ? "" : ".") + key;
if (value instanceof Config commentedConfig) {
updateConfigValues(oldConfig, commentedConfig, outputConfig, finalKey);
} else {
outputConfig.set(finalKey,
oldConfig.contains(finalKey) ? oldConfig.get(finalKey) : value);
}
});
}
}

View File

@@ -0,0 +1,67 @@
package com.hypherionmc.craterlib.core.config.formats;
import me.hypherionmc.moonconfig.core.CommentedConfig;
import me.hypherionmc.moonconfig.core.conversion.ObjectConverter;
import me.hypherionmc.moonconfig.core.file.CommentedFileConfig;
import java.io.File;
public class TomlConfigFormat<S> extends AbstractConfigFormat<S> {
public TomlConfigFormat(File configPath, Runnable onSave) {
super(configPath, onSave);
}
@Override
public void saveConfig(S conf) {
ObjectConverter converter = new ObjectConverter();
CommentedFileConfig config = CommentedFileConfig.builder(getConfigPath()).sync().build();
converter.toConfig(conf, config);
config.save();
getOnSave().run();
}
@Override
public S readConfig(S conf) {
/* Set up the Serializer and Config Object */
ObjectConverter converter = new ObjectConverter();
CommentedFileConfig config = CommentedFileConfig.builder(getConfigPath()).build();
config.load();
/* Load the config and return the loaded config */
converter.toObject(config, conf);
return conf;
}
@Override
public void migrateConfig(S conf) {
/* Set up the Serializer and Config Objects */
CommentedFileConfig config = CommentedFileConfig.builder(getConfigPath()).build();
CommentedFileConfig newConfig = CommentedFileConfig.builder(getConfigPath()).sync().build();
config.load();
/* Upgrade the config */
if (wasConfigChanged(config, newConfig)) {
new ObjectConverter().toConfig(conf, newConfig);
updateConfigValues(config, newConfig, newConfig, "");
newConfig.save();
}
config.close();
newConfig.close();
}
public void updateConfigValues(CommentedConfig oldConfig, CommentedConfig newConfig, CommentedConfig outputConfig, String subKey) {
/* Loop over the config keys and check what has changed */
newConfig.valueMap().forEach((key, value) -> {
String finalKey = subKey + (subKey.isEmpty() ? "" : ".") + key;
if (value instanceof CommentedConfig commentedConfig) {
updateConfigValues(oldConfig, commentedConfig, outputConfig, finalKey);
} else {
outputConfig.set(finalKey,
oldConfig.contains(finalKey) ? oldConfig.get(finalKey) : value);
}
});
}
}

View File

@@ -0,0 +1,27 @@
package com.hypherionmc.craterlib.mixin.events;
import com.hypherionmc.craterlib.api.events.server.ServerStatusEvent;
import com.hypherionmc.craterlib.core.event.CraterEventBus;
import com.hypherionmc.craterlib.nojang.network.protocol.status.WrappedServerStatus;
import net.minecraft.network.protocol.status.ServerStatus;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Optional;
@Mixin(ServerStatus.class)
public class ServerStatusMixin {
@Inject(method = "favicon", at = @At("RETURN"), cancellable = true)
private void injectIconEvent(CallbackInfoReturnable<Optional<ServerStatus.Favicon>> cir) {
ServerStatusEvent.FaviconRequestEvent event = new ServerStatusEvent.FaviconRequestEvent(cir.getReturnValue().isEmpty() ? Optional.empty() : Optional.of(new WrappedServerStatus.WrappedFavicon(cir.getReturnValue().get())));
CraterEventBus.INSTANCE.postEvent(event);
if (event.getNewIcon().isPresent()) {
cir.setReturnValue(Optional.of(event.getNewIcon().get().toMojang()));
}
}
}

View File

@@ -16,6 +16,10 @@ public class BridgedCommandSourceStack {
internal.sendSuccess(() -> ChatUtils.adventureToMojang(supplier.get()), bl);
}
public void sendFailure(Component text) {
internal.sendFailure(ChatUtils.adventureToMojang(text));
}
public CommandSourceStack toMojang() {
return internal;
}

View File

@@ -1,85 +0,0 @@
package com.hypherionmc.craterlib.nojang.commands;
import com.hypherionmc.craterlib.api.commands.CraterCommand;
import com.hypherionmc.craterlib.nojang.authlib.BridgedGameProfile;
import com.hypherionmc.craterlib.nojang.world.entity.player.BridgedPlayer;
import com.hypherionmc.craterlib.utils.TriConsumer;
import com.mojang.authlib.GameProfile;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.GameProfileArgument;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class CommandsRegistry {
public static final CommandsRegistry INSTANCE = new CommandsRegistry();
private final List<CraterCommand> commands = new ArrayList<>();
public void registerCommand(CraterCommand cmd) {
commands.add(cmd);
}
public void registerCommands(CommandDispatcher<CommandSourceStack> stack) {
commands.forEach(cmd -> {
if (cmd.hasArguments()) {
CommandWithArguments.register(cmd, stack);
} else {
CommandWithoutArguments.register(cmd, stack);
}
});
}
static class CommandWithoutArguments {
public static void register(CraterCommand cmd, CommandDispatcher<CommandSourceStack> dispatcher) {
LiteralArgumentBuilder<CommandSourceStack> command = Commands.literal(cmd.getCommandName())
.requires(source -> source.hasPermission(cmd.getPermissionLevel()))
.executes(context -> {
cmd.getExecutor().accept(BridgedCommandSourceStack.of(context.getSource()));
return 1;
});
dispatcher.register(command);
}
}
@SuppressWarnings("unchecked")
static class CommandWithArguments {
public static void register(CraterCommand cmd, CommandDispatcher<CommandSourceStack> dispatcher) {
LiteralArgumentBuilder<CommandSourceStack> command = Commands.literal(cmd.getCommandName())
.requires(source -> source.hasPermission(cmd.getPermissionLevel()));
cmd.getArguments().forEach((key, pair) -> command.then(Commands.argument(key, pair.getLeft()).executes(context -> {
// This is FUCKING UGLY.... Need to improve this in the future
if (pair.getLeft() instanceof GameProfileArgument) {
Collection<GameProfile> profiles = GameProfileArgument.getGameProfiles(context, key);
List<BridgedGameProfile> bridgedGameProfiles = new ArrayList<>();
profiles.forEach(p -> bridgedGameProfiles.add(BridgedGameProfile.of(p)));
((TriConsumer<BridgedPlayer, List<BridgedGameProfile>, BridgedCommandSourceStack>) pair.getRight())
.accept(BridgedPlayer.of(context.getSource().getPlayer()), bridgedGameProfiles, BridgedCommandSourceStack.of(context.getSource()));
return 1;
}
return 1;
})));
dispatcher.register(command);
}
}
}

View File

@@ -0,0 +1,31 @@
package com.hypherionmc.craterlib.nojang.network.protocol.status;
import net.minecraft.network.protocol.status.ServerStatus;
import org.jetbrains.annotations.ApiStatus;
public final class WrappedServerStatus {
public static final class WrappedFavicon {
private final ServerStatus.Favicon internal;
public WrappedFavicon(byte[] iconBytes) {
internal = new ServerStatus.Favicon(iconBytes);
}
@ApiStatus.Internal
public WrappedFavicon(ServerStatus.Favicon internal) {
this.internal = internal;
}
public byte[] iconBytes() {
return internal.iconBytes();
}
public ServerStatus.Favicon toMojang() {
return internal;
}
}
}

View File

@@ -57,6 +57,11 @@ public class BridgedPlayer {
return null;
}
public void disconnect(Component message) {
if (isServerPlayer())
toMojangServerPlayer().connection.disconnect(ChatUtils.adventureToMojang(message));
}
public ServerPlayer toMojangServerPlayer() {
return (ServerPlayer) internal;
}

View File

@@ -108,4 +108,11 @@ public class ChatUtils {
return mojangToAdventure(Component.translatable(Util.makeDescriptionId("biome", identifier.toMojang())));
}
public static net.kyori.adventure.text.Component format(String value) {
return net.kyori.adventure.text.Component.translatable(convertFormattingCodes(value));
}
private static String convertFormattingCodes(String input) {
return input.replaceAll("§([0-9a-fklmnor])", "\u00A7$1");
}
}

View File

@@ -16,7 +16,8 @@
"events.CommandMixin",
"events.PlayerAdvancementsMixin",
"events.PlayerListMixin",
"events.ServerPlayerMixin"
"events.ServerPlayerMixin",
"events.ServerStatusMixin"
],
"injectors": {
"defaultRequire": 1

View File

@@ -113,8 +113,8 @@ publisher {
setVersionType("release")
setChangelog("https://raw.githubusercontent.com/hypherionmc/changelogs/main/craterlib/changelog-fabric.md")
setProjectVersion("${minecraft_version}-${project.version}")
setDisplayName("[FABRIC/QUILT 1.21.0] CraterLib - ${project.version}")
setGameVersions("1.21")
setDisplayName("[FABRIC/QUILT 1.21.x] CraterLib - ${project.version}")
setGameVersions("1.21", "1.21.1")
setLoaders("fabric", "quilt")
setArtifact(remapJar)
setCurseEnvironment("both")

View File

@@ -9,7 +9,6 @@ import com.hypherionmc.craterlib.core.networking.CraterPacketNetwork;
import com.hypherionmc.craterlib.core.networking.data.PacketSide;
import com.hypherionmc.craterlib.core.platform.ModloaderEnvironment;
import com.hypherionmc.craterlib.network.CraterFabricNetworkHandler;
import com.hypherionmc.craterlib.nojang.commands.CommandsRegistry;
import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
@@ -21,8 +20,7 @@ public class CraterLibInitializer implements ModInitializer {
public void onInitialize() {
new CraterPacketNetwork(new CraterFabricNetworkHandler(PacketSide.SERVER));
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
CraterEventBus.INSTANCE.postEvent(new CraterRegisterCommandEvent());
CommandsRegistry.INSTANCE.registerCommands(dispatcher);
CraterEventBus.INSTANCE.postEvent(new CraterRegisterCommandEvent(dispatcher));
});

View File

@@ -2,7 +2,6 @@ package com.hypherionmc.craterlib;
import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen;
import com.hypherionmc.craterlib.core.config.ConfigController;
import com.hypherionmc.craterlib.core.config.ModuleConfig;
import com.hypherionmc.craterlib.core.config.annotations.NoConfigScreen;
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
import com.terraformersmc.modmenu.api.ModMenuApi;
@@ -19,9 +18,9 @@ public class CraterLibModMenuIntegration implements ModMenuApi {
public Map<String, ConfigScreenFactory<?>> getProvidedConfigScreenFactories() {
Map<String, ConfigScreenFactory<?>> configScreens = new HashMap<>();
ConfigController.getMonitoredConfigs().forEach((conf, watcher) -> {
ConfigController.getWatchedConfigs().forEach((conf, watcher) -> {
if (!conf.getClass().isAnnotationPresent(NoConfigScreen.class)) {
configScreens.put(((ModuleConfig) conf).getModId(), screen -> new CraterConfigScreen((ModuleConfig) conf, screen));
configScreens.put(watcher.getLeft().getModId(), screen -> new CraterConfigScreen(watcher.getLeft(), screen));
}
});

View File

@@ -27,8 +27,8 @@ public class ServerGamePacketListenerImplMixin {
cancellable = true
)
private void injectChatEvent(PlayerChatMessage arg, Component arg2, FilteredText arg3, CallbackInfo ci) {
Component finalArg = arg2 == null ? arg.decoratedContent() : arg2;
CraterServerChatEvent event = new CraterServerChatEvent(BridgedPlayer.of(this.player), finalArg.getString(), ChatUtils.mojangToAdventure(finalArg));
Component finalcomp = arg2 == null ? arg.decoratedContent() : arg2;
CraterServerChatEvent event = new CraterServerChatEvent(BridgedPlayer.of(this.player), finalcomp.getString(), ChatUtils.mojangToAdventure(finalcomp));
CraterEventBus.INSTANCE.postEvent(event);
if (event.wasCancelled())
ci.cancel();

View File

@@ -0,0 +1,52 @@
package com.hypherionmc.craterlib.mixin;
import com.hypherionmc.craterlib.api.events.server.ServerStatusEvent;
import com.hypherionmc.craterlib.core.event.CraterEventBus;
import com.hypherionmc.craterlib.utils.ChatUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.protocol.status.ClientboundStatusResponsePacket;
import net.minecraft.network.protocol.status.ServerStatus;
import net.minecraft.network.protocol.status.ServerboundStatusRequestPacket;
import net.minecraft.server.network.ServerStatusPacketListenerImpl;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ServerStatusPacketListenerImpl.class)
public class ServerStatusPacketListenerMixin {
@Shadow
@Final
private ServerStatus status;
@Shadow @Final private Connection connection;
@Inject(method = "handleStatusRequest",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/network/Connection;send(Lnet/minecraft/network/protocol/Packet;)V",
shift = At.Shift.BEFORE),
cancellable = true
)
private void injectHandleStatusRequest(ServerboundStatusRequestPacket arg, CallbackInfo ci) {
ServerStatusEvent.StatusRequestEvent event = new ServerStatusEvent.StatusRequestEvent(ChatUtils.mojangToAdventure(status.description()));
CraterEventBus.INSTANCE.postEvent(event);
if (event.getNewStatus() != null) {
ci.cancel();
this.connection.send(new ClientboundStatusResponsePacket(
new ServerStatus(ChatUtils.adventureToMojang(
event.getNewStatus()),
status.players(),
status.version(),
status.favicon(),
status.enforcesSecureChat()
)
));
}
}
}

View File

@@ -9,7 +9,8 @@
"TutorialMixin"
],
"server": [
"ServerGamePacketListenerImplMixin"
"ServerGamePacketListenerImplMixin",
"ServerStatusPacketListenerMixin"
],
"injectors": {
"defaultRequire": 1

View File

@@ -27,8 +27,8 @@ public class ServerGamePacketListenerImplMixin {
cancellable = true
)
private void injectChatEvent(Component component, PlayerChatMessage arg, FilteredText p_296589_, CallbackInfo ci) {
Component finalArg = component == null ? arg.decoratedContent() : component;
CraterServerChatEvent event = new CraterServerChatEvent(BridgedPlayer.of(this.player), finalArg.getString(), ChatUtils.mojangToAdventure(finalArg));
Component finalcomp = component == null ? arg.decoratedContent() : component;
CraterServerChatEvent event = new CraterServerChatEvent(BridgedPlayer.of(this.player), finalcomp.getString(), ChatUtils.mojangToAdventure(finalcomp));
CraterEventBus.INSTANCE.postEvent(event);
if (event.wasCancelled())
ci.cancel();

View File

@@ -0,0 +1,53 @@
package com.hypherionmc.craterlib.mixin;
import com.hypherionmc.craterlib.api.events.server.ServerStatusEvent;
import com.hypherionmc.craterlib.core.event.CraterEventBus;
import com.hypherionmc.craterlib.utils.ChatUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.protocol.status.ClientboundStatusResponsePacket;
import net.minecraft.network.protocol.status.ServerStatus;
import net.minecraft.network.protocol.status.ServerboundStatusRequestPacket;
import net.minecraft.server.network.ServerStatusPacketListenerImpl;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ServerStatusPacketListenerImpl.class)
public class ServerStatusPacketListenerMixin {
@Shadow
@Final
private ServerStatus status;
@Shadow @Final private Connection connection;
@Inject(method = "handleStatusRequest",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/network/Connection;send(Lnet/minecraft/network/protocol/Packet;)V",
shift = At.Shift.BEFORE),
cancellable = true
)
private void injectHandleStatusRequest(ServerboundStatusRequestPacket arg, CallbackInfo ci) {
ServerStatusEvent.StatusRequestEvent event = new ServerStatusEvent.StatusRequestEvent(ChatUtils.mojangToAdventure(status.description()));
CraterEventBus.INSTANCE.postEvent(event);
if (event.getNewStatus() != null) {
ci.cancel();
this.connection.send(new ClientboundStatusResponsePacket(
new ServerStatus(ChatUtils.adventureToMojang(
event.getNewStatus()),
status.players(),
status.version(),
status.favicon(),
status.enforcesSecureChat(),
status.isModded()
)
));
}
}
}

View File

@@ -9,7 +9,8 @@
"ConfigScreenHandlerMixin"
],
"server": [
"ServerGamePacketListenerImplMixin"
"ServerGamePacketListenerImplMixin",
"ServerStatusPacketListenerMixin"
],
"injectors": {
"defaultRequire": 1

View File

@@ -106,8 +106,8 @@ publisher {
setVersionType("release")
setChangelog("https://raw.githubusercontent.com/hypherionmc/changelogs/main/craterlib/changelog-forge.md")
setProjectVersion("${minecraft_version}-${project.version}")
setDisplayName("[NeoForge 1.21.0] CraterLib - ${project.version}")
setGameVersions("1.21")
setDisplayName("[NeoForge 1.21.x] CraterLib - ${project.version}")
setGameVersions("1.21", "1.21.1")
setLoaders("neoforge")
setArtifact(remapJar)
setCurseEnvironment("both")

View File

@@ -2,8 +2,8 @@ package com.hypherionmc.craterlib.client;
import com.hypherionmc.craterlib.api.events.client.LateInitEvent;
import com.hypherionmc.craterlib.client.gui.config.CraterConfigScreen;
import com.hypherionmc.craterlib.core.config.AbstractConfig;
import com.hypherionmc.craterlib.core.config.ConfigController;
import com.hypherionmc.craterlib.core.config.ModuleConfig;
import com.hypherionmc.craterlib.core.config.annotations.NoConfigScreen;
import com.hypherionmc.craterlib.core.event.CraterEventBus;
import com.hypherionmc.craterlib.core.platform.ClientPlatform;
@@ -51,9 +51,9 @@ public class NeoForgeClientHelper implements ClientPlatform {
LateInitEvent event = new LateInitEvent(new BridgedMinecraft(), BridgedOptions.of(Minecraft.getInstance().options));
CraterEventBus.INSTANCE.postEvent(event);
ConfigController.getMonitoredConfigs().forEach((conf, watcher) -> {
ConfigController.getWatchedConfigs().forEach((conf, watcher) -> {
if (!conf.getClass().isAnnotationPresent(NoConfigScreen.class)) {
ModuleConfig config = (ModuleConfig) conf;
AbstractConfig config = watcher.getLeft();
ModList.get().getModContainerById(config.getModId()).ifPresent(c -> c.registerExtensionPoint(IConfigScreenFactory.class, ((minecraft, screen) -> new CraterConfigScreen(config, screen))));
}
});

View File

@@ -3,7 +3,6 @@ package com.hypherionmc.craterlib.common;
import com.hypherionmc.craterlib.api.events.server.CraterRegisterCommandEvent;
import com.hypherionmc.craterlib.api.events.server.CraterServerLifecycleEvent;
import com.hypherionmc.craterlib.core.event.CraterEventBus;
import com.hypherionmc.craterlib.nojang.commands.CommandsRegistry;
import com.hypherionmc.craterlib.nojang.server.BridgedMinecraftServer;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.neoforge.event.RegisterCommandsEvent;
@@ -36,8 +35,7 @@ public class NeoForgeServerEvents {
@SubscribeEvent
public void onCommandRegister(RegisterCommandsEvent event) {
CraterEventBus.INSTANCE.postEvent(new CraterRegisterCommandEvent());
CommandsRegistry.INSTANCE.registerCommands(event.getDispatcher());
CraterEventBus.INSTANCE.postEvent(new CraterRegisterCommandEvent(event.getDispatcher()));
}
}

View File

@@ -27,8 +27,8 @@ public class ServerGamePacketListenerImplMixin {
cancellable = true
)
private void injectChatEvent(Component component, PlayerChatMessage arg, FilteredText p_296589_, CallbackInfo ci) {
Component finalArg = component == null ? arg.decoratedContent() : component;
CraterServerChatEvent event = new CraterServerChatEvent(BridgedPlayer.of(this.player), finalArg.getString(), ChatUtils.mojangToAdventure(finalArg));
Component finalcomp = component == null ? arg.decoratedContent() : component;
CraterServerChatEvent event = new CraterServerChatEvent(BridgedPlayer.of(this.player), finalcomp.getString(), ChatUtils.mojangToAdventure(finalcomp));
CraterEventBus.INSTANCE.postEvent(event);
if (event.wasCancelled())
ci.cancel();

View File

@@ -0,0 +1,53 @@
package com.hypherionmc.craterlib.mixin;
import com.hypherionmc.craterlib.api.events.server.ServerStatusEvent;
import com.hypherionmc.craterlib.core.event.CraterEventBus;
import com.hypherionmc.craterlib.utils.ChatUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.protocol.status.ClientboundStatusResponsePacket;
import net.minecraft.network.protocol.status.ServerStatus;
import net.minecraft.network.protocol.status.ServerboundStatusRequestPacket;
import net.minecraft.server.network.ServerStatusPacketListenerImpl;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ServerStatusPacketListenerImpl.class)
public class ServerStatusPacketListenerMixin {
@Shadow
@Final
private ServerStatus status;
@Shadow @Final private Connection connection;
@Inject(method = "handleStatusRequest",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/network/Connection;send(Lnet/minecraft/network/protocol/Packet;)V",
shift = At.Shift.BEFORE),
cancellable = true
)
private void injectHandleStatusRequest(ServerboundStatusRequestPacket arg, CallbackInfo ci) {
ServerStatusEvent.StatusRequestEvent event = new ServerStatusEvent.StatusRequestEvent(ChatUtils.mojangToAdventure(status.description()));
CraterEventBus.INSTANCE.postEvent(event);
if (event.getNewStatus() != null) {
ci.cancel();
this.connection.send(new ClientboundStatusResponsePacket(
new ServerStatus(ChatUtils.adventureToMojang(
event.getNewStatus()),
status.players(),
status.version(),
status.favicon(),
status.enforcesSecureChat(),
status.isModded()
)
));
}
}
}

View File

@@ -7,7 +7,8 @@
],
"client": [],
"server": [
"ServerGamePacketListenerImplMixin"
"ServerGamePacketListenerImplMixin",
"ServerStatusPacketListenerMixin"
],
"injectors": {
"defaultRequire": 1

View File

@@ -16,7 +16,7 @@ A Library mod and modding api for easier multi-version minecraft and mod loader
| 1.18.2-1.20.2 | |
| 1.20.4 | |
| 1.20.6 | |
| 1.21 | |
| 1.21.x | |
- - Not Supported; no bug fixes or new features.
- 🚧 - Work in Progress; not ready for release.

View File

@@ -57,11 +57,13 @@ subprojects {
// All Projects
shade "me.hypherionmc.moon-config:core:${moon_config}"
shade "me.hypherionmc.moon-config:toml:${moon_config}"
shade "me.hypherionmc.moon-config:json:${moon_config}"
shade "com.hypherionmc:rpcsdk:${rpc_sdk}"
shade "me.hypherionmc.sdlink:mcdiscordformatter-1.20.3:${discord_formatter}"
shade "net.kyori:adventure-api:${adventure}"
shade "net.kyori:adventure-text-serializer-gson:${adventure}"
compileOnly 'net.luckperms:api:5.4'
compileOnly("org.projectlombok:lombok:${lombok}")
annotationProcessor("org.projectlombok:lombok:${lombok}")
}

View File

@@ -1,7 +1,7 @@
#Project
version_major=2
version_minor=0
version_patch=3
version_minor=1
version_patch=0
version_build=0
#Mod