Split commands into classes

This commit is contained in:
s809
2024-10-20 00:26:24 +05:00
parent 1df5dd5458
commit 69ffd795c9
5 changed files with 310 additions and 160 deletions
@@ -1,29 +1,20 @@
package com.seibel.distanthorizons.common;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.*;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiAfterDhInitEvent;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBeforeDhInitEvent;
import com.seibel.distanthorizons.common.commands.CommandInitializer;
import com.seibel.distanthorizons.common.wrappers.DependencySetup;
import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftServerWrapper;
import com.seibel.distanthorizons.common.wrappers.misc.ServerPlayerWrapper;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.ConfigBase;
import com.seibel.distanthorizons.core.config.eventHandlers.presets.ThreadPresetConfigEventHandler;
import com.seibel.distanthorizons.core.config.types.AbstractConfigType;
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.jar.ModJarInfo;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.messages.base.CodecCrashMessage;
import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.core.world.DhServerWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IModAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IModChecker;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
@@ -34,25 +25,9 @@ import net.minecraft.server.dedicated.DedicatedServer;
import org.apache.logging.log4j.Logger;
import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import static com.mojang.brigadier.arguments.DoubleArgumentType.doubleArg;
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
import static com.seibel.distanthorizons.core.network.messages.MessageRegistry.DEBUG_CODEC_CRASH_MESSAGE;
import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal;
#if MC_VER >= MC_1_19_2
import net.minecraft.network.chat.Component;
#else // < 1.19.2
import net.minecraft.network.chat.TranslatableComponent;
#endif
/**
* Base for all mod loader initializers
* and handles most setup.
@@ -61,7 +36,7 @@ public abstract class AbstractModInitializer
{
protected static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
private CommandDispatcher<CommandSourceStack> commandDispatcher;
private CommandInitializer commandInitializer;
@@ -132,7 +107,7 @@ public abstract class AbstractModInitializer
LOGGER.info(ModInfo.READABLE_NAME + " server Initialized.");
ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterDhInitEvent.class, null);
this.subscribeRegisterCommandsEvent(dispatcher -> { this.commandDispatcher = dispatcher; });
this.subscribeRegisterCommandsEvent(dispatcher -> { this.commandInitializer = new CommandInitializer(dispatcher); });
this.subscribeServerStartingEvent(server ->
{
@@ -140,7 +115,7 @@ public abstract class AbstractModInitializer
this.initConfig();
this.postInit();
this.initCommands();
this.commandInitializer.initCommands();
LOGGER.info("Dedicated server initialized at " + server.getServerDirectory());
});
@@ -196,137 +171,6 @@ public abstract class AbstractModInitializer
LOGGER.info("Mod Post-Initialized");
}
@SuppressWarnings({"rawtypes", "unchecked"})
private void initCommands()
{
LiteralArgumentBuilder<CommandSourceStack> builder = literal("dhconfig")
.requires(source -> source.hasPermission(4));
for (AbstractConfigType<?, ?> type : ConfigBase.INSTANCE.entries)
{
if (!(type instanceof ConfigEntry))
{
continue;
}
//noinspection PatternVariableCanBeUsed
ConfigEntry configEntry = (ConfigEntry) type;
if (configEntry.getServersideShortName() == null)
{
continue;
}
Function<
Function<CommandContext<CommandSourceStack>, Object>,
Command<CommandSourceStack>
> makeConfigUpdater = (getter) -> (commandContext) -> {
Object value = getter.apply(commandContext);
commandContext.getSource().sendSuccess(
#if MC_VER >= MC_1_20_1
() -> Component.literal("Changed the value of "+configEntry.getServersideShortName()+" to "+value),
#elif MC_VER >= MC_1_19_2
Component.literal("Changed the value of "+configEntry.getServersideShortName()+" to "+value),
#else
new TranslatableComponent("Changed the value of "+configEntry.getServersideShortName()+" to "+value),
#endif
true);
configEntry.set(value);
return 1;
};
LiteralArgumentBuilder<CommandSourceStack> subcommand = literal(configEntry.getServersideShortName())
.executes((commandContext) -> {
#if MC_VER >= MC_1_20_1
commandContext.getSource().sendSuccess(() -> Component.literal("Current value of "+configEntry.getServersideShortName()+" is "+configEntry.get()), true);
#elif MC_VER >= MC_1_19_2
commandContext.getSource().sendSuccess(Component.literal("Current value of "+configEntry.getServersideShortName()+" is "+configEntry.get()), true);
#else // < 1.19.2
commandContext.getSource().sendSuccess(new TranslatableComponent("Current value of "+configEntry.getServersideShortName()+" is "+configEntry.get()), true);
#endif
return 1;
});
if (Enum.class.isAssignableFrom(configEntry.getType()))
{
for (Object choice : configEntry.getType().getEnumConstants())
{
subcommand.then(
literal(choice.toString())
.executes(makeConfigUpdater.apply(c -> choice))
);
}
}
else
{
boolean setterAdded = false;
for (java.util.Map.Entry<Class<?>, Pair<Supplier<ArgumentType<?>>, BiFunction<CommandContext<?>, String, ?>>> pair : new HashMap<
Class<?>,
Pair<
Supplier<ArgumentType<?>>,
BiFunction<CommandContext<?>, String, ?>>
>() {{
this.put(Integer.class, new Pair<>(() -> integer((int) configEntry.getMin(), (int) configEntry.getMax()), IntegerArgumentType::getInteger));
this.put(Double.class, new Pair<>(() -> doubleArg((double) configEntry.getMin(), (double) configEntry.getMax()), DoubleArgumentType::getDouble));
this.put(Boolean.class, new Pair<>(BoolArgumentType::bool, BoolArgumentType::getBool));
this.put(String.class, new Pair<>(StringArgumentType::string, StringArgumentType::getString));
}}.entrySet())
{
if (!pair.getKey().isAssignableFrom(configEntry.getType()))
{
continue;
}
subcommand.then(argument("value", pair.getValue().first.get())
.executes(makeConfigUpdater.apply(c -> pair.getValue().second.apply(c, "value"))));
setterAdded = true;
break;
}
if (!setterAdded)
{
throw new RuntimeException("Config type of "+type.getName()+" is not supported: "+configEntry.getType().getSimpleName());
}
}
builder.then(subcommand);
}
this.commandDispatcher.register(builder);
if (DEBUG_CODEC_CRASH_MESSAGE)
{
LiteralArgumentBuilder<CommandSourceStack> dhcrash = literal("dhcrash")
.requires(source -> source.hasPermission(4))
.then(literal("encode")
.executes(c -> {
assert SharedApi.getIDhServerWorld() != null;
((DhServerWorld) SharedApi.getIDhServerWorld()).getServerPlayerStateManager()
#if MC_VER >= MC_1_19_2
.getConnectedPlayer(ServerPlayerWrapper.getWrapper(Objects.requireNonNull(c.getSource().getPlayer())))
#else
.getConnectedPlayer(ServerPlayerWrapper.getWrapper(Objects.requireNonNull(c.getSource().getPlayerOrException())))
#endif
.networkSession.sendMessage(new CodecCrashMessage(CodecCrashMessage.ECrashPhase.ENCODE));
return 1;
}))
.then(literal("decode")
.executes(c -> {
assert SharedApi.getIDhServerWorld() != null;
((DhServerWorld) SharedApi.getIDhServerWorld()).getServerPlayerStateManager()
#if MC_VER >= MC_1_19_2
.getConnectedPlayer(ServerPlayerWrapper.getWrapper(Objects.requireNonNull(c.getSource().getPlayer())))
#else
.getConnectedPlayer(ServerPlayerWrapper.getWrapper(Objects.requireNonNull(c.getSource().getPlayerOrException())))
#endif
.networkSession.sendMessage(new CodecCrashMessage(CodecCrashMessage.ECrashPhase.DECODE));
return 1;
}));
this.commandDispatcher.register(dhcrash);
}
}
//==================================//
@@ -0,0 +1,79 @@
package com.seibel.distanthorizons.common.commands;
import com.mojang.brigadier.context.CommandContext;
import com.seibel.distanthorizons.common.wrappers.misc.ServerPlayerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import net.minecraft.commands.CommandSourceStack;
#if MC_VER >= MC_1_19_2
import net.minecraft.network.chat.Component;
import java.util.Objects;
#else // < 1.19.2
import net.minecraft.network.chat.TranslatableComponent;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
#endif
/**
* Abstract class providing common functionality for DH's commands.
*/
public abstract class AbstractCommand
{
/**
* Sends a success response to the player with the given text.
*
* @param commandContext The command context to send the response to.
* @param text The text to display in the success message.
* @return 1, indicating that the command was successful.
*/
protected int sendSuccessResponse(CommandContext<CommandSourceStack> commandContext, String text)
{
#if MC_VER >= MC_1_20_1
commandContext.getSource().sendSuccess(() -> Component.literal(text), true);
#elif MC_VER >= MC_1_19_2
commandContext.getSource().sendSuccess(Component.literal(text), true);
#else
commandContext.getSource().sendSuccess(new TranslatableComponent(text), true);
#endif
return 1;
}
/**
* Gets the server player from a command context.
*
* @param commandContext The command context to get the server player from.
* @return The server player wrapper for the player who sent the command.
*/
protected IServerPlayerWrapper getSourcePlayer(CommandContext<CommandSourceStack> commandContext) #if MC_VER < MC_1_19_2 throws CommandSyntaxException #endif
{
#if MC_VER >= MC_1_19_2
return ServerPlayerWrapper.getWrapper(Objects.requireNonNull(commandContext.getSource().getPlayer()));
#else
return ServerPlayerWrapper.getWrapper(commandContext.getSource().getPlayerOrException());
#endif
}
/**
* Checks if the source of a command is a player.
*
* @param source The source of the command to check.
* @return True if the source is a player, false otherwise.
*/
protected boolean isPlayerSource(CommandSourceStack source)
{
#if MC_VER >= MC_1_19_2
return source.isPlayer();
#else
try
{
source.getPlayerOrException();
return true;
}
catch (CommandSyntaxException e)
{
return false;
}
#endif
}
}
@@ -0,0 +1,40 @@
package com.seibel.distanthorizons.common.commands;
import com.mojang.brigadier.CommandDispatcher;
import net.minecraft.commands.CommandSourceStack;
import static com.seibel.distanthorizons.core.network.messages.MessageRegistry.DEBUG_CODEC_CRASH_MESSAGE;
/**
* Initializes commands of the mod.
*/
public class CommandInitializer
{
private final CommandDispatcher<CommandSourceStack> commandDispatcher;
/**
* Constructs a new instance of this class.
*
* @param commandDispatcher The dispatcher to use for registering commands.
*/
public CommandInitializer(CommandDispatcher<CommandSourceStack> commandDispatcher)
{
this.commandDispatcher = commandDispatcher;
}
/**
* Initializes all available commands.
*/
public void initCommands()
{
new DhConfigCommand().register(this.commandDispatcher);
if (DEBUG_CODEC_CRASH_MESSAGE)
{
new DhCrashCommand().register(this.commandDispatcher);
}
}
}
@@ -0,0 +1,142 @@
package com.seibel.distanthorizons.common.commands;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.*;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.seibel.distanthorizons.core.config.ConfigBase;
import com.seibel.distanthorizons.core.config.types.AbstractConfigType;
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
import net.minecraft.commands.CommandSourceStack;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.ToIntBiFunction;
import static com.mojang.brigadier.arguments.DoubleArgumentType.doubleArg;
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal;
/**
* Command for managing config.
*/
public class DhConfigCommand extends AbstractCommand
{
private static final List<CommandArgumentData<?>> commandArguments = Arrays.asList(
new CommandArgumentData<>(Integer.class, configEntry -> integer(configEntry.getMin(), configEntry.getMax()), IntegerArgumentType::getInteger),
new CommandArgumentData<>(Double.class, configEntry -> doubleArg(configEntry.getMin(), configEntry.getMax()), DoubleArgumentType::getDouble),
new CommandArgumentData<>(Boolean.class, BoolArgumentType::bool, BoolArgumentType::getBool),
new CommandArgumentData<>(String.class, StringArgumentType::string, StringArgumentType::getString)
);
/**
* Registers the command with the given dispatcher.
*
* @param commandDispatcher the dispatcher to register the command with.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public void register(CommandDispatcher<CommandSourceStack> commandDispatcher)
{
LiteralArgumentBuilder<CommandSourceStack> builder = literal("dhconfig")
.requires(source -> source.hasPermission(4));
for (AbstractConfigType<?, ?> type : ConfigBase.INSTANCE.entries)
{
// Skip non-config entries
if (!(type instanceof ConfigEntry))
{
continue;
}
//noinspection PatternVariableCanBeUsed
ConfigEntry configEntry = (ConfigEntry) type;
if (configEntry.getServersideShortName() == null)
{
continue;
}
LiteralArgumentBuilder<CommandSourceStack> subcommand = literal(configEntry.getServersideShortName())
.executes(commandContext -> this.sendSuccessResponse(commandContext, "Current value of ${configEntry.getServersideShortName()} is ${configEntry.get()}"));
ToIntBiFunction<CommandContext<CommandSourceStack>, Object> updateConfigValue = (commandContext, value) -> {
configEntry.set(value);
return this.sendSuccessResponse(commandContext, "Changed the value of ${configEntry.getServersideShortName()} to $value");
};
// Enum type needs a special case since enums aren't represented by existing argument type
// and need literals for each individual value
if (Enum.class.isAssignableFrom(configEntry.getType()))
{
for (Object choice : configEntry.getType().getEnumConstants())
{
subcommand.then(
literal(choice.toString())
.executes(c -> updateConfigValue.applyAsInt(c, choice))
);
}
}
else
{
boolean setterAdded = false;
for (CommandArgumentData<?> commandArgumentData : commandArguments)
{
if (!commandArgumentData.argumentClass.isAssignableFrom(configEntry.getType()))
{
continue;
}
subcommand.then(argument("value", commandArgumentData.getArgumentType(configEntry))
.executes(c -> updateConfigValue.applyAsInt(c, commandArgumentData.getValue(c, "value"))));
setterAdded = true;
break;
}
if (!setterAdded)
{
throw new RuntimeException("Config type of " + type.getName() + " is not supported: " + configEntry.getType().getSimpleName());
}
}
builder.then(subcommand);
}
commandDispatcher.register(builder);
}
private static class CommandArgumentData<T>
{
public final Class<T> argumentClass;
public final Function<ConfigEntry<T>, ArgumentType<T>> argumentTypeFunction;
private final BiFunction<CommandContext<CommandSourceStack>, String, T> valueGetter;
public CommandArgumentData(Class<T> argumentClass, Supplier<ArgumentType<T>> argumentTypeSupplier, BiFunction<CommandContext<CommandSourceStack>, String, T> valueGetter)
{
this(argumentClass, configEntry -> argumentTypeSupplier.get(), valueGetter);
}
public CommandArgumentData(Class<T> argumentClass, Function<ConfigEntry<T>, ArgumentType<T>> argumentTypeFunction, BiFunction<CommandContext<CommandSourceStack>, String, T> valueGetter)
{
this.argumentClass = argumentClass;
this.argumentTypeFunction = argumentTypeFunction;
this.valueGetter = valueGetter;
}
public ArgumentType<T> getArgumentType(ConfigEntry<T> configEntry)
{
return this.argumentTypeFunction.apply(configEntry);
}
public T getValue(CommandContext<CommandSourceStack> commandContext, String argumentName)
{
return this.valueGetter.apply(commandContext, argumentName);
}
}
}
@@ -0,0 +1,45 @@
package com.seibel.distanthorizons.common.commands;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerState;
import com.seibel.distanthorizons.core.network.messages.base.CodecCrashMessage;
import net.minecraft.commands.CommandSourceStack;
import static net.minecraft.commands.Commands.literal;
public class DhCrashCommand extends AbstractCommand
{
public void register(CommandDispatcher<CommandSourceStack> commandDispatcher)
{
LiteralArgumentBuilder<CommandSourceStack> dhcrash = literal("dhcrash")
.requires(source -> this.isPlayerSource(source) && source.hasPermission(4))
.then(literal("encode")
.executes(c -> {
assert SharedApi.getIDhServerWorld() != null;
ServerPlayerState serverPlayerState = SharedApi.getIDhServerWorld().getServerPlayerStateManager()
.getConnectedPlayer(this.getSourcePlayer(c));
if (serverPlayerState != null)
{
serverPlayerState.networkSession.sendMessage(new CodecCrashMessage(CodecCrashMessage.ECrashPhase.ENCODE));
}
return 1;
}))
.then(literal("decode")
.executes(c -> {
assert SharedApi.getIDhServerWorld() != null;
ServerPlayerState serverPlayerState = SharedApi.getIDhServerWorld().getServerPlayerStateManager()
.getConnectedPlayer(this.getSourcePlayer(c));
if (serverPlayerState != null)
{
serverPlayerState.networkSession.sendMessage(new CodecCrashMessage(CodecCrashMessage.ECrashPhase.DECODE));
}
return 1;
}));
commandDispatcher.register(dhcrash);
}
}