diff --git a/src/main/java/dev/anvilcraft/rg/RollingGateCategories.java b/src/main/java/dev/anvilcraft/rg/RollingGateCategories.java index 0d7faca..8d35aa2 100644 --- a/src/main/java/dev/anvilcraft/rg/RollingGateCategories.java +++ b/src/main/java/dev/anvilcraft/rg/RollingGateCategories.java @@ -2,5 +2,6 @@ public class RollingGateCategories { public static final String BASE = "base"; + public static final String CREATIVE = "creative"; public static final String DISABLED = "disabled"; } diff --git a/src/main/java/dev/anvilcraft/rg/RollingGateServerRules.java b/src/main/java/dev/anvilcraft/rg/RollingGateServerRules.java index 5967223..0a02869 100644 --- a/src/main/java/dev/anvilcraft/rg/RollingGateServerRules.java +++ b/src/main/java/dev/anvilcraft/rg/RollingGateServerRules.java @@ -1,8 +1,26 @@ package dev.anvilcraft.rg; +import dev.anvilcraft.rg.api.RGValidator; import dev.anvilcraft.rg.api.Rule; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; public class RollingGateServerRules { - @Rule(allowed = {"zh_cn", "en_us"}, categories = {RollingGateCategories.BASE}) + @Rule(allowed = {"zh_cn", "en_us"}, categories = RollingGateCategories.BASE) public static String language = "zh_cn"; + + public static class ViewDistanceValidator extends RGValidator.IntegerValidator { + @Override + public @NotNull Map.Entry getRange() { + return Map.entry(0, 32); + } + } + + @Rule( + allowed = {"0", "12", "16", "32"}, + categories = RollingGateCategories.CREATIVE, + validator = ViewDistanceValidator.class + ) + public static int viewDistance = 0; } diff --git a/src/main/java/dev/anvilcraft/rg/api/RGRule.java b/src/main/java/dev/anvilcraft/rg/api/RGRule.java index b5d834e..ac3f3fb 100644 --- a/src/main/java/dev/anvilcraft/rg/api/RGRule.java +++ b/src/main/java/dev/anvilcraft/rg/api/RGRule.java @@ -2,6 +2,9 @@ import com.google.gson.JsonElement; import dev.anvilcraft.rg.RollingGate; +import dev.anvilcraft.rg.api.event.RGRuleChangeEvent; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.server.ServerLifecycleHooks; import org.jetbrains.annotations.NotNull; import java.lang.reflect.Field; @@ -153,7 +156,15 @@ public void setFieldValue(String value) { throw new RGRuleException("Illegal value: %s, reason: %s", value, validator.reason()); } } - this.field.set(null, this.codec.decode(value)); + RGRuleChangeEvent event; + if (this.environment().isServer()) { + event = new RGRuleChangeEvent.Server<>(this, this.getValue(), this.codec.decode(value), ServerLifecycleHooks.getCurrentServer()); + } else { + event = new RGRuleChangeEvent.Client<>(this, this.getValue(), this.codec.decode(value)); + } + NeoForge.EVENT_BUS.post(event); + if (event.isCanceled()) return; + this.field.set(null, event.getNewValue()); } catch (IllegalAccessException e) { throw new RGRuleException("Illegal value: %s", value); } diff --git a/src/main/java/dev/anvilcraft/rg/api/RGValidator.java b/src/main/java/dev/anvilcraft/rg/api/RGValidator.java index b16627b..b67250d 100644 --- a/src/main/java/dev/anvilcraft/rg/api/RGValidator.java +++ b/src/main/java/dev/anvilcraft/rg/api/RGValidator.java @@ -2,10 +2,7 @@ import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; +import java.util.Map; @SuppressWarnings("unused") public interface RGValidator { @@ -39,188 +36,60 @@ public String reason() { } } - abstract class ValidatorFactory { - public abstract Class> get(); + abstract class NumberValidator implements RGValidator { + public abstract @NotNull Map.Entry getRange(); - - private static class ByteValidatorFactory extends ValidatorFactory { - private final byte min; - private final byte max; - - private ByteValidatorFactory(byte min, byte max) { - this.min = min; - this.max = max; - } - - public Class> get() { - return RangeValidator.class; - } - - public class RangeValidator implements RGValidator { - @Override - public boolean validate(@NotNull Byte oldValue, @NotNull String newValue) { - try { - byte value = Byte.parseByte(newValue); - return value >= min && value <= max; - } catch (NumberFormatException e) { - return false; - } - } - - @Override - public String reason() { - return "The input value must be between " + min + " and " + max + "!"; - } - } - } - - - private static class ShortValidatorFactory extends ValidatorFactory { - private final short min; - private final short max; - - private ShortValidatorFactory(short min, short max) { - this.min = min; - this.max = max; - } - - public Class> get() { - return RangeValidator.class; - } - - public class RangeValidator implements RGValidator { - @Override - public boolean validate(@NotNull Short oldValue, @NotNull String newValue) { - try { - short value = Short.parseShort(newValue); - return value >= min && value <= max; - } catch (NumberFormatException e) { - return false; - } - } - - @Override - public String reason() { - return "The input value must be between " + min + " and " + max + "!"; - } - } - } - - private static class IntValidatorFactory extends ValidatorFactory { - private final int min; - private final int max; - - private IntValidatorFactory(int min, int max) { - this.min = min; - this.max = max; - } - - public Class> get() { - return RangeValidator.class; - } - - public class RangeValidator implements RGValidator { - @Override - public boolean validate(@NotNull Integer oldValue, @NotNull String newValue) { - try { - int value = Integer.parseInt(newValue); - return value >= min && value <= max; - } catch (NumberFormatException e) { - return false; - } - } - - @Override - public String reason() { - return "The input value must be between " + min + " and " + max + "!"; - } + @Override + public boolean validate(@NotNull Integer oldValue, @NotNull String newValue) { + try { + T value = parse(newValue); + return value.doubleValue() >= getRange().getKey().doubleValue() && value.doubleValue() <= getRange().getValue().doubleValue(); + } catch (NumberFormatException e) { + return false; } } - - private static class LongValidatorFactory extends ValidatorFactory { - private final long min; - private final long max; - - private LongValidatorFactory(long min, long max) { - this.min = min; - this.max = max; - } - - public Class> get() { - return RangeValidator.class; - } - - public class RangeValidator implements RGValidator { - @Override - public boolean validate(@NotNull Long oldValue, @NotNull String newValue) { - try { - long value = Long.parseLong(newValue); - return value >= min && value <= max; - } catch (NumberFormatException e) { - return false; - } - } - - @Override - public String reason() { - return "The input value must be between " + min + " and " + max + "!"; - } - } + @Override + public String reason() { + return "The input value must be between " + getRange().getKey().toString() + " and " + getRange().getValue().toString() + "!"; } + protected abstract T parse(@NotNull String newValue); + } - private static class StringValidatorFactory extends ValidatorFactory { - private final List accept; - - private StringValidatorFactory(String... accept) { - this.accept = Arrays.asList(accept); - } - - private StringValidatorFactory(Collection accept) { - this.accept = new ArrayList<>(accept); - } - - public Class> get() { - return RangeValidator.class; - } - - public class RangeValidator implements RGValidator { - @Override - public boolean validate(@NotNull String oldValue, @NotNull String newValue) { - if (newValue.isEmpty()) return false; - return accept.contains(newValue); - } - - @Override - public String reason() { - return "The input value must be one of %s!".formatted(accept); - } - } - } - - public static @NotNull Class> rangeByte(byte min, byte max) { - return new ByteValidatorFactory(min, max).get(); + abstract class ByteValidator extends NumberValidator { + protected Byte parse(@NotNull String newValue) { + return Byte.parseByte(newValue); } + } - public static @NotNull Class> rangeShort(short min, short max) { - return new ShortValidatorFactory(min, max).get(); + abstract class ShortValidator extends NumberValidator { + protected Short parse(@NotNull String newValue) { + return Short.parseShort(newValue); } + } - public static @NotNull Class> rangeInt(int min, int max) { - return new IntValidatorFactory(min, max).get(); + abstract class IntegerValidator extends NumberValidator { + protected Integer parse(@NotNull String newValue) { + return Integer.parseInt(newValue); } + } - public static @NotNull Class> rangeLong(long min, long max) { - return new LongValidatorFactory(min, max).get(); + abstract class LongValidator extends NumberValidator { + protected Long parse(@NotNull String newValue) { + return Long.parseLong(newValue); } + } - public static @NotNull Class> rangeString(String... accept) { - return new StringValidatorFactory(accept).get(); + abstract class FloatValidator extends NumberValidator { + protected Float parse(@NotNull String newValue) { + return Float.parseFloat(newValue); } + } - public static @NotNull Class> rangeString(Collection accept) { - return new StringValidatorFactory(accept).get(); + abstract class DoubleValidator extends NumberValidator { + protected Double parse(@NotNull String newValue) { + return Double.parseDouble(newValue); } } } diff --git a/src/main/java/dev/anvilcraft/rg/api/event/RGRuleChangeEvent.java b/src/main/java/dev/anvilcraft/rg/api/event/RGRuleChangeEvent.java new file mode 100644 index 0000000..6e35e0a --- /dev/null +++ b/src/main/java/dev/anvilcraft/rg/api/event/RGRuleChangeEvent.java @@ -0,0 +1,53 @@ +package dev.anvilcraft.rg.api.event; + +import dev.anvilcraft.rg.api.RGRule; +import net.minecraft.server.MinecraftServer; +import net.neoforged.bus.api.Event; +import net.neoforged.bus.api.ICancellableEvent; + +public class RGRuleChangeEvent extends Event implements ICancellableEvent { + private final RGRule rule; + private final T oldValue; + private T newValue; + + public RGRuleChangeEvent(RGRule rule, T oldValue, T newValue) { + this.rule = rule; + this.oldValue = oldValue; + this.newValue = newValue; + } + + public RGRule getRule() { + return rule; + } + + public T getNewValue() { + return newValue; + } + + public T getOldValue() { + return oldValue; + } + + public void setNewValue(T newValue) { + this.newValue = newValue; + } + + public static class Server extends RGRuleChangeEvent { + private final MinecraftServer server; + + public Server(RGRule rule, T oldValue, T newValue, MinecraftServer server) { + super(rule, oldValue, newValue); + this.server = server; + } + + public MinecraftServer getServer() { + return server; + } + } + + public static class Client extends RGRuleChangeEvent { + public Client(RGRule rule, T oldValue, T newValue) { + super(rule, oldValue, newValue); + } + } +} diff --git a/src/main/java/dev/anvilcraft/rg/api/server/ServerRGRuleManager.java b/src/main/java/dev/anvilcraft/rg/api/server/ServerRGRuleManager.java index 55af8e1..0716f50 100644 --- a/src/main/java/dev/anvilcraft/rg/api/server/ServerRGRuleManager.java +++ b/src/main/java/dev/anvilcraft/rg/api/server/ServerRGRuleManager.java @@ -125,7 +125,7 @@ private boolean checkPermission(@NotNull CommandSourceStack source) { private void listCommand(LiteralArgumentBuilder builder, TriFunction, RGRule, String, Integer> execute, boolean list) { for (Map.Entry> entry : rules.entrySet()) { RGRule rgRule = entry.getValue(); - LiteralArgumentBuilder keyNode = Commands.literal(entry.getKey()); + LiteralArgumentBuilder keyNode = Commands.literal(rgRule.name()); if (list) keyNode.executes(ctx -> this.ruleInfoCommand(ctx, rgRule)); for (String value : rgRule.allowed()) { keyNode.then(Commands.literal(value).executes(ctx -> execute.apply(ctx, rgRule, value))); @@ -152,8 +152,12 @@ private int listCommand(@NotNull CommandContext context) { categoryComponent.append("["); categoryComponent.append(TranslationUtil.trans(getDescriptionCategoryKey(category))); categoryComponent.append("] "); - categoriesComponent.append(categoryComponent.withStyle(ChatFormatting.AQUA)); - categoriesComponent.withStyle(Style.EMPTY.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/%s category %s".formatted(literal, category)))); + categoryComponent.withStyle( + Style.EMPTY + .applyFormat(ChatFormatting.AQUA) + .withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/%s category %s".formatted(literal, category))) + ); + categoriesComponent.append(categoryComponent); } context.getSource().sendSuccess(() -> categoriesComponent, false); return 1; @@ -174,8 +178,19 @@ private int ruleInfoCommand(@NotNull CommandContext cont MutableComponent result = Component.empty(); for (String string : rule.allowed()) { if (!string.equals(rule.allowed()[0])) result.append(" "); - //noinspection unchecked - boolean isGlobalDefault = string.equals(rule.codec().encode((T) worldConfig.get(rule))); + Object worldDefault = worldConfig.get(rule); + Object globalDefault = globalConfig.get(rule); + T ruleDefault = rule.defaultValue(); + boolean isGlobalDefault; + if (worldDefault != null) { + //noinspection unchecked + isGlobalDefault = string.equals(rule.codec().encode((T) worldDefault)); + } else if (globalDefault != null) { + //noinspection unchecked + isGlobalDefault = string.equals(rule.codec().encode((T) globalDefault)); + } else { + isGlobalDefault = string.equals(rule.codec().encode(ruleDefault)); + } boolean isSelect = string.equals(rule.codec().encode(rule.getValue())); MutableComponent component = Component.literal("[%s]".formatted(string)); Style style = Style.EMPTY; @@ -197,6 +212,8 @@ private int ruleInfoCommand(@NotNull CommandContext cont private int categoryCommand(@NotNull CommandContext context) { String category = StringArgumentType.getString(context, "category"); + MutableComponent categoryComponent = TranslationUtil.trans(getDescriptionCategoryKey(category)).append(":"); + context.getSource().sendSuccess(() -> categoryComponent, false); for (RGRule rule : rules.values()) { if (Arrays.stream(rule.categories()).noneMatch(s -> s.equals(category))) continue; MutableComponent component = Component.literal("- "); diff --git a/src/main/java/dev/anvilcraft/rg/event/RGRuleChangeEventListener.java b/src/main/java/dev/anvilcraft/rg/event/RGRuleChangeEventListener.java new file mode 100644 index 0000000..9b82441 --- /dev/null +++ b/src/main/java/dev/anvilcraft/rg/event/RGRuleChangeEventListener.java @@ -0,0 +1,23 @@ +package dev.anvilcraft.rg.event; + +import dev.anvilcraft.rg.RollingGate; +import dev.anvilcraft.rg.api.event.RGRuleChangeEvent; +import dev.anvilcraft.rg.mixin.server.DedicatedServerAccessor; +import net.minecraft.server.MinecraftServer; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import org.jetbrains.annotations.NotNull; + +@EventBusSubscriber(modid = RollingGate.MODID) +public class RGRuleChangeEventListener { + @SubscribeEvent + public static void onRuleChange(@NotNull RGRuleChangeEvent.Server event) { + if (event.getRule().name().equals("viewDistance")) changeViewDistance(event.getServer(), event.getNewValue()); + } + + public static void changeViewDistance(@NotNull MinecraftServer server, int value) { + if (!server.isDedicatedServer()) return; + int distance = value >= 2 ? value : ((DedicatedServerAccessor) server).getSettings().getProperties().viewDistance; + server.getPlayerList().setViewDistance(distance); + } +} diff --git a/src/main/java/dev/anvilcraft/rg/mixin/server/DedicatedServerAccessor.java b/src/main/java/dev/anvilcraft/rg/mixin/server/DedicatedServerAccessor.java new file mode 100644 index 0000000..cf04940 --- /dev/null +++ b/src/main/java/dev/anvilcraft/rg/mixin/server/DedicatedServerAccessor.java @@ -0,0 +1,12 @@ +package dev.anvilcraft.rg.mixin.server; + +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.server.dedicated.DedicatedServerSettings; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(DedicatedServer.class) +public interface DedicatedServerAccessor { + @Accessor + DedicatedServerSettings getSettings(); +} diff --git a/src/main/resources/assets/rolling_gate/lang/en_us.json b/src/main/resources/assets/rolling_gate/lang/en_us.json index bef8e9e..83cd303 100644 --- a/src/main/resources/assets/rolling_gate/lang/en_us.json +++ b/src/main/resources/assets/rolling_gate/lang/en_us.json @@ -1,9 +1,13 @@ { + "rolling_gate.category.base": "BASE", + "rolling_gate.category.creative": "CREATIVE", + "rolling_gate.category.disabled": "DISABLED", + "rolling_gate.rolling_gate.rule.language": "Language", "rolling_gate.rolling_gate.rule.language.desc": "Set the default language for RollingGate", - "rolling_gate.category.base": "BASE", - "rolling_gate.category.disabled": "DISABLED", + "rolling_gate.rolling_gate.rule.view_distance": "View Distance", + "rolling_gate.rolling_gate.rule.view_distance.desc": "Set the default view distance for server", "rolling_gate.command.root.version": "Version: %s", "rolling_gate.command.reload.success": "Reload Success!", diff --git a/src/main/resources/assets/rolling_gate/lang/zh_cn.json b/src/main/resources/assets/rolling_gate/lang/zh_cn.json index 1396a5d..5b9c1de 100644 --- a/src/main/resources/assets/rolling_gate/lang/zh_cn.json +++ b/src/main/resources/assets/rolling_gate/lang/zh_cn.json @@ -1,9 +1,13 @@ { + "rolling_gate.category.base": "基础", + "rolling_gate.category.creative": "创造", + "rolling_gate.category.disabled": "禁用类", + "rolling_gate.rolling_gate.rule.language": "语言", "rolling_gate.rolling_gate.rule.language.desc": "设置卷帘门的默认语言", - "rolling_gate.category.base": "基础", - "rolling_gate.category.disabled": "禁用类", + "rolling_gate.rolling_gate.rule.view_distance": "视距", + "rolling_gate.rolling_gate.rule.view_distance.desc": "设置服务器的默认视距", "rolling_gate.command.root.version": "版本: %s", "rolling_gate.command.reload.success": "重载成功!", diff --git a/src/main/resources/rolling_gate.mixins.json b/src/main/resources/rolling_gate.mixins.json index 6ad6758..f058f76 100644 --- a/src/main/resources/rolling_gate.mixins.json +++ b/src/main/resources/rolling_gate.mixins.json @@ -5,6 +5,7 @@ "compatibilityLevel": "JAVA_8", "refmap": "rolling_gate.refmap.json", "mixins": [ + "server.DedicatedServerAccessor" ], "client": [ ],