Settings rework and encapsulation (#1330)
* Settings rework and encapsulation * Settings overhaul 3 Move everything in the new system to a new API package Require rules using the new system to specify descriptions in their translation files Implement temporary system on top of current translations to allow that to work properly * Fix getRules fallback * Final touches to settings refactor * Mention that set shouldn't throw and remove comment * Change translation keys to unify with translation PR * Deprecate printAllRulesToLog to allow using any PrintStream * Remove restriction of CarpetRules being Comparable And fix overreaction in a deprecation message * Bring back user input to rule observers * Make Validator deprecation warning not spam on use * Remove unneeded abstract methods in CarpetRule already inherited from CarpetRule They literally do nothing, don't even force them to be implemented, and may be confusing as to why they're there or as if they are needed to be implemented * Fix legacy SettingsManager field not being assigned correctly * Finish fixing conflicts from future merge * Actually fix remaining conflicts from future merge (missing imports) * Import the newer rule in SettingsManager * Refactor Validators * Don't spam deprecation warnings on custom managers * Get out the var * Fix missing warning in old rule observers * Update src/main/java/carpet/api/settings/Rule.java * Remove `@ApiStatus.Experimental` annotations * Fix incorrect `@deprecated` javadoc tags By removing them given the class is deprecated anyway and else there's too much noise * Add missing static import * Remove TypedField and use a regular field I liked that class tbh, but it's true that it wasn't really needed * Move `field` assignment out of for removal * Fix not following contract of `type()` returning boxed value Previously handled by TypedField * Fix few incorrect translation keys * Remove resaving of comments when saving config files Comments ignoring is still a thing when reading, so `locked` configs for example will keep comments * Condense rule load messages in one * Only print loaded settings message when at least one setting was changed * Fix legacy validator detector * Tweak and add more context to response to invalid values * Fix strict validator not actually erroring on invalid input
This commit is contained in:
1 parent
f5a92ebb8e
commit
0a972d6c56
44 files changed
+2712
-1641
No files matched your search
@@ -1,13 +1,14 @@
|
||||
package carpet;
|
||||
|
||||
import carpet.script.CarpetExpression;
|
||||
import carpet.settings.SettingsManager;
|
||||
import carpet.api.settings.SettingsManager;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import net.minecraft.commands.CommandBuildContext;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
public interface CarpetExtension
|
||||
@@ -69,7 +70,11 @@ public interface CarpetExtension
|
||||
registerCommands(dispatcher);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated Implement {@link #extensionSettingsManager()} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
default carpet.settings.SettingsManager customSettingsManager() {return null;}
|
||||
|
||||
/**
|
||||
* Provide your own custom settings manager managed in the same way as base /carpet
|
||||
@@ -78,7 +83,15 @@ public interface CarpetExtension
|
||||
* @return Your custom {@link SettingsManager} instance to be managed by Carpet
|
||||
*
|
||||
*/
|
||||
default SettingsManager customSettingsManager() {return null;}
|
||||
default SettingsManager extensionSettingsManager() {
|
||||
// Warn extensions overriding the other (deprecated) method, go ahead and override this if you want to provide a custom SettingsManager
|
||||
SettingsManager deprecatedManager = customSettingsManager();
|
||||
if (deprecatedManager != null) {
|
||||
// Extension is providing a manager via the old method (and also hasn't overriden this)
|
||||
CarpetServer.warnOutdatedManager(this);
|
||||
}
|
||||
return customSettingsManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Event that gets called when a player logs in
|
||||
@@ -134,10 +147,10 @@ public interface CarpetExtension
|
||||
*
|
||||
* @param lang A {@link String} being the language id selected by the user
|
||||
* @return A {@link Map<String, String>} containing the string key with it's
|
||||
* respective translation {@link String} or {@link null} if not available
|
||||
* respective translation {@link String} or an empty map if not available
|
||||
*
|
||||
*/
|
||||
default Map<String, String> canHasTranslations(String lang) { return null;}
|
||||
default Map<String, String> canHasTranslations(String lang) { return Collections.emptyMap();}
|
||||
|
||||
/**
|
||||
* Handles each call that creates / parses the scarpet expression.
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package carpet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import carpet.commands.CounterCommand;
|
||||
import carpet.commands.DistanceCommand;
|
||||
@@ -22,7 +24,7 @@ import carpet.helpers.HopperCounter;
|
||||
import carpet.helpers.TickSpeed;
|
||||
import carpet.logging.LoggerRegistry;
|
||||
import carpet.script.CarpetScriptServer;
|
||||
import carpet.settings.SettingsManager;
|
||||
import carpet.api.settings.SettingsManager;
|
||||
import carpet.logging.HUDController;
|
||||
import carpet.utils.FabricAPIHooks;
|
||||
import carpet.utils.MobAI;
|
||||
@@ -43,7 +45,7 @@ public class CarpetServer // static for now - easier to handle all around the co
|
||||
public static MinecraftServer minecraft_server;
|
||||
private static CommandDispatcher<CommandSourceStack> currentCommandDispatcher;
|
||||
public static CarpetScriptServer scriptServer;
|
||||
public static SettingsManager settingsManager;
|
||||
public static carpet.settings.SettingsManager settingsManager; // to change type to api type, can't change right now because of binary and source compat
|
||||
public static final List<CarpetExtension> extensions = new ArrayList<>();
|
||||
|
||||
// Separate from onServerLoaded, because a server can be loaded multiple times in singleplayer
|
||||
@@ -77,7 +79,7 @@ public class CarpetServer // static for now - easier to handle all around the co
|
||||
// to register before this call in a ModInitializer (declared in fabric.mod.json)
|
||||
public static void onGameStarted()
|
||||
{
|
||||
settingsManager = new SettingsManager(CarpetSettings.carpetVersion, "carpet", "Carpet Mod");
|
||||
settingsManager = new carpet.settings.SettingsManager(CarpetSettings.carpetVersion, "carpet", "Carpet Mod");
|
||||
settingsManager.parseSettingsClass(CarpetSettings.class);
|
||||
extensions.forEach(CarpetExtension::onGameStarted);
|
||||
FabricAPIHooks.initialize();
|
||||
@@ -92,7 +94,7 @@ public class CarpetServer // static for now - easier to handle all around the co
|
||||
|
||||
settingsManager.attachServer(server);
|
||||
extensions.forEach(e -> {
|
||||
SettingsManager sm = e.customSettingsManager();
|
||||
SettingsManager sm = e.extensionSettingsManager();
|
||||
if (sm != null) sm.attachServer(server);
|
||||
e.onServerLoaded(server);
|
||||
});
|
||||
@@ -129,7 +131,7 @@ public class CarpetServer // static for now - easier to handle all around the co
|
||||
}
|
||||
settingsManager.registerCommand(dispatcher, commandBuildContext);
|
||||
extensions.forEach(e -> {
|
||||
SettingsManager sm = e.customSettingsManager();
|
||||
SettingsManager sm = e.extensionSettingsManager();
|
||||
if (sm != null) sm.registerCommand(dispatcher, commandBuildContext);
|
||||
});
|
||||
TickCommand.register(dispatcher, commandBuildContext);
|
||||
@@ -204,6 +206,10 @@ public class CarpetServer // static for now - easier to handle all around the co
|
||||
public static void onServerDoneClosing(MinecraftServer server)
|
||||
{
|
||||
settingsManager.detachServer();
|
||||
extensions.forEach(e -> {
|
||||
SettingsManager manager = e.extensionSettingsManager();
|
||||
if (manager != null) manager.detachServer();
|
||||
});
|
||||
}
|
||||
|
||||
public static void registerExtensionLoggers()
|
||||
@@ -216,5 +222,14 @@ public class CarpetServer // static for now - easier to handle all around the co
|
||||
scriptServer.reload(server);
|
||||
extensions.forEach(e -> e.onReload(server));
|
||||
}
|
||||
|
||||
private static final Set<CarpetExtension> warnedOutdatedManagerProviders = new HashSet<>();
|
||||
static void warnOutdatedManager(CarpetExtension ext)
|
||||
{
|
||||
if (!warnedOutdatedManagerProviders.contains(ext))
|
||||
CarpetSettings.LOG.warn("""
|
||||
%s is providing a SettingsManager from an outdated method in CarpetExtension!
|
||||
This behaviour will not work in later Carpet versions and the manager won't be registered!""".formatted(ext.getClass().getName()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package carpet;
|
||||
|
||||
import carpet.api.settings.CarpetRule;
|
||||
import carpet.api.settings.RuleCategory;
|
||||
import carpet.api.settings.Validators;
|
||||
import carpet.api.settings.Validator;
|
||||
import carpet.script.utils.AppStoreManager;
|
||||
import carpet.settings.ParsedRule;
|
||||
import carpet.settings.Rule;
|
||||
import carpet.settings.SettingsManager;
|
||||
import carpet.settings.Validator;
|
||||
import carpet.utils.Translations;
|
||||
import carpet.utils.CommandHelper;
|
||||
import carpet.utils.Messenger;
|
||||
import carpet.utils.SpawnChunks;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
@@ -30,19 +32,19 @@ import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
|
||||
import static carpet.settings.RuleCategory.BUGFIX;
|
||||
import static carpet.settings.RuleCategory.COMMAND;
|
||||
import static carpet.settings.RuleCategory.CREATIVE;
|
||||
import static carpet.settings.RuleCategory.EXPERIMENTAL;
|
||||
import static carpet.settings.RuleCategory.FEATURE;
|
||||
import static carpet.settings.RuleCategory.OPTIMIZATION;
|
||||
import static carpet.settings.RuleCategory.SURVIVAL;
|
||||
import static carpet.settings.RuleCategory.TNT;
|
||||
import static carpet.settings.RuleCategory.DISPENSER;
|
||||
import static carpet.settings.RuleCategory.SCARPET;
|
||||
import static carpet.settings.RuleCategory.CLIENT;
|
||||
import static carpet.api.settings.RuleCategory.BUGFIX;
|
||||
import static carpet.api.settings.RuleCategory.COMMAND;
|
||||
import static carpet.api.settings.RuleCategory.CREATIVE;
|
||||
import static carpet.api.settings.RuleCategory.EXPERIMENTAL;
|
||||
import static carpet.api.settings.RuleCategory.FEATURE;
|
||||
import static carpet.api.settings.RuleCategory.OPTIMIZATION;
|
||||
import static carpet.api.settings.RuleCategory.SURVIVAL;
|
||||
import static carpet.api.settings.RuleCategory.TNT;
|
||||
import static carpet.api.settings.RuleCategory.DISPENSER;
|
||||
import static carpet.api.settings.RuleCategory.SCARPET;
|
||||
import static carpet.api.settings.RuleCategory.CLIENT;
|
||||
|
||||
@SuppressWarnings("CanBeFinal")
|
||||
@SuppressWarnings({"CanBeFinal", "removal"}) // removal should be removed after migrating rules to the new system
|
||||
public class CarpetSettings
|
||||
{
|
||||
public static final String carpetVersion = "1.4.79+v220607";
|
||||
@@ -56,29 +58,25 @@ public class CarpetSettings
|
||||
public static final int vanillaStructureBlockLimit = 48;
|
||||
|
||||
private static class LanguageValidator extends Validator<String> {
|
||||
@Override public String validate(CommandSourceStack source, ParsedRule<String> currentRule, String newValue, String string) {
|
||||
if (currentRule.get().equals(newValue) || source == null)
|
||||
{
|
||||
return newValue;
|
||||
}
|
||||
@Override public String validate(CommandSourceStack source, CarpetRule<String> currentRule, String newValue, String string) {
|
||||
if (!Translations.isValidLanguage(newValue))
|
||||
{
|
||||
Messenger.m(source, "r "+newValue+" is not a valid language");
|
||||
return null;
|
||||
}
|
||||
CarpetSettings.language = newValue;
|
||||
Translations.updateLanguage(source);
|
||||
Translations.updateLanguage();
|
||||
return newValue;
|
||||
}
|
||||
}
|
||||
@Rule(
|
||||
desc = "sets the language for carpet",
|
||||
desc = "Sets the language for Carpet",
|
||||
category = FEATURE,
|
||||
options = {"none", "zh_cn", "zh_tw"},
|
||||
strict = false,
|
||||
options = {"en_us", "zh_cn", "zh_tw"},
|
||||
strict = true, // the current system doesn't handle fallbacks and other, not defined languages would make unreadable mess. Change later
|
||||
validate = LanguageValidator.class
|
||||
)
|
||||
public static String language = "none";
|
||||
public static String language = "en_us";
|
||||
|
||||
/*
|
||||
These will be turned when events can be added / removed in code
|
||||
@@ -129,7 +127,7 @@ public class CarpetSettings
|
||||
*/
|
||||
|
||||
private static class CarpetPermissionLevel extends Validator<String> {
|
||||
@Override public String validate(CommandSourceStack source, ParsedRule<String> currentRule, String newValue, String string) {
|
||||
@Override public String validate(CommandSourceStack source, CarpetRule<String> currentRule, String newValue, String string) {
|
||||
if (source == null || source.hasPermission(4))
|
||||
return newValue;
|
||||
return null;
|
||||
@@ -174,14 +172,14 @@ public class CarpetSettings
|
||||
|
||||
|
||||
private static class OneHourMaxDelayLimit extends Validator<Integer> {
|
||||
@Override public Integer validate(CommandSourceStack source, ParsedRule<Integer> currentRule, Integer newValue, String string) {
|
||||
@Override public Integer validate(CommandSourceStack source, CarpetRule<Integer> currentRule, Integer newValue, String string) {
|
||||
return (newValue > 0 && newValue <= 72000) ? newValue : null;
|
||||
}
|
||||
@Override
|
||||
public String description() { return "You must choose a value from 1 to 72000";}
|
||||
}
|
||||
|
||||
@Rule(desc = "Dropping entire stacks works also from on the crafting UI result slot", category = {BUGFIX, SURVIVAL})
|
||||
@Rule(desc = "Dropping entire stacks works also from on the crafting UI result slot", category = {RuleCategory.BUGFIX, SURVIVAL})
|
||||
public static boolean ctrlQCraftingFix = false;
|
||||
|
||||
@Rule(desc = "Parrots don't get of your shoulders until you receive proper damage", category = {SURVIVAL, FEATURE})
|
||||
@@ -209,7 +207,7 @@ public class CarpetSettings
|
||||
public static class StackableShulkerBoxValidator extends Validator<String>
|
||||
{
|
||||
@Override
|
||||
public String validate(CommandSourceStack source, ParsedRule<String> currentRule, String newValue, String string)
|
||||
public String validate(CommandSourceStack source, CarpetRule<String> currentRule, String newValue, String string)
|
||||
{
|
||||
if (newValue.matches("^[0-9]+$")) {
|
||||
int value = Integer.parseInt(newValue);
|
||||
@@ -262,8 +260,8 @@ public class CarpetSettings
|
||||
private static class CheckOptimizedTntEnabledValidator<T> extends Validator<T>
|
||||
{
|
||||
@Override
|
||||
public T validate(CommandSourceStack source, ParsedRule<T> currentRule, T newValue, String string) {
|
||||
return optimizedTNT || currentRule.defaultValue.equals(newValue) ? newValue : null;
|
||||
public T validate(CommandSourceStack source, CarpetRule<T> currentRule, T newValue, String string) {
|
||||
return optimizedTNT || currentRule.defaultValue().equals(newValue) ? newValue : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -278,7 +276,7 @@ public class CarpetSettings
|
||||
|
||||
private static class TNTRandomRangeValidator extends Validator<Double> {
|
||||
@Override
|
||||
public Double validate(CommandSourceStack source, ParsedRule<Double> currentRule, Double newValue, String string) {
|
||||
public Double validate(CommandSourceStack source, CarpetRule<Double> currentRule, Double newValue, String string) {
|
||||
return newValue == -1 || newValue >= 0 ? newValue : null;
|
||||
}
|
||||
|
||||
@@ -294,7 +292,7 @@ public class CarpetSettings
|
||||
|
||||
private static class TNTAngleValidator extends Validator<Double> {
|
||||
@Override
|
||||
public Double validate(CommandSourceStack source, ParsedRule<Double> currentRule, Double newValue, String string) {
|
||||
public Double validate(CommandSourceStack source, CarpetRule<Double> currentRule, Double newValue, String string) {
|
||||
return (newValue >= 0 && newValue < Math.PI * 2) || newValue == -1 ? newValue : null;
|
||||
}
|
||||
|
||||
@@ -388,7 +386,7 @@ public class CarpetSettings
|
||||
|
||||
|
||||
private static class ChainStoneSetting extends Validator<String> {
|
||||
@Override public String validate(CommandSourceStack source, ParsedRule<String> currentRule, String newValue, String string) {
|
||||
@Override public String validate(CommandSourceStack source, CarpetRule<String> currentRule, String newValue, String string) {
|
||||
CarpetSettings.doChainStone = !newValue.toLowerCase(Locale.ROOT).equals("false");
|
||||
CarpetSettings.chainStoneStickToAll = newValue.toLowerCase(Locale.ROOT).equals("stick_to_all");
|
||||
|
||||
@@ -485,12 +483,26 @@ public class CarpetSettings
|
||||
public static String commandScript = "true";
|
||||
|
||||
private static class ModulePermissionLevel extends Validator<String> {
|
||||
@Override public String validate(CommandSourceStack source, ParsedRule<String> currentRule, String newValue, String string) {
|
||||
int permissionLevel = SettingsManager.getCommandLevel(newValue);
|
||||
@Override public String validate(CommandSourceStack source, CarpetRule<String> currentRule, String newValue, String string) {
|
||||
int permissionLevel = switch (newValue) {
|
||||
case "false":
|
||||
yield 0;
|
||||
case "true":
|
||||
case "ops":
|
||||
yield 2;
|
||||
case "0":
|
||||
case "1":
|
||||
case "2":
|
||||
case "3":
|
||||
case "4":
|
||||
yield Integer.parseInt(newValue);
|
||||
default: throw new IllegalArgumentException();
|
||||
};
|
||||
if (source != null && !source.hasPermission(permissionLevel))
|
||||
return null;
|
||||
CarpetSettings.runPermissionLevel = permissionLevel;
|
||||
CarpetServer.settingsManager.notifyPlayersCommandsChanged();
|
||||
if (source != null)
|
||||
CommandHelper.notifyPlayersCommandsChanged(source.getServer());
|
||||
return newValue;
|
||||
}
|
||||
@Override
|
||||
@@ -506,7 +518,7 @@ public class CarpetSettings
|
||||
},
|
||||
category = {SCARPET},
|
||||
options = {"ops", "0", "1", "2", "3", "4"},
|
||||
validate = {Validator._COMMAND_LEVEL_VALIDATOR.class, ModulePermissionLevel.class}
|
||||
validate = {Validators.CommandLevel.class, ModulePermissionLevel.class}
|
||||
)
|
||||
public static String commandScriptACE = "ops";
|
||||
|
||||
@@ -585,7 +597,7 @@ public class CarpetSettings
|
||||
//public static boolean miningGhostBlockFix = false;
|
||||
|
||||
private static class PushLimitLimits extends Validator<Integer> {
|
||||
@Override public Integer validate(CommandSourceStack source, ParsedRule<Integer> currentRule, Integer newValue, String string) {
|
||||
@Override public Integer validate(CommandSourceStack source, CarpetRule<Integer> currentRule, Integer newValue, String string) {
|
||||
return (newValue>0 && newValue <= 1024) ? newValue : null;
|
||||
}
|
||||
@Override
|
||||
@@ -610,7 +622,7 @@ public class CarpetSettings
|
||||
public static int railPowerLimit = 9;
|
||||
|
||||
private static class FillLimitLimits extends Validator<Integer> {
|
||||
@Override public Integer validate(CommandSourceStack source, ParsedRule<Integer> currentRule, Integer newValue, String string) {
|
||||
@Override public Integer validate(CommandSourceStack source, CarpetRule<Integer> currentRule, Integer newValue, String string) {
|
||||
return (newValue>0 && newValue <= 20000000) ? newValue : null;
|
||||
}
|
||||
@Override
|
||||
@@ -640,7 +652,7 @@ public class CarpetSettings
|
||||
options = {"0", "1", "20"},
|
||||
category = OPTIMIZATION,
|
||||
strict = false,
|
||||
validate = Validator.NONNEGATIVE_NUMBER.class
|
||||
validate = Validators.NonNegativeNumber.class
|
||||
)
|
||||
public static int maxEntityCollisions = 0;
|
||||
|
||||
@@ -649,7 +661,7 @@ public class CarpetSettings
|
||||
options = {"0", "12", "20", "40"},
|
||||
category = CREATIVE,
|
||||
strict = false,
|
||||
validate = Validator.NONNEGATIVE_NUMBER.class
|
||||
validate = Validators.NonNegativeNumber.class
|
||||
)
|
||||
public static int pingPlayerListLimit = 12;
|
||||
/*
|
||||
@@ -680,9 +692,9 @@ public class CarpetSettings
|
||||
|
||||
private static class ViewDistanceValidator extends Validator<Integer>
|
||||
{
|
||||
@Override public Integer validate(CommandSourceStack source, ParsedRule<Integer> currentRule, Integer newValue, String string)
|
||||
@Override public Integer validate(CommandSourceStack source, CarpetRule<Integer> currentRule, Integer newValue, String string)
|
||||
{
|
||||
if (currentRule.get().equals(newValue) || source == null)
|
||||
if (currentRule.value().equals(newValue) || source == null)
|
||||
{
|
||||
return newValue;
|
||||
}
|
||||
@@ -721,9 +733,9 @@ public class CarpetSettings
|
||||
|
||||
private static class SimulationDistanceValidator extends Validator<Integer>
|
||||
{
|
||||
@Override public Integer validate(CommandSourceStack source, ParsedRule<Integer> currentRule, Integer newValue, String string)
|
||||
@Override public Integer validate(CommandSourceStack source, CarpetRule<Integer> currentRule, Integer newValue, String string)
|
||||
{
|
||||
if (currentRule.get().equals(newValue) || source == null)
|
||||
if (currentRule.value().equals(newValue) || source == null)
|
||||
{
|
||||
return newValue;
|
||||
}
|
||||
@@ -773,14 +785,14 @@ public class CarpetSettings
|
||||
SpawnChunks.changeSpawnChunks(overworld.getChunkSource(), centerChunk, size);
|
||||
}
|
||||
}
|
||||
@Override public Integer validate(CommandSourceStack source, ParsedRule<Integer> currentRule, Integer newValue, String string) {
|
||||
@Override public Integer validate(CommandSourceStack source, CarpetRule<Integer> currentRule, Integer newValue, String string) {
|
||||
if (source == null) return newValue;
|
||||
if (newValue < 0 || newValue > 32)
|
||||
{
|
||||
Messenger.m(source, "r spawn chunk size has to be between 0 and 32");
|
||||
return null;
|
||||
}
|
||||
if (currentRule.get().intValue() == newValue.intValue())
|
||||
if (currentRule.value().intValue() == newValue.intValue())
|
||||
{
|
||||
//must been some startup thing
|
||||
return newValue;
|
||||
@@ -805,42 +817,31 @@ public class CarpetSettings
|
||||
public static int spawnChunksSize = 11;
|
||||
|
||||
public static class LightBatchValidator extends Validator<Integer> {
|
||||
public static void applyLightBatchSizes()
|
||||
public static void applyLightBatchSizes(int maxBatchSize)
|
||||
{
|
||||
Iterator<ServerLevel> iterator = CarpetServer.minecraft_server.getAllLevels().iterator();
|
||||
|
||||
while (iterator.hasNext())
|
||||
{
|
||||
ServerLevel serverWorld = iterator.next();
|
||||
serverWorld.getChunkSource().getLightEngine().setTaskPerBatch(lightEngineMaxBatchSize);
|
||||
serverWorld.getChunkSource().getLightEngine().setTaskPerBatch(maxBatchSize);
|
||||
}
|
||||
}
|
||||
@Override public Integer validate(CommandSourceStack source, ParsedRule<Integer> currentRule, Integer newValue, String string) {
|
||||
@Override public Integer validate(CommandSourceStack source, CarpetRule<Integer> currentRule, Integer newValue, String string) {
|
||||
if (source == null) return newValue;
|
||||
if (newValue < 0)
|
||||
{
|
||||
Messenger.m(source, "r light batch size has to be at least 0");
|
||||
return null;
|
||||
}
|
||||
if (currentRule.get().intValue() == newValue.intValue())
|
||||
if (currentRule.value().intValue() == newValue.intValue())
|
||||
{
|
||||
//must been some startup thing
|
||||
return newValue;
|
||||
}
|
||||
if (CarpetServer.minecraft_server == null) return newValue;
|
||||
|
||||
// Set the field before we apply.
|
||||
try
|
||||
{
|
||||
currentRule.field.set(null, newValue.intValue());
|
||||
}
|
||||
catch (IllegalAccessException e)
|
||||
{
|
||||
Messenger.m(source, "r Unable to access setting for "+currentRule.name);
|
||||
return null;
|
||||
}
|
||||
|
||||
applyLightBatchSizes(); // Apply new settings
|
||||
applyLightBatchSizes(newValue); // Apply new settings
|
||||
|
||||
return newValue;
|
||||
}
|
||||
@@ -881,13 +882,13 @@ public class CarpetSettings
|
||||
)
|
||||
public static boolean renewableDeepslate = false;
|
||||
|
||||
@Rule(desc = "fixes block placement rotation issue when player rotates quickly while placing blocks", category = BUGFIX)
|
||||
@Rule(desc = "fixes block placement rotation issue when player rotates quickly while placing blocks", category = RuleCategory.BUGFIX)
|
||||
public static boolean placementRotationFix = false;
|
||||
|
||||
@Rule(
|
||||
desc = "Fixes leads breaking/becoming invisible in unloaded chunks",
|
||||
extra = "You may still get visibly broken leash links on the client side, but server side the link is still there.",
|
||||
category = BUGFIX
|
||||
category = RuleCategory.BUGFIX
|
||||
)// needs checkfix for 1.15
|
||||
public static boolean leadFix = false;
|
||||
|
||||
@@ -943,7 +944,7 @@ public class CarpetSettings
|
||||
},
|
||||
category = {CREATIVE, CLIENT},
|
||||
strict = false,
|
||||
validate = Validator.NONNEGATIVE_NUMBER.class
|
||||
validate = Validators.NonNegativeNumber.class
|
||||
)
|
||||
public static double creativeFlySpeed = 1.0;
|
||||
|
||||
@@ -959,7 +960,7 @@ public class CarpetSettings
|
||||
},
|
||||
category = {CREATIVE, CLIENT},
|
||||
strict = false,
|
||||
validate = Validator.PROBABILITY.class
|
||||
validate = Validators.Probablity.class
|
||||
)
|
||||
public static double creativeFlyDrag = 0.09;
|
||||
|
||||
@@ -975,7 +976,7 @@ public class CarpetSettings
|
||||
|
||||
public static class StructureBlockLimitValidator extends Validator<Integer> {
|
||||
|
||||
@Override public Integer validate(CommandSourceStack source, ParsedRule<Integer> currentRule, Integer newValue, String string) {
|
||||
@Override public Integer validate(CommandSourceStack source, CarpetRule<Integer> currentRule, Integer newValue, String string) {
|
||||
return (newValue >= vanillaStructureBlockLimit) ? newValue : null;
|
||||
}
|
||||
|
||||
@@ -1002,7 +1003,7 @@ public class CarpetSettings
|
||||
public static class StructureBlockIgnoredValidator extends Validator<String> {
|
||||
|
||||
@Override
|
||||
public String validate(CommandSourceStack source, ParsedRule<String> currentRule, String newValue, String string) {
|
||||
public String validate(CommandSourceStack source, CarpetRule<String> currentRule, String newValue, String string) {
|
||||
Optional<Block> ignoredBlock = Registry.BLOCK.getOptional(ResourceLocation.tryParse(newValue));
|
||||
if (!ignoredBlock.isPresent()) {
|
||||
Messenger.m(source, "r Unknown block '" + newValue + "'.");
|
||||
@@ -1027,7 +1028,7 @@ public class CarpetSettings
|
||||
options = {"96", "192", "2048"},
|
||||
category = {CREATIVE, CLIENT},
|
||||
strict = false,
|
||||
validate = Validator.NONNEGATIVE_NUMBER.class
|
||||
validate = Validators.NonNegativeNumber.class
|
||||
)
|
||||
public static int structureBlockOutlineDistance = 96;
|
||||
|
||||
@@ -1050,7 +1051,7 @@ public class CarpetSettings
|
||||
|
||||
private static class UpdateSuppressionBlockModes extends Validator<Integer> {
|
||||
@Override
|
||||
public Integer validate(CommandSourceStack source, ParsedRule<Integer> currentRule, Integer newValue, String string) {
|
||||
public Integer validate(CommandSourceStack source, CarpetRule<Integer> currentRule, Integer newValue, String string) {
|
||||
return newValue < -1 ? null : newValue;
|
||||
}
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package carpet.api.settings;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.ClassUtils;
|
||||
|
||||
import carpet.network.ServerNetworkHandler;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
/**
|
||||
* <p>A Carpet rule, that can return its required properties and stores a value.</p>
|
||||
*
|
||||
* <p>Name and description translations are picked up from the translation system.</p>
|
||||
*
|
||||
* @param <T> The value's type
|
||||
*/
|
||||
public interface CarpetRule<T> {
|
||||
/**
|
||||
* <p>Returns this rule's name</p>
|
||||
*
|
||||
* <p>This is also the rule's id, used as its key in {@link SettingsManager}, for changing rules in-game and for creation
|
||||
* of translation keys for the rule.</p>
|
||||
*
|
||||
* <p>This method must always return the same value for the same {@link CarpetRule}.</p>
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* <p>Returns a {@link List} of {@link BaseComponent} with extra information about this rule, that is,
|
||||
* the lines after the rule's description.</p>
|
||||
*
|
||||
* <p>Handling of translation of the result of this method is responsibility of the rule implementation.</p>
|
||||
*/
|
||||
List<Component> extraInfo();
|
||||
|
||||
/**
|
||||
* <p>Returns a {@link Collection} of categories this rule is on.</p>
|
||||
*/
|
||||
Collection<String> categories();
|
||||
|
||||
/**
|
||||
* <p>Returns a {@link Collection} of suggestions for values that this rule will be able
|
||||
* to accept as {@link String strings}. The rule must be able to accept all of the suggestions in the returned {@link Collection} as a value,
|
||||
* though it may have requirements for those to be applicable.</p>
|
||||
*
|
||||
* <p>The returned collection must contain the rule's default value.</p>
|
||||
*/
|
||||
Collection<String> suggestions();
|
||||
|
||||
/**
|
||||
* <p>Returns the {@link SettingsManager} this rule is in.</p>
|
||||
*
|
||||
* <p>This method may be removed or changed in the future, and is not part of the api contract, but it's needed to sync
|
||||
* the rule with clients and other workings. Overriding this method should be safe.</p>
|
||||
*/
|
||||
SettingsManager settingsManager();
|
||||
|
||||
/**
|
||||
* <p>Returns this rule's value</p>
|
||||
*/
|
||||
T value();
|
||||
|
||||
/**
|
||||
* <p>Returns whether this rule can be toggled in the client-side when not connected to a
|
||||
* Carpet server.</p>
|
||||
*
|
||||
* <p>In the default implementation, this is the case when {@link #categories()} contains {@link RuleCategory#CLIENT} </p>
|
||||
*/
|
||||
boolean canBeToggledClientSide();
|
||||
|
||||
/**
|
||||
* <p>Returns the type of this rule's value.</p>
|
||||
*
|
||||
* <p>If this rule's type is primitive, it returns a wrapped version of it (such as the result of running
|
||||
* {@link ClassUtils#primitiveToWrapper(Class)} on it) </p>
|
||||
*/
|
||||
Class<T> type();
|
||||
|
||||
/**
|
||||
* <p>Returns this rule's default value.</p>
|
||||
*
|
||||
* <p>This value will never be {@code null}, and will always be a valid value for {@link #set(CommandSourceStack, Object)}.</p>
|
||||
*/
|
||||
T defaultValue();
|
||||
|
||||
/**
|
||||
* <p>Sets this rule's value to the provided {@link String}, after first converting the {@link String} into a suitable type.</p>
|
||||
*
|
||||
* <p>This methods run any required validation on the value first, and throws {@link InvalidRuleValueException} if the value is not suitable
|
||||
* for this rule, regardless of whether it was impossible to convert the value to the required type, the rule doesn't accept the
|
||||
* value, or the rule is immutable.</p>
|
||||
*
|
||||
* <p>Implementations of this method must notify their {@link SettingsManager} by calling
|
||||
* {@link SettingsManager#notifyRuleChanged(CommandSourceStack, CarpetRule, String)}, who is responsible for
|
||||
* notifying the {@link ServerNetworkHandler} (if the rule isn't restricted from being synchronized with clients)
|
||||
* and the {@link CarpetEventServer.Event#CARPET_RULE_CHANGES#onCarpetRuleChanges(CarpetRule, CommandSourceStack)} Scarpet event
|
||||
* in case the value of the rule was changed because of the invocation.</p>
|
||||
*
|
||||
* <p>This method must not throw any exception other than the documented {@link InvalidRuleValueException}.</p>
|
||||
*
|
||||
* @param source The {@link CommandSourceStack} to notify about the result of this rule change or {@code null} in order to not notify
|
||||
* @param value The new value for this rule as a {@link String}
|
||||
* @throws InvalidRuleValueException if the value passed to the method was not valid as a value to this rule, either because of incompatible type,
|
||||
* because the rule can't accept that value or because there was some requirement missing for that value to be allowed
|
||||
*/
|
||||
void set(CommandSourceStack source, String value) throws InvalidRuleValueException;
|
||||
|
||||
/**
|
||||
* <p>This method follows the same contract as {@link #set(CommandSourceStack, String)}, but accepts a value already parsed (though not verified).</p>
|
||||
* @see #set(CommandSourceStack, String)
|
||||
*/
|
||||
void set(CommandSourceStack source, T value) throws InvalidRuleValueException;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package carpet.api.settings;
|
||||
|
||||
import carpet.utils.Messenger;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
|
||||
/**
|
||||
* <p>An {@link Exception} thrown when the value given for a {@link CarpetRule} is invalid.</p>
|
||||
*
|
||||
* <p>It can hold a message to be sent to the executing source.</p>
|
||||
*/
|
||||
public class InvalidRuleValueException extends Exception {
|
||||
|
||||
/**
|
||||
* <p>Constructs a new {@link InvalidRuleValueException} with a message that will be passed to the executing source</p>
|
||||
* @param cause The cause of the exception
|
||||
*/
|
||||
public InvalidRuleValueException(String cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Constructs a new {@link InvalidRuleValueException} with no detail message, that therefore should not notify the source</p>
|
||||
*/
|
||||
public InvalidRuleValueException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Notifies the given source with the exception's message if it exists, does nothing if it doesn't exist or it is {@code null}</p>
|
||||
* @param source The source to notify
|
||||
*/
|
||||
public void notifySource(String ruleName, CommandSourceStack source) {
|
||||
if (getMessage() != null)
|
||||
Messenger.m(source, "r Couldn't set value for rule " + ruleName + ": "+ getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package carpet.api.settings;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Any field in this class annotated with this class is interpreted as a carpet rule.
|
||||
* The field must be static and have a type of one of:
|
||||
* - boolean
|
||||
* - int
|
||||
* - double
|
||||
* - String
|
||||
* - long
|
||||
* - float
|
||||
* - a subclass of Enum
|
||||
* The default value of the rule will be the initial value of the field.
|
||||
*/
|
||||
@Documented
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Rule
|
||||
{
|
||||
/**
|
||||
* <p>An array of categories the rule is in, as {@link String strings}.</p>
|
||||
*
|
||||
* <p>Those must have a corresponding translation key. Categories provided in Carpet's {@link RuleCategory} already
|
||||
* have translation key.</p>
|
||||
*/
|
||||
String[] categories();
|
||||
|
||||
/**
|
||||
* Options to select in menu.
|
||||
* Inferred for booleans and enums. Otherwise, must be present.
|
||||
*/
|
||||
String[] options() default {};
|
||||
|
||||
/**
|
||||
* if a rule is not strict - can take any value, otherwise it needs to match
|
||||
* any of the options
|
||||
* For enums, its always strict, same for booleans - no need to set that for them.
|
||||
*/
|
||||
boolean strict() default true;
|
||||
|
||||
/**
|
||||
* If specified, the rule will automatically enable or disable
|
||||
* a builtin Scarpet Rule App with this name.
|
||||
*/
|
||||
String appSource() default "";
|
||||
|
||||
/**
|
||||
* The class of the validator checked right before the rule is changed, using the returned value as the new value to set, or cancel the change if null is returned.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
Class<? extends Validator>[] validators() default {};
|
||||
|
||||
/**
|
||||
* <p>A class or list of classes implementing {@link Condition} that will have their {@link Condition#shouldRegister()} method
|
||||
* executed while the rule is being parsed in {@link SettingsManager#parseSettingsClass(Class)}, that will cause the rule to be skipped
|
||||
* if it evaluates to false</p>
|
||||
*
|
||||
* @see Condition
|
||||
*/
|
||||
Class<? extends Condition>[] conditions() default {};
|
||||
|
||||
/**
|
||||
* <p>Represents a condition that must be met for a rule to be registered in a {@link SettingsManager} via
|
||||
* {@link SettingsManager#parseSettingsClass(Class)}</p>
|
||||
*
|
||||
* @see Rule#conditions()
|
||||
* @see #shouldRegister()
|
||||
*/
|
||||
interface Condition {
|
||||
/**
|
||||
* <p>Returns whether the rule that has had this {@link Condition} added should register.</p>
|
||||
* @return {@code true} to register the rule, {@code false} otherwise
|
||||
*/
|
||||
boolean shouldRegister();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package carpet.api.settings;
|
||||
|
||||
/**
|
||||
* <p>Multiple categories for rules that Carpet uses and can be used to group other rules under the same
|
||||
* categories.</p>
|
||||
*
|
||||
* <p>These are not the only categories that can be used, you can create your own ones for your rules
|
||||
* and they will be added to the SettingsManager.</p>
|
||||
*
|
||||
*/
|
||||
public class RuleCategory {
|
||||
public static final String BUGFIX = "bugfix";
|
||||
public static final String SURVIVAL = "survival";
|
||||
public static final String CREATIVE = "creative";
|
||||
public static final String EXPERIMENTAL = "experimental";
|
||||
public static final String OPTIMIZATION = "optimization";
|
||||
public static final String FEATURE = "feature";
|
||||
public static final String COMMAND = "command";
|
||||
public static final String TNT = "tnt";
|
||||
public static final String DISPENSER = "dispenser";
|
||||
public static final String SCARPET = "scarpet";
|
||||
/**
|
||||
* Rules with this {@link RuleCategory} will have a client-side
|
||||
* counterpart, so they can be set independently without the server
|
||||
* running Carpet
|
||||
*/
|
||||
public static final String CLIENT = "client";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package carpet.api.settings;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import carpet.utils.TranslationKeys;
|
||||
import carpet.utils.Translations;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
|
||||
/**
|
||||
* <p>A helper class for operating with {@link CarpetRule} instances and values.</p>
|
||||
*
|
||||
* <p>If a method is visible and has javadocs it's probably API</p>
|
||||
*/
|
||||
public final class RuleHelper {
|
||||
private RuleHelper() {}
|
||||
|
||||
/**
|
||||
* <p>Gets a {@code boolean} value for a given {@link CarpetRule}.</p>
|
||||
*
|
||||
* <p>The current implementation is as follows:</p>
|
||||
* <ul>
|
||||
* <li>If the rule's type is a {@code boolean}, it will return the value directly.</li>
|
||||
* <li>If the rule's type is a {@link Number}, it will return {@code true} if the value is greater than zero.</li>
|
||||
* <li>In any other case, it will return {@code false}.</li>
|
||||
* </ul>
|
||||
* @param rule The rule to get the {@code boolean} value from
|
||||
* @return A {@code boolean} representation of this rule's value
|
||||
*/
|
||||
public static boolean getBooleanValue(CarpetRule<?> rule) {
|
||||
if (rule.type() == Boolean.class) return (boolean) rule.value();
|
||||
if (Number.class.isAssignableFrom(rule.type())) return ((Number) rule.value()).doubleValue() > 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Converts a rule's value into its similar {@link String} representation.</p>
|
||||
*
|
||||
* <p>If the value is an {@link Enum}, this method returns the name of the enum constant lowercased, else
|
||||
* it returns the result of running {@link #toString()} on the value.</p>
|
||||
* @param value A rule's value
|
||||
* @return A {@link String} representation of the given value
|
||||
*/
|
||||
public static String toRuleString(Object value) {
|
||||
if (value instanceof Enum) return ((Enum<?>) value).name().toLowerCase(Locale.ROOT);
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks whether the given {@code CarpetRule rule} is in its default value</p>
|
||||
*
|
||||
* @param rule The rule to check
|
||||
* @return {@code true} if the rule's default value equals its current value
|
||||
*/
|
||||
public static boolean isInDefaultValue(CarpetRule<?> rule) {
|
||||
return rule.defaultValue().equals(rule.value());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Resets the given {@link CarpetRule rule} to its default value, and notifies the given source, if provided.</p>
|
||||
* @param rule The {@link CarpetRule} to reset to its default value
|
||||
* @param source A {@link ServerCommandSource} to notify about this change, or {@code null}
|
||||
*
|
||||
* @param <T> The type of the {@link CarpetRule}
|
||||
*/
|
||||
public static <T> void resetToDefault(CarpetRule<T> rule, CommandSourceStack source) {
|
||||
try {
|
||||
rule.set(source, rule.defaultValue());
|
||||
} catch (InvalidRuleValueException e) {
|
||||
throw new IllegalStateException("Rule couldn't be set to default value!", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Translations
|
||||
// The methods from this point are not stable API yet and may change or be removed in binary incompatible ways later
|
||||
|
||||
/**
|
||||
* @param rule The {@link CarpetRule} to get the translated name of
|
||||
* @return A {@link String} being the translated {@link CarpetRule#name() name} of the given rule, the current language.
|
||||
*
|
||||
* @apiNote This method isn't stable API yet and may change or be removed in binary incompatible ways in later Carpet versions
|
||||
*/
|
||||
public static String translatedName(CarpetRule<?> rule) {
|
||||
String key = String.format(TranslationKeys.RULE_NAME_PATTERN, rule.settingsManager().identifier(), rule.name());
|
||||
return Translations.hasTranslation(key) ? Translations.tr(key) + String.format(" (%s)", rule.name()): rule.name();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param rule The {@link CarpetRule} to get the translated description of
|
||||
* @return A {@link String} being the translated description of this rule, in Carpet's configured language.
|
||||
*
|
||||
* @apiNote This method isn't stable API yet and may change or be removed in binary incompatible ways in later Carpet versions
|
||||
*/
|
||||
public static String translatedDescription(CarpetRule<?> rule) {
|
||||
return Translations.tr(String.format(TranslationKeys.RULE_DESC_PATTERN, rule.settingsManager().identifier(), rule.name()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param manager A settings manager identifier
|
||||
* @param category A category identifier
|
||||
* @return The translated name of the category
|
||||
*
|
||||
* @apiNote This method isn't stable API yet and may change or be removed in binary incompatible ways in later Carpet versions
|
||||
*/
|
||||
public static String translatedCategory(String manager, String category) {
|
||||
return Translations.tr(TranslationKeys.CATEGORY_PATTERN.formatted(manager, category), category);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,790 @@
|
||||
package carpet.api.settings;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
|
||||
import carpet.CarpetExtension;
|
||||
import carpet.CarpetServer;
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.api.settings.Rule.Condition;
|
||||
import carpet.network.ServerNetworkHandler;
|
||||
import carpet.settings.ParsedRule;
|
||||
import carpet.utils.CommandHelper;
|
||||
import carpet.utils.Messenger;
|
||||
import carpet.utils.TranslationKeys;
|
||||
import carpet.utils.Translations;
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.commands.CommandBuildContext;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.SharedSuggestionProvider;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.level.storage.LevelResource;
|
||||
|
||||
import static carpet.script.CarpetEventServer.Event.CARPET_RULE_CHANGES;
|
||||
import static carpet.utils.Translations.tr;
|
||||
import static java.util.Comparator.comparing;
|
||||
import static net.minecraft.commands.Commands.argument;
|
||||
import static net.minecraft.commands.Commands.literal;
|
||||
import static net.minecraft.commands.SharedSuggestionProvider.suggest;
|
||||
|
||||
/**
|
||||
* <p>A {@link SettingsManager} is a class that manages {@link CarpetRule carpet rules} in a {@link MinecraftServer},
|
||||
* including a command to manage it, and allowing other code to hook into rule changes by using {@link RuleObserver rule observers}.</p>
|
||||
*
|
||||
* <p>You can create your own {@link SettingsManager} if you want to have your own command that will be handled the same way as Carpet's
|
||||
* by using {@link #SettingsManager(String, String, String)} and returning it in your {@link CarpetExtension}'s
|
||||
* {@link CarpetExtension#extensionSettingsManager()} method.</p>
|
||||
*/
|
||||
@SuppressWarnings({"deprecation", "removal"}) // remove after removing old system
|
||||
public class SettingsManager {
|
||||
private final Map<String, CarpetRule<?>> rules = new HashMap<>();
|
||||
private final String version;
|
||||
private final String identifier;
|
||||
private final String fancyName;
|
||||
private boolean locked;
|
||||
private MinecraftServer server;
|
||||
private final List<RuleObserver> observers = new ArrayList<>();
|
||||
private static final List<RuleObserver> staticObservers = new ArrayList<>();
|
||||
static record ConfigReadResult(Map<String, String> ruleMap, boolean locked) {}
|
||||
|
||||
/**
|
||||
* <p>Defines a class that can be notified about a {@link CarpetRule} changing.</p>
|
||||
*
|
||||
* @see #ruleChanged(CommandSourceStack, CarpetRule, String)
|
||||
* @see SettingsManager#registerRuleObserver(RuleObserver)
|
||||
* @see SettingsManager#registerGlobalRuleObserver(RuleObserver)
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public static interface RuleObserver {
|
||||
/**
|
||||
* <p>Notifies this {@link RuleObserver} about the change of a {@link CarpetRule}.</p>
|
||||
*
|
||||
* <p>When this is called, the {@link CarpetRule} value has already been set.</p>
|
||||
*
|
||||
* @param source The {@link CommandSourceStack} that likely originated this change, and should be the notified source for further
|
||||
* messages. Can be {@code null} if there was none and the operation shouldn't send feedback.
|
||||
* @param changedRule The {@link CarpetRule} that changed. Use {@link CarpetRule#value() changedRule.value()} to get the rule's value,
|
||||
* and pass it to {@link RuleHelper#toRuleString(Object)} to get the {@link String} version of it
|
||||
* @param userInput The {@link String} that the user entered when changing the rule, or a best-effort representation of it in case that is
|
||||
* is not available at the time (such as loading from disk or a rule being changed programmatically). Note that this value
|
||||
* may not represent the same string as converting the current value to a {@link String} via {@link RuleHelper#toRuleString(Object)},
|
||||
* given the rule implementation may have adapted the input into a different value, for example with the use of a {@link Validator}
|
||||
*/
|
||||
void ruleChanged(CommandSourceStack source, CarpetRule<?> changedRule, String userInput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link SettingsManager} with the given version, identifier and fancy name
|
||||
*
|
||||
* @param version A {@link String} with the mod's version
|
||||
* @param identifier A {@link String} with the mod's id, will be the command name
|
||||
* @param fancyName A {@link String} being the mod's fancy name.
|
||||
*/
|
||||
public SettingsManager(String version, String identifier, String fancyName)
|
||||
{
|
||||
this.version = version;
|
||||
this.identifier = identifier;
|
||||
this.fancyName = fancyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Registers a {@link RuleObserver} to changes in rules from
|
||||
* this {@link SettingsManager} instance.</p>
|
||||
*
|
||||
* @see SettingsManager#registerGlobalRuleObserver(RuleObserver)
|
||||
*
|
||||
* @param observer A {@link RuleObserver} that will be called with
|
||||
* the used {@link CommandSourceStack} and the changed
|
||||
* {@link CarpetRule}.
|
||||
*/
|
||||
public void registerRuleObserver(RuleObserver observer)
|
||||
{
|
||||
observers.add(observer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@link RuleObserver} to changes in rules from
|
||||
* <b>any</b> {@link SettingsManager} instance (unless their implementation disallows it).
|
||||
* @see SettingsManager#registerRuleObserver(RuleObserver)
|
||||
*
|
||||
* @param observer A {@link RuleObserver} that will be called with
|
||||
* the used {@link CommandSourceStack}, and the changed
|
||||
* {@link CarpetRule}.
|
||||
*/
|
||||
public static void registerGlobalRuleObserver(RuleObserver observer)
|
||||
{
|
||||
staticObservers.add(observer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A {@link String} being this {@link SettingsManager}'s
|
||||
* identifier, which is also the command name
|
||||
*/
|
||||
public String identifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns whether this {@link SettingsManager} is locked, and any rules in it should therefore not be
|
||||
* toggleable and its management command should not be available.</p>
|
||||
* @return {@code true} if this {@link SettingsManager} is locked
|
||||
*/
|
||||
public boolean locked() {
|
||||
return locked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all annotated fields with the {@link Rule} annotation
|
||||
* to this {@link SettingsManager} in order to handle them.
|
||||
* @param settingsClass The class that will be analyzed
|
||||
*/
|
||||
public void parseSettingsClass(Class<?> settingsClass)
|
||||
{
|
||||
// In the current translation system languages are not loaded this early. Ensure they are loaded
|
||||
Translations.updateLanguage();
|
||||
boolean warned = settingsClass == CarpetSettings.class; // don't warn for ourselves
|
||||
|
||||
nextRule: for (Field field : settingsClass.getDeclaredFields())
|
||||
{
|
||||
Class<? extends Rule.Condition>[] conditions;
|
||||
Rule newAnnotation = field.getAnnotation(Rule.class);
|
||||
carpet.settings.Rule oldAnnotation = field.getAnnotation(carpet.settings.Rule.class);
|
||||
if (newAnnotation != null) {
|
||||
conditions = newAnnotation.conditions();
|
||||
} else if (oldAnnotation != null) {
|
||||
conditions = oldAnnotation.condition();
|
||||
if (!warned) {
|
||||
CarpetSettings.LOG.warn("""
|
||||
Registering outdated rules for settings class '%s'!
|
||||
This won't be supported in the future and rules won't be registered!""".formatted(settingsClass.getName()));
|
||||
warned = true;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
for (Class<? extends Rule.Condition> condition : conditions) { //Should this be moved to ParsedRule.of?
|
||||
try
|
||||
{
|
||||
Constructor<? extends Condition> constr = condition.getDeclaredConstructor();
|
||||
constr.setAccessible(true);
|
||||
if (!(constr.newInstance()).shouldRegister())
|
||||
continue nextRule;
|
||||
}
|
||||
catch (ReflectiveOperationException e)
|
||||
{
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
CarpetRule<?> parsed = ParsedRule.of(field, this);
|
||||
rules.put(parsed.name(), parsed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A String {@link Iterable} with all categories
|
||||
* that the rules in this {@link SettingsManager} have.
|
||||
* @implNote This method doesn't cache the result, so each call loops through all rules and finds all present categories
|
||||
*/
|
||||
public Iterable<String> getCategories()
|
||||
{
|
||||
return getCarpetRules().stream().map(CarpetRule::categories).<String>mapMulti(Collection::forEach).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets a registered rule in this {@link SettingsManager}.</p>
|
||||
*
|
||||
* @param name The name of the rule to get
|
||||
* @return A {@link CarpetRule} with the provided name or {@code null} if none in this {@link SettingsManager} matches
|
||||
*/
|
||||
public CarpetRule<?> getCarpetRule(String name)
|
||||
{
|
||||
return rules.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return An unmodifiable {@link Collection} of the registered rules in this {@link SettingsManager}.
|
||||
*/
|
||||
public Collection<CarpetRule<?>> getCarpetRules()
|
||||
{
|
||||
return Collections.unmodifiableCollection(rules.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Adds a {@link CarpetRule} to this {@link SettingsManager}.</p>
|
||||
*
|
||||
* <p>Useful when having different {@link CarpetRule} implementations instead of a class of {@code static},
|
||||
* annotated fields.</p>
|
||||
*
|
||||
* @param rule The {@link CarpetRule} to add
|
||||
* @throws UnsupportedOperationException If a rule with that name is already present in this {@link SettingsManager}
|
||||
*/
|
||||
public void addCarpetRule(CarpetRule<?> rule) {
|
||||
if (rules.containsKey(rule.name()))
|
||||
throw new UnsupportedOperationException(fancyName + " settings manager already contains a rule with that name!");
|
||||
rules.put(rule.name(), rule);
|
||||
}
|
||||
|
||||
public void notifyRuleChanged(CommandSourceStack source, CarpetRule<?> rule, String userInput)
|
||||
{
|
||||
observers.forEach(observer -> observer.ruleChanged(source, rule, userInput));
|
||||
staticObservers.forEach(observer -> observer.ruleChanged(source, rule, userInput));
|
||||
ServerNetworkHandler.updateRuleWithConnectedClients(rule);
|
||||
switchScarpetRuleIfNeeded(source, rule); //TODO move into rule
|
||||
if (CARPET_RULE_CHANGES.isNeeded()) CARPET_RULE_CHANGES.onCarpetRuleChanges(rule, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a {@link MinecraftServer} to this {@link SettingsManager}.<br>
|
||||
* This is handled automatically by Carpet and calling it manually is not supported.
|
||||
*
|
||||
* @param server The {@link MinecraftServer} instance to be attached
|
||||
*/
|
||||
public void attachServer(MinecraftServer server)
|
||||
{
|
||||
this.server = server;
|
||||
loadConfigurationFromConf();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches the {@link MinecraftServer} of this {@link SettingsManager} and
|
||||
* resets its {@link CarpetRule}s to their default values.<br>
|
||||
* This is handled automatically by Carpet and calling it manually is not supported.
|
||||
*/
|
||||
public void detachServer()
|
||||
{
|
||||
for (CarpetRule<?> rule : rules.values()) RuleHelper.resetToDefault(rule, null);
|
||||
server = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Initializes Scarpet rules in this {@link SettingsManager}, if any.</p>
|
||||
* <p>This is handled automatically by Carpet and calling it is not supported.</p>
|
||||
*/
|
||||
public void initializeScarpetRules() { //TODO try remove
|
||||
for (CarpetRule<?> rule : rules.values())
|
||||
{
|
||||
if (rule instanceof ParsedRule<?> pr && !pr.scarpetApp.isEmpty()) {
|
||||
switchScarpetRuleIfNeeded(server.createCommandSourceStack(), pr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calling this method is not supported.
|
||||
*/
|
||||
public void inspectClientsideCommand(CommandSourceStack source, String string)
|
||||
{
|
||||
if (string.startsWith("/" + identifier + " "))
|
||||
{
|
||||
String[] res = string.split("\\s+", 3);
|
||||
if (res.length == 3)
|
||||
{
|
||||
String rule = res[1];
|
||||
String strOption = res[2];
|
||||
if (rules.containsKey(rule) && rules.get(rule).canBeToggledClientSide())
|
||||
{
|
||||
try {
|
||||
rules.get(rule).set(source, strOption);
|
||||
} catch (InvalidRuleValueException e) {
|
||||
e.notifySource(rule, source);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void switchScarpetRuleIfNeeded(CommandSourceStack source, CarpetRule<?> carpetRule) //TODO remove. This should be handled by the rule
|
||||
{
|
||||
if (carpetRule instanceof ParsedRule<?> rule && !rule.scarpetApp.isEmpty())
|
||||
{
|
||||
if (RuleHelper.getBooleanValue(rule) || (rule.type() == String.class && !rule.value().equals("false")))
|
||||
{
|
||||
CarpetServer.scriptServer.addScriptHost(source, rule.scarpetApp, s -> CommandHelper.canUseCommand(s, rule.value()), false, false, true, null);
|
||||
} else {
|
||||
CarpetServer.scriptServer.removeScriptHost(source, rule.scarpetApp, false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Path getFile()
|
||||
{
|
||||
return server.getWorldPath(LevelResource.ROOT).resolve(identifier + ".conf");
|
||||
}
|
||||
|
||||
private Collection<CarpetRule<?>> getRulesSorted()
|
||||
{
|
||||
return rules.values().stream().sorted(comparing(CarpetRule::name)).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables all {@link CarpetRule}s with the {@link RuleCategory#COMMAND} category,
|
||||
* called when the {@link SettingsManager} is {@link #locked}.
|
||||
*/
|
||||
private void disableBooleanCommands()
|
||||
{
|
||||
for (CarpetRule<?> rule : rules.values())
|
||||
{
|
||||
if (!rule.categories().contains(RuleCategory.COMMAND))
|
||||
continue;
|
||||
try {
|
||||
if (rule.suggestions().contains("false"))
|
||||
rule.set(server.createCommandSourceStack(), "false");
|
||||
else
|
||||
CarpetSettings.LOG.warn("Couldn't disable command rule "+ rule.name() + ": it doesn't suggest false as a valid option");
|
||||
} catch (InvalidRuleValueException e) {
|
||||
throw new IllegalStateException(e); // contract of CarpetRule.suggestions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeSettingsToConf(ConfigReadResult data)
|
||||
{
|
||||
if (locked)
|
||||
return;
|
||||
try (BufferedWriter fw = Files.newBufferedWriter(getFile()))
|
||||
{
|
||||
for (String key: data.ruleMap().keySet())
|
||||
{
|
||||
fw.write(key + " " + data.ruleMap().get(key));
|
||||
fw.newLine();
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
CarpetSettings.LOG.error("[CM]: failed write "+identifier+".conf config file", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<CarpetRule<?>> findStartupOverrides()
|
||||
{
|
||||
Set<String> defaults = readSettingsFromConf(getFile()).ruleMap().keySet();
|
||||
return rules.values().stream().filter(r -> defaults.contains(r.name())).
|
||||
sorted(comparing(CarpetRule::name)).toList();
|
||||
}
|
||||
|
||||
private Collection<CarpetRule<?>> getNonDefault()
|
||||
{
|
||||
return rules.values().stream().filter(Predicate.not(RuleHelper::isInDefaultValue)).sorted(comparing(CarpetRule::name)).toList();
|
||||
}
|
||||
|
||||
private void loadConfigurationFromConf()
|
||||
{
|
||||
for (CarpetRule<?> rule : rules.values()) RuleHelper.resetToDefault(rule, server.createCommandSourceStack());
|
||||
ConfigReadResult conf = readSettingsFromConf(getFile());
|
||||
locked = false;
|
||||
if (conf.locked())
|
||||
{
|
||||
CarpetSettings.LOG.info("[CM]: "+fancyName+" features are locked by the administrator");
|
||||
disableBooleanCommands();
|
||||
}
|
||||
int loadedCount = 0;
|
||||
for (String key: conf.ruleMap().keySet())
|
||||
{
|
||||
try
|
||||
{
|
||||
rules.get(key).set(server.createCommandSourceStack(), conf.ruleMap().get(key));
|
||||
loadedCount++;
|
||||
}
|
||||
catch (InvalidRuleValueException exc)
|
||||
{
|
||||
CarpetSettings.LOG.error("[CM Error]: Failed to load setting: " + key, exc);
|
||||
}
|
||||
}
|
||||
if (loadedCount > 0)
|
||||
CarpetSettings.LOG.info("[CM] Loaded " + loadedCount + " settings from " + identifier + ".conf");
|
||||
locked = conf.locked();
|
||||
}
|
||||
|
||||
|
||||
private ConfigReadResult readSettingsFromConf(Path path)
|
||||
{
|
||||
try (BufferedReader reader = Files.newBufferedReader(path))
|
||||
{
|
||||
String line = "";
|
||||
boolean confLocked = false;
|
||||
Map<String, String> result = new HashMap<String, String>();
|
||||
while ((line = reader.readLine()) != null)
|
||||
{
|
||||
line = line.replaceAll("[\\r\\n]", "");
|
||||
if ("locked".equalsIgnoreCase(line))
|
||||
{
|
||||
confLocked = true;
|
||||
}
|
||||
String[] fields = line.split("\\s+",2);
|
||||
if (fields.length > 1)
|
||||
{
|
||||
if (result.isEmpty() && fields[0].startsWith("#") || fields[1].startsWith("#"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!rules.containsKey(fields[0]))
|
||||
{
|
||||
CarpetSettings.LOG.error("[CM]: "+fancyName+" Setting " + fields[0] + " is not a valid rule - ignoring...");
|
||||
continue;
|
||||
}
|
||||
result.put(fields[0], fields[1]);
|
||||
}
|
||||
}
|
||||
return new ConfigReadResult(result, confLocked);
|
||||
}
|
||||
catch (NoSuchFileException e)
|
||||
{
|
||||
if (path.equals(getFile()) && FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT)
|
||||
{
|
||||
Path defaultsPath = FabricLoader.getInstance().getConfigDir().resolve("carpet/default_"+identifier+".conf");
|
||||
try {
|
||||
if (Files.notExists(defaultsPath))
|
||||
{
|
||||
Files.createDirectories(defaultsPath.getParent());
|
||||
Files.createFile(defaultsPath);
|
||||
try (BufferedWriter fw = Files.newBufferedWriter(defaultsPath))
|
||||
{
|
||||
fw.write("# This is " + fancyName + "'s default configuration file");
|
||||
fw.newLine();
|
||||
fw.write("# Settings specified here will be used when a world doesn't have a config file, but they will be completely "
|
||||
+ "ignored once the world has one.");
|
||||
fw.newLine();
|
||||
}
|
||||
}
|
||||
return readSettingsFromConf(defaultsPath);
|
||||
} catch (IOException e2) {
|
||||
CarpetSettings.LOG.error("Exception when loading fallback default config: ", e2);
|
||||
}
|
||||
}
|
||||
return new ConfigReadResult(new HashMap<>(), false);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
CarpetSettings.LOG.error("Exception while loading Carpet rules from config", e);
|
||||
return new ConfigReadResult(new HashMap<>(), false);
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<CarpetRule<?>> getRulesMatching(String search) {
|
||||
String lcSearch = search.toLowerCase(Locale.ROOT);
|
||||
return rules.values().stream().filter(rule ->
|
||||
{
|
||||
if (rule.name().toLowerCase(Locale.ROOT).contains(lcSearch)) return true; // substring match, case insensitive
|
||||
for (String c : rule.categories()) if (c.equals(search)) return true; // category exactly, case sensitive
|
||||
return Sets.newHashSet(RuleHelper.translatedDescription(rule).toLowerCase(Locale.ROOT).split("\\W+")).contains(lcSearch); // contains full term in description, but case insensitive
|
||||
}).sorted(comparing(CarpetRule::name)).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* A method to pretty print in markdown (useful for Github wiki/readme) the current
|
||||
* registered rules for a category to the log. Contains the name, description,
|
||||
* categories, type, defaults, whether or not they are strict, their suggested
|
||||
* values, etc.
|
||||
*
|
||||
* @param ps A {@link PrintStream} to dump the rules to, such as {@link System#out}
|
||||
* @param category A {@link String} being the category to print, {@link null} to print
|
||||
* all registered rules.
|
||||
* @return actually nothing, the int is just there for brigadier
|
||||
*
|
||||
* @apiNote While extensions are expected to be able to call this method, binary compatibility isn't
|
||||
* guaranteed, but this shouldn't be an issue given this is only intended to be ran for doc
|
||||
* generation (where version is controlled) and it's not expected to be referenced in production anyway
|
||||
*/
|
||||
public int dumpAllRulesToStream(PrintStream ps, String category)
|
||||
{
|
||||
ps.println("# "+fancyName+" Settings");
|
||||
for (CarpetRule<?> rule : new TreeMap<>(rules).values())
|
||||
{
|
||||
if (category != null && !rule.categories().contains(category))
|
||||
continue;
|
||||
ps.println("## " + rule.name());
|
||||
ps.println(RuleHelper.translatedDescription(rule)+" ");
|
||||
for (Component extra : rule.extraInfo())
|
||||
ps.println(extra.getString() + " ");
|
||||
ps.println("* Type: `" + rule.type().getSimpleName() + "` ");
|
||||
ps.println("* Default value: `" + RuleHelper.toRuleString(rule.defaultValue()) + "` ");
|
||||
String options = rule.suggestions().stream().map(s -> "`" + s + "`").collect(Collectors.joining(", "));
|
||||
if (!options.isEmpty()) ps.println((rule instanceof ParsedRule<?> pr && pr.isStrict?"* Required":"* Suggested")+" options: " + options + " ");
|
||||
ps.println("* Categories: " + rule.categories().stream().map(s -> "`" + s.toUpperCase(Locale.ROOT) + "`").collect(Collectors.joining(", ")) + " ");
|
||||
if (rule instanceof ParsedRule<?>)
|
||||
{
|
||||
boolean preamble = false;
|
||||
for (Validator<?> validator : ((ParsedRule<?>) rule).realValidators)
|
||||
{
|
||||
if (validator.description() != null)
|
||||
{
|
||||
if (!preamble)
|
||||
{
|
||||
ps.println("* Additional notes: ");
|
||||
preamble = true;
|
||||
}
|
||||
ps.println(" * "+validator.description()+" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
ps.println(" ");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
private CarpetRule<?> contextRule(CommandContext<CommandSourceStack> ctx) throws CommandSyntaxException
|
||||
{
|
||||
String ruleName = StringArgumentType.getString(ctx, "rule");
|
||||
CarpetRule<?> rule = getCarpetRule(ruleName);
|
||||
if (rule == null)
|
||||
throw new SimpleCommandExceptionType(Messenger.c("rb "+ tr(TranslationKeys.UNKNOWN_RULE) + ": "+ruleName)).create();
|
||||
return rule;
|
||||
}
|
||||
|
||||
static CompletableFuture<Suggestions> suggestMatchingContains(Stream<String> stream, SuggestionsBuilder suggestionsBuilder) {
|
||||
List<String> regularSuggestionList = new ArrayList<>();
|
||||
List<String> smartSuggestionList = new ArrayList<>();
|
||||
String query = suggestionsBuilder.getRemaining().toLowerCase(Locale.ROOT);
|
||||
stream.forEach((listItem) -> {
|
||||
// Regex camelCase Search
|
||||
var words = Arrays.stream(listItem.split("(?<!^)(?=[A-Z])")).map(s -> s.toLowerCase(Locale.ROOT)).collect(Collectors.toList());
|
||||
var prefixes = new ArrayList<String>(words.size());
|
||||
for (int i = 0; i < words.size(); i++)
|
||||
prefixes.add(String.join("", words.subList(i, words.size())));
|
||||
if (prefixes.stream().anyMatch(s -> s.startsWith(query))) {
|
||||
smartSuggestionList.add(listItem);
|
||||
}
|
||||
// Regular prefix matching, reference: CommandSource.suggestMatching
|
||||
if (SharedSuggestionProvider.matchesSubStr(query, listItem.toLowerCase(Locale.ROOT))) {
|
||||
regularSuggestionList.add(listItem);
|
||||
}
|
||||
});
|
||||
var filteredSuggestionList = regularSuggestionList.isEmpty() ? smartSuggestionList : regularSuggestionList;
|
||||
Objects.requireNonNull(suggestionsBuilder);
|
||||
filteredSuggestionList.forEach(suggestionsBuilder::suggest);
|
||||
return suggestionsBuilder.buildFuture();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the settings command for this {@link SettingsManager}.<br>
|
||||
* It is handled automatically by Carpet.
|
||||
* @param dispatcher The current {@link CommandDispatcher}
|
||||
* @param commandBuildContext The current {@link CommandBuildContext}
|
||||
*/
|
||||
public void registerCommand(final CommandDispatcher<CommandSourceStack> dispatcher, final CommandBuildContext commandBuildContext)
|
||||
{
|
||||
if (dispatcher.getRoot().getChildren().stream().anyMatch(node -> node.getName().equalsIgnoreCase(identifier)))
|
||||
{
|
||||
CarpetSettings.LOG.error("Failed to add settings command for " + identifier + ". It is masking previous command.");
|
||||
return;
|
||||
}
|
||||
|
||||
LiteralArgumentBuilder<CommandSourceStack> literalargumentbuilder = literal(identifier).requires((player) ->
|
||||
CommandHelper.canUseCommand(player, CarpetSettings.carpetCommandPermissionLevel) && !locked());
|
||||
|
||||
literalargumentbuilder.executes((context)-> listAllSettings(context.getSource())).
|
||||
then(literal("list").
|
||||
executes( (c) -> listSettings(c.getSource(), String.format(tr(TranslationKeys.ALL_MOD_SETTINGS), fancyName),
|
||||
getRulesSorted())).
|
||||
then(literal("defaults").
|
||||
executes( (c)-> listSettings(c.getSource(),
|
||||
String.format(tr(TranslationKeys.CURRENT_FROM_FILE_HEADER), fancyName, (identifier+".conf")),
|
||||
findStartupOverrides()))).
|
||||
then(argument("tag",StringArgumentType.word()).
|
||||
suggests( (c, b)->suggest(getCategories(), b)).
|
||||
executes( (c) -> listSettings(c.getSource(),
|
||||
String.format(tr(TranslationKeys.MOD_SETTINGS_MATCHING), fancyName, RuleHelper.translatedCategory(identifier(),StringArgumentType.getString(c, "tag"))),
|
||||
getRulesMatching(StringArgumentType.getString(c, "tag")))))).
|
||||
then(literal("removeDefault").
|
||||
requires(s -> !locked()).
|
||||
then(argument("rule", StringArgumentType.word()).
|
||||
suggests( (c, b) -> suggestMatchingContains(getRulesSorted().stream().map(CarpetRule::name), b)).
|
||||
executes((c) -> removeDefault(c.getSource(), contextRule(c))))).
|
||||
then(literal("setDefault").
|
||||
requires(s -> !locked()).
|
||||
then(argument("rule", StringArgumentType.word()).
|
||||
suggests( (c, b) -> suggestMatchingContains(getRulesSorted().stream().map(CarpetRule::name), b)).
|
||||
then(argument("value", StringArgumentType.greedyString()).
|
||||
suggests((c, b)-> suggest(contextRule(c).suggestions(), b)).
|
||||
executes((c) -> setDefault(c.getSource(), contextRule(c), StringArgumentType.getString(c, "value")))))).
|
||||
then(argument("rule", StringArgumentType.word()).
|
||||
suggests( (c, b) -> suggestMatchingContains(getRulesSorted().stream().map(CarpetRule::name), b)).
|
||||
requires(s -> !locked() ).
|
||||
executes( (c) -> displayRuleMenu(c.getSource(), contextRule(c))).
|
||||
then(argument("value", StringArgumentType.greedyString()).
|
||||
suggests((c, b)-> suggest(contextRule(c).suggestions(),b)).
|
||||
executes((c) -> setRule(c.getSource(), contextRule(c), StringArgumentType.getString(c, "value")))));
|
||||
|
||||
dispatcher.register(literalargumentbuilder);
|
||||
}
|
||||
|
||||
private int displayRuleMenu(CommandSourceStack source, CarpetRule<?> rule) //TODO check if there's dupe code around options buttons
|
||||
{
|
||||
String displayName = RuleHelper.translatedName(rule);
|
||||
|
||||
Messenger.m(source, "");
|
||||
Messenger.m(source, "wb "+ displayName ,"!/"+identifier+" "+rule.name(),"^g refresh");
|
||||
Messenger.m(source, "w "+ RuleHelper.translatedDescription(rule));
|
||||
|
||||
rule.extraInfo().forEach(s -> Messenger.m(source, s));
|
||||
|
||||
List<Component> tags = new ArrayList<>();
|
||||
tags.add(Messenger.c("w "+ tr(TranslationKeys.TAGS)+": "));
|
||||
for (String t: rule.categories())
|
||||
{
|
||||
String translated = RuleHelper.translatedCategory(identifier(), t);
|
||||
tags.add(Messenger.c("c ["+ translated +"]", "^g "+ String.format(tr(TranslationKeys.LIST_ALL_CATEGORY), translated),"!/"+identifier+" list "+t));
|
||||
tags.add(Messenger.c("w , "));
|
||||
}
|
||||
tags.remove(tags.size() - 1);
|
||||
Messenger.m(source, tags.toArray(new Object[0]));
|
||||
|
||||
Messenger.m(source, "w "+ tr(TranslationKeys.CURRENT_VALUE)+": ", String.format("%s %s (%s value)", RuleHelper.getBooleanValue(rule) ? "lb" : "nb", RuleHelper.toRuleString(rule.value()), RuleHelper.isInDefaultValue(rule) ? "default" : "modified"));
|
||||
List<Component> options = new ArrayList<>();
|
||||
options.add(Messenger.c("w Options: ", "y [ "));
|
||||
for (String o: rule.suggestions())
|
||||
{
|
||||
options.add(makeSetRuleButton(rule, o, false));
|
||||
options.add(Messenger.c("w "));
|
||||
}
|
||||
options.remove(options.size()-1);
|
||||
options.add(Messenger.c("y ]"));
|
||||
Messenger.m(source, options.toArray(new Object[0]));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private int setRule(CommandSourceStack source, CarpetRule<?> rule, String newValue)
|
||||
{
|
||||
try {
|
||||
rule.set(source, newValue);
|
||||
Messenger.m(source, "w "+rule.toString()+", ", "c ["+ tr(TranslationKeys.CHANGE_PERMANENTLY)+"?]",
|
||||
"^w "+String.format(tr(TranslationKeys.CHANGE_PERMANENTLY_HOVER), identifier+".conf"),
|
||||
"?/"+identifier+" setDefault "+rule.name()+" "+ RuleHelper.toRuleString(rule.value()));
|
||||
} catch (InvalidRuleValueException e) {
|
||||
e.notifySource(rule.name(), source);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// stores different defaults in the file
|
||||
private int setDefault(CommandSourceStack source, CarpetRule<?> rule, String stringValue)
|
||||
{
|
||||
if (locked()) return 0;
|
||||
if (!rules.containsKey(rule.name())) return 0;
|
||||
ConfigReadResult conf = readSettingsFromConf(getFile());
|
||||
conf.ruleMap().put(rule.name(), stringValue);
|
||||
writeSettingsToConf(conf); // this may feels weird, but if conf
|
||||
// is locked, it will never reach this point.
|
||||
try {
|
||||
rule.set(source, stringValue);
|
||||
Messenger.m(source ,"gi "+String.format(tr(TranslationKeys.DEFAULT_SET), RuleHelper.translatedName(rule), stringValue));
|
||||
} catch (InvalidRuleValueException e) {
|
||||
e.notifySource(rule.name(), source);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
// removes overrides of the default values in the file
|
||||
private int removeDefault(CommandSourceStack source, CarpetRule<?> rule)
|
||||
{
|
||||
if (locked) return 0;
|
||||
if (!rules.containsKey(rule.name())) return 0;
|
||||
ConfigReadResult conf = readSettingsFromConf(getFile());
|
||||
conf.ruleMap().remove(rule.name());
|
||||
writeSettingsToConf(conf);
|
||||
RuleHelper.resetToDefault(rules.get(rule.name()), source);
|
||||
Messenger.m(source ,"gi "+String.format(tr(TranslationKeys.DEFAULT_REMOVED), RuleHelper.translatedName(rule)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
private Component displayInteractiveSetting(CarpetRule<?> rule)
|
||||
{
|
||||
String displayName = RuleHelper.translatedName(rule);
|
||||
List<Object> args = new ArrayList<>();
|
||||
args.add("w - "+ displayName +" ");
|
||||
args.add("!/"+identifier+" "+rule.name());
|
||||
args.add("^y "+RuleHelper.translatedDescription(rule));
|
||||
for (String option: rule.suggestions())
|
||||
{
|
||||
args.add(makeSetRuleButton(rule, option, true));
|
||||
args.add("w ");
|
||||
}
|
||||
if (!rule.suggestions().contains(RuleHelper.toRuleString(rule.value())))
|
||||
{
|
||||
args.add(makeSetRuleButton(rule, RuleHelper.toRuleString(rule.value()), true));
|
||||
args.add("w ");
|
||||
}
|
||||
args.remove(args.size()-1);
|
||||
return Messenger.c(args.toArray(new Object[0]));
|
||||
}
|
||||
|
||||
private Component makeSetRuleButton(CarpetRule<?> rule, String option, boolean brackets)
|
||||
{
|
||||
String style = RuleHelper.isInDefaultValue(rule)?"g":(option.equalsIgnoreCase(RuleHelper.toRuleString(rule.defaultValue()))?"e":"y");
|
||||
if (option.equalsIgnoreCase(RuleHelper.toRuleString(rule.value())))
|
||||
{
|
||||
style = style + "u";
|
||||
if (option.equalsIgnoreCase(RuleHelper.toRuleString(rule.defaultValue())))
|
||||
style = style + "b";
|
||||
}
|
||||
String component = style + (brackets ? " [" : " ") + option + (brackets ? "]" : "");
|
||||
if (option.equalsIgnoreCase(RuleHelper.toRuleString(rule.value())))
|
||||
return Messenger.c(component);
|
||||
return Messenger.c(component, "^g "+ tr(TranslationKeys.SWITCH_TO).formatted(option + (option.equals(RuleHelper.toRuleString(rule.defaultValue()))?" (default)":"")), "?/"+identifier+" " + rule.name() + " " + option);
|
||||
}
|
||||
|
||||
private int listSettings(CommandSourceStack source, String title, Collection<CarpetRule<?>> settings_list)
|
||||
{
|
||||
|
||||
Messenger.m(source,String.format("wb %s:",title));
|
||||
settings_list.forEach(e -> Messenger.m(source, displayInteractiveSetting(e)));
|
||||
return settings_list.size();
|
||||
}
|
||||
private int listAllSettings(CommandSourceStack source)
|
||||
{
|
||||
int count = listSettings(source, String.format(tr(TranslationKeys.CURRENT_SETTINGS_HEADER), fancyName), getNonDefault());
|
||||
|
||||
if (version != null)
|
||||
Messenger.m(source, "g "+fancyName+" "+ tr(TranslationKeys.VERSION) + ": " + version);
|
||||
|
||||
List<String> tags = new ArrayList<>();
|
||||
tags.add("w " + tr(TranslationKeys.BROWSE_CATEGORIES) + ":\n");
|
||||
for (String t : getCategories())
|
||||
{
|
||||
String translated = RuleHelper.translatedCategory(identifier(), t);
|
||||
String translatedPlus = !translated.equals(t) ? "%s (%s)".formatted(translated, t) : t;
|
||||
tags.add("c [" + translated +"]");
|
||||
tags.add("^g " + String.format(tr(TranslationKeys.LIST_ALL_CATEGORY), translatedPlus));
|
||||
tags.add("!/"+identifier+" list " + t);
|
||||
tags.add("w ");
|
||||
}
|
||||
tags.remove(tags.size() - 1);
|
||||
Messenger.m(source, tags.toArray(new Object[0]));
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package carpet.api.settings;
|
||||
|
||||
import carpet.utils.Messenger;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
|
||||
/**
|
||||
* <p>A {@link Validator} is a class that is able to validate the values given to a {@link CarpetRule}, cancelling rule
|
||||
* modification if the value is not valid or even changing the value to a different one if needed.</p>
|
||||
*
|
||||
* <p>Validators are used in the default implementation, {@link ParsedRule}, as the way of validating most input (other than validating
|
||||
* it can actually be introduced in the rule), but can (and may) also be used in other {@link CarpetRule} implementations, though those are not
|
||||
* required to.</p>
|
||||
*
|
||||
* @see #validate(CommandSourceStack, CarpetRule, Object, String)
|
||||
*
|
||||
* @param <T> The type of the rule's value
|
||||
*/
|
||||
public abstract class Validator<T>
|
||||
{
|
||||
/**
|
||||
* <p>Validates whether the value passed to the given {@link CarpetRule} is valid as a new value for it.</p>
|
||||
*
|
||||
* <p>Validators can also change the value that the rule is going to be set to by returning a value different to the
|
||||
* one that has been passed to them</p>
|
||||
*
|
||||
* <p>This method must not throw any exceptions.</p>
|
||||
*
|
||||
* @param source The {@link CommandSourceStack} that originated this change, and should be further notified
|
||||
* about it. May be {@code null} during rule synchronization.
|
||||
* @param changingRule The {@link CarpetRule} that is being changed
|
||||
* @param newValue The new value that is being set to the rule
|
||||
* @param userInput The value that is being given to this rule by the user as a {@link String}, or a best-effort representation
|
||||
* of the value as a {@link String}. This value may not correspond to the result of {@link RuleHelper#toRuleString(Object)}
|
||||
* if other validators have modified the value, it's just a representation of the user's input.
|
||||
* @return The new value to set the rule to instead, can return the {@code newValue} if the given value is correct.
|
||||
* Returns {@code null} if the given value is not correct.
|
||||
*/
|
||||
public abstract T validate(CommandSourceStack source, CarpetRule<T> changingRule, T newValue, String userInput);
|
||||
|
||||
/**
|
||||
* @return A description of this {@link Validator}. It is used in the default {@link #notifyFailure(CommandSourceStack, CarpetRule, String)}
|
||||
* implementation and to add extra information in {@link SettingsManager#printAllRulesToLog(String)}
|
||||
*/
|
||||
public String description() {return null;}
|
||||
/**
|
||||
* <p>Called after failing validation of the {@link CarpetRule} in order to notify the causing {@link CommandSourceStack} about the
|
||||
* failure.</p>
|
||||
*
|
||||
* @param source The {@link CommandSourceStack} that originated this change. It will never be {@code null}
|
||||
* @param currentRule The {@link CarpetRule} that failed verification
|
||||
* @param providedValue The {@link String} that was provided to the changing rule
|
||||
*/
|
||||
public void notifyFailure(CommandSourceStack source, CarpetRule<T> currentRule, String providedValue)
|
||||
{
|
||||
Messenger.m(source, "r Wrong value for " + currentRule.name() + ": " + providedValue);
|
||||
if (description() != null)
|
||||
Messenger.m(source, "r " + description());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package carpet.api.settings;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import carpet.utils.CommandHelper;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
|
||||
/**
|
||||
* <p>A collection of standard {@link Validator validators} you can use in your rules.</p>
|
||||
*
|
||||
* @see Rule
|
||||
* @see Rule#validators()
|
||||
*
|
||||
*/
|
||||
public final class Validators {
|
||||
private Validators() {};
|
||||
|
||||
/**
|
||||
* <p>A {@link Validator} that checks whether the given {@link String} value was a valid command level as Carpet allows them,
|
||||
* so either a number from 0 to 4, or one of the keywords {@code true}, {@code false} or {@code ops} </p>
|
||||
*
|
||||
* <p>While there is no public API method for checking whether a source can execute a command,
|
||||
* {@link CommandHelper#canUseCommand(CommandSourceStack, Object)} is not expected to change anytime soon.</p>
|
||||
*
|
||||
*/
|
||||
public static class CommandLevel extends Validator<String> {
|
||||
@Deprecated(forRemoval = true) // internal use only, will be made pckg private when phasing out old api
|
||||
public static final List<String> OPTIONS = List.of("true", "false", "ops", "0", "1", "2", "3", "4");
|
||||
@Override
|
||||
public String validate(CommandSourceStack source, CarpetRule<String> currentRule, String newValue, String userString) {
|
||||
if (!OPTIONS.contains(newValue))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return newValue;
|
||||
}
|
||||
@Override public String description() { return "Can be limited to 'ops' only, true/false for everyone/no one, or a custom permission level";}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>A {@link Validator} that checks whether the entered number is equal or greater than {@code 0}.</p>
|
||||
*/
|
||||
public static class NonNegativeNumber<T extends Number> extends Validator<T>
|
||||
{
|
||||
@Override
|
||||
public T validate(CommandSourceStack source, CarpetRule<T> currentRule, T newValue, String string)
|
||||
{
|
||||
return newValue.doubleValue() >= 0 ? newValue : null;
|
||||
}
|
||||
@Override
|
||||
public String description() { return "Must be a positive number or 0";}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>A {@link Validator} that checks whether the entered number is between 0 and 1, inclusive.</p>
|
||||
*/
|
||||
public static class Probablity<T extends Number> extends Validator<T>
|
||||
{
|
||||
@Override
|
||||
public T validate(CommandSourceStack source, CarpetRule<T> currentRule, T newValue, String string)
|
||||
{
|
||||
return (newValue.doubleValue() >= 0 && newValue.doubleValue() <= 1 )? newValue : null;
|
||||
}
|
||||
@Override
|
||||
public String description() { return "Must be between 0 and 1";}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package carpet.commands;
|
||||
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.settings.SettingsManager;
|
||||
import carpet.utils.CommandHelper;
|
||||
import carpet.utils.DistanceCalculator;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
@@ -17,7 +17,7 @@ public class DistanceCommand
|
||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher, CommandBuildContext commandBuildContext)
|
||||
{
|
||||
LiteralArgumentBuilder<CommandSourceStack> command = literal("distance").
|
||||
requires((player) -> SettingsManager.canUseCommand(player, CarpetSettings.commandDistance)).
|
||||
requires((player) -> CommandHelper.canUseCommand(player, CarpetSettings.commandDistance)).
|
||||
then(literal("from").
|
||||
executes( (c) -> DistanceCalculator.setStart(c.getSource(), c.getSource().getPosition())).
|
||||
then(argument("from", Vec3Argument.vec3()).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package carpet.commands;
|
||||
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.settings.SettingsManager;
|
||||
import carpet.utils.CommandHelper;
|
||||
import carpet.utils.Messenger;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.mojang.brigadier.Command;
|
||||
@@ -40,7 +40,7 @@ public class DrawCommand
|
||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher, final CommandBuildContext context)
|
||||
{
|
||||
LiteralArgumentBuilder<CommandSourceStack> command = literal("draw").
|
||||
requires((player) -> SettingsManager.canUseCommand(player, CarpetSettings.commandDraw)).
|
||||
requires((player) -> CommandHelper.canUseCommand(player, CarpetSettings.commandDraw)).
|
||||
then(literal("sphere").
|
||||
then(argument("center", BlockPosArgument.blockPos()).
|
||||
then(argument("radius", IntegerArgumentType.integer(1)).
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package carpet.commands;
|
||||
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.settings.SettingsManager;
|
||||
import carpet.utils.BlockInfo;
|
||||
import carpet.utils.CommandHelper;
|
||||
import carpet.utils.Messenger;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
@@ -26,7 +26,7 @@ public class InfoCommand
|
||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher, CommandBuildContext commandBuildContext)
|
||||
{
|
||||
LiteralArgumentBuilder<CommandSourceStack> command = literal("info").
|
||||
requires((player) -> SettingsManager.canUseCommand(player, CarpetSettings.commandInfo)).
|
||||
requires((player) -> CommandHelper.canUseCommand(player, CarpetSettings.commandInfo)).
|
||||
then(literal("block").
|
||||
then(argument("block position", BlockPosArgument.blockPos()).
|
||||
executes( (c) -> infoBlock(
|
||||
|
||||
@@ -3,7 +3,7 @@ package carpet.commands;
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.logging.Logger;
|
||||
import carpet.logging.LoggerRegistry;
|
||||
import carpet.settings.SettingsManager;
|
||||
import carpet.utils.CommandHelper;
|
||||
import carpet.utils.Messenger;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
@@ -29,7 +29,7 @@ public class LogCommand
|
||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher, CommandBuildContext commandBuildContext)
|
||||
{
|
||||
LiteralArgumentBuilder<CommandSourceStack> literalargumentbuilder = Commands.literal("log").
|
||||
requires((player) -> SettingsManager.canUseCommand(player, CarpetSettings.commandLog)).
|
||||
requires((player) -> CommandHelper.canUseCommand(player, CarpetSettings.commandLog)).
|
||||
executes((context) -> listLogs(context.getSource())).
|
||||
then(Commands.literal("clear").
|
||||
executes( (c) -> unsubFromAll(c.getSource(), c.getSource().getTextName())).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package carpet.commands;
|
||||
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.settings.SettingsManager;
|
||||
import carpet.utils.CommandHelper;
|
||||
import carpet.utils.MobAI;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
@@ -20,7 +20,7 @@ public class MobAICommand
|
||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher, final CommandBuildContext commandBuildContext)
|
||||
{
|
||||
LiteralArgumentBuilder<CommandSourceStack> command = literal("track").
|
||||
requires((player) -> SettingsManager.canUseCommand(player, CarpetSettings.commandTrackAI)).
|
||||
requires((player) -> CommandHelper.canUseCommand(player, CarpetSettings.commandTrackAI)).
|
||||
then(argument("entity type", EntitySummonArgument.id()).
|
||||
|
||||
suggests( (c, b) -> suggest(MobAI.availbleTypes(), b)).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package carpet.commands;
|
||||
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.settings.SettingsManager;
|
||||
import carpet.utils.CommandHelper;
|
||||
import carpet.utils.Messenger;
|
||||
import carpet.utils.PerimeterDiagnostics;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
@@ -25,7 +25,7 @@ public class PerimeterInfoCommand
|
||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher, CommandBuildContext commandBuildContext)
|
||||
{
|
||||
LiteralArgumentBuilder<CommandSourceStack> command = literal("perimeterinfo").
|
||||
requires((player) -> SettingsManager.canUseCommand(player, CarpetSettings.commandPerimeterInfo)).
|
||||
requires((player) -> CommandHelper.canUseCommand(player, CarpetSettings.commandPerimeterInfo)).
|
||||
executes( (c) -> perimeterDiagnose(
|
||||
c.getSource(),
|
||||
new BlockPos(c.getSource().getPosition()),
|
||||
|
||||
@@ -4,7 +4,7 @@ import carpet.helpers.EntityPlayerActionPack;
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.fakes.ServerPlayerEntityInterface;
|
||||
import carpet.patches.EntityPlayerMPFake;
|
||||
import carpet.settings.SettingsManager;
|
||||
import carpet.utils.CommandHelper;
|
||||
import carpet.utils.Messenger;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.brigadier.Command;
|
||||
@@ -53,7 +53,7 @@ public class PlayerCommand
|
||||
.map(GameType::getName)
|
||||
.toArray(String[]::new);
|
||||
LiteralArgumentBuilder<CommandSourceStack> literalargumentbuilder = literal("player")
|
||||
.requires((player) -> SettingsManager.canUseCommand(player, CarpetSettings.commandPlayer))
|
||||
.requires((player) -> CommandHelper.canUseCommand(player, CarpetSettings.commandPlayer))
|
||||
.then(argument("player", StringArgumentType.word())
|
||||
.suggests( (c, b) -> suggest(getPlayers(c.getSource()), b))
|
||||
.then(literal("stop").executes(PlayerCommand::stop))
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package carpet.commands;
|
||||
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.settings.SettingsManager;
|
||||
import carpet.utils.CommandHelper;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import net.minecraft.commands.CommandBuildContext;
|
||||
@@ -19,7 +20,7 @@ public class ProfileCommand
|
||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher, CommandBuildContext commandBuildContext)
|
||||
{
|
||||
LiteralArgumentBuilder<CommandSourceStack> literalargumentbuilder = literal("profile").
|
||||
requires((player) -> SettingsManager.canUseCommand(player, CarpetSettings.commandProfile)).
|
||||
requires((player) -> CommandHelper.canUseCommand(player, CarpetSettings.commandProfile)).
|
||||
executes( (c) -> healthReport(c.getSource(), 100)).
|
||||
then(literal("health").
|
||||
executes( (c) -> healthReport(c.getSource(), 100)).
|
||||
|
||||
@@ -13,8 +13,8 @@ import carpet.script.Tokenizer;
|
||||
import carpet.script.exception.CarpetExpressionException;
|
||||
import carpet.script.value.FunctionValue;
|
||||
import carpet.script.value.Value;
|
||||
import carpet.settings.SettingsManager;
|
||||
import carpet.utils.CarpetProfiler;
|
||||
import carpet.utils.CommandHelper;
|
||||
import carpet.utils.Messenger;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
@@ -140,7 +140,7 @@ public class ScriptCommand
|
||||
LiteralArgumentBuilder<CommandSourceStack> u = literal("resume").
|
||||
executes( (cc) -> { CarpetServer.scriptServer.stopAll = false; return 1;});
|
||||
LiteralArgumentBuilder<CommandSourceStack> l = literal("run").
|
||||
requires((player) -> SettingsManager.canUseCommand(player, CarpetSettings.commandScriptACE)).
|
||||
requires((player) -> CommandHelper.canUseCommand(player, CarpetSettings.commandScriptACE)).
|
||||
then(argument("expr", StringArgumentType.greedyString()).suggests(ScriptCommand::suggestCode).
|
||||
executes((cc) -> compute(
|
||||
cc,
|
||||
@@ -266,7 +266,7 @@ public class ScriptCommand
|
||||
BlockPredicateArgument.getBlockPredicate(cc, "filter"),
|
||||
"outline"
|
||||
)))))))));
|
||||
LiteralArgumentBuilder<CommandSourceStack> a = literal("load").requires( (player) -> SettingsManager.canUseCommand(player, CarpetSettings.commandScriptACE) ).
|
||||
LiteralArgumentBuilder<CommandSourceStack> a = literal("load").requires( (player) -> CommandHelper.canUseCommand(player, CarpetSettings.commandScriptACE) ).
|
||||
then(argument("app", StringArgumentType.word()).
|
||||
suggests( (cc, bb) -> suggest(CarpetServer.scriptServer.listAvailableModules(true),bb)).
|
||||
executes((cc) ->
|
||||
@@ -283,7 +283,7 @@ public class ScriptCommand
|
||||
)
|
||||
)
|
||||
);
|
||||
LiteralArgumentBuilder<CommandSourceStack> f = literal("unload").requires( (player) -> SettingsManager.canUseCommand(player, CarpetSettings.commandScriptACE) ).
|
||||
LiteralArgumentBuilder<CommandSourceStack> f = literal("unload").requires( (player) -> CommandHelper.canUseCommand(player, CarpetSettings.commandScriptACE) ).
|
||||
then(argument("app", StringArgumentType.word()).
|
||||
suggests( (cc, bb) -> suggest(CarpetServer.scriptServer.unloadableModules,bb)).
|
||||
executes((cc) ->
|
||||
@@ -292,7 +292,7 @@ public class ScriptCommand
|
||||
return success?1:0;
|
||||
}));
|
||||
|
||||
LiteralArgumentBuilder<CommandSourceStack> q = literal("event").requires( (player) -> SettingsManager.canUseCommand(player, CarpetSettings.commandScriptACE) ).
|
||||
LiteralArgumentBuilder<CommandSourceStack> q = literal("event").requires( (player) -> CommandHelper.canUseCommand(player, CarpetSettings.commandScriptACE) ).
|
||||
executes( (cc) -> listEvents(cc.getSource())).
|
||||
then(literal("add_to").
|
||||
then(argument("event", StringArgumentType.word()).
|
||||
@@ -327,11 +327,11 @@ public class ScriptCommand
|
||||
StringArgumentType.getString(cc, "call")
|
||||
)?1:0))));
|
||||
|
||||
LiteralArgumentBuilder<CommandSourceStack> d = literal("download").requires((player) -> SettingsManager.canUseCommand(player, CarpetSettings.commandScriptACE)).
|
||||
LiteralArgumentBuilder<CommandSourceStack> d = literal("download").requires((player) -> CommandHelper.canUseCommand(player, CarpetSettings.commandScriptACE)).
|
||||
then(argument("path", StringArgumentType.greedyString()).
|
||||
suggests(ScriptCommand::suggestDownloadableApps).
|
||||
executes(cc-> AppStoreManager.downloadScript(cc.getSource(), StringArgumentType.getString(cc,"path"))));
|
||||
LiteralArgumentBuilder<CommandSourceStack> r = literal("remove").requires( (player) -> SettingsManager.canUseCommand(player, CarpetSettings.commandScriptACE) ).
|
||||
LiteralArgumentBuilder<CommandSourceStack> r = literal("remove").requires( (player) -> CommandHelper.canUseCommand(player, CarpetSettings.commandScriptACE) ).
|
||||
then(argument("app", StringArgumentType.word()).
|
||||
suggests( (cc, bb) -> suggest(CarpetServer.scriptServer.unloadableModules,bb)).
|
||||
executes((cc) ->
|
||||
@@ -341,10 +341,10 @@ public class ScriptCommand
|
||||
}));
|
||||
|
||||
dispatcher.register(literal("script").
|
||||
requires((player) -> SettingsManager.canUseCommand(player, CarpetSettings.commandScript)).
|
||||
requires((player) -> CommandHelper.canUseCommand(player, CarpetSettings.commandScript)).
|
||||
then(b).then(u).then(o).then(l).then(s).then(c).then(h).then(i).then(e).then(t).then(a).then(f).then(q).then(d).then(r));
|
||||
dispatcher.register(literal("script").
|
||||
requires((player) -> SettingsManager.canUseCommand(player, CarpetSettings.commandScript)).
|
||||
requires((player) -> CommandHelper.canUseCommand(player, CarpetSettings.commandScript)).
|
||||
then(literal("in").
|
||||
then(argument("app", StringArgumentType.word()).
|
||||
suggests( (cc, bb) -> suggest(CarpetServer.scriptServer.modules.keySet(), bb)).
|
||||
|
||||
@@ -4,7 +4,7 @@ import carpet.CarpetSettings;
|
||||
import carpet.fakes.SpawnGroupInterface;
|
||||
import carpet.helpers.HopperCounter;
|
||||
import carpet.helpers.TickSpeed;
|
||||
import carpet.settings.SettingsManager;
|
||||
import carpet.utils.CommandHelper;
|
||||
import carpet.utils.Messenger;
|
||||
import carpet.utils.SpawnReporter;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
@@ -37,13 +37,12 @@ import static net.minecraft.commands.Commands.argument;
|
||||
import static net.minecraft.commands.Commands.literal;
|
||||
import static net.minecraft.commands.SharedSuggestionProvider.suggest;
|
||||
|
||||
|
||||
public class SpawnCommand
|
||||
{
|
||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher, CommandBuildContext commandBuildContext)
|
||||
{
|
||||
LiteralArgumentBuilder<CommandSourceStack> literalargumentbuilder = literal("spawn").
|
||||
requires((player) -> SettingsManager.canUseCommand(player, CarpetSettings.commandSpawn));
|
||||
requires((player) -> CommandHelper.canUseCommand(player, CarpetSettings.commandSpawn));
|
||||
|
||||
literalargumentbuilder.
|
||||
then(literal("list").
|
||||
|
||||
@@ -17,9 +17,9 @@ public class TestCommand
|
||||
{
|
||||
dispatcher.register(literal("testcarpet").
|
||||
then(literal("dump").
|
||||
executes((c) -> CarpetServer.settingsManager.printAllRulesToLog(null)).
|
||||
executes((c) -> CarpetServer.settingsManager.dumpAllRulesToStream(System.out, null)).
|
||||
then(argument("category", word()).
|
||||
executes( (c) -> CarpetServer.settingsManager.printAllRulesToLog(getString(c, "category"))))).
|
||||
executes( (c) -> CarpetServer.settingsManager.dumpAllRulesToStream(System.out, getString(c, "category"))))).
|
||||
then(argument("first",word()).
|
||||
executes( (c)-> test(c, getString(c, "first")+" 1"))).
|
||||
then(argument("second", word()).
|
||||
|
||||
@@ -3,8 +3,8 @@ package carpet.commands;
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.helpers.TickSpeed;
|
||||
import carpet.network.ServerNetworkHandler;
|
||||
import carpet.settings.SettingsManager;
|
||||
import carpet.utils.CarpetProfiler;
|
||||
import carpet.utils.CommandHelper;
|
||||
import carpet.utils.Messenger;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
@@ -29,7 +29,7 @@ public class TickCommand
|
||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher, CommandBuildContext commandBuildContext)
|
||||
{
|
||||
LiteralArgumentBuilder<CommandSourceStack> literalargumentbuilder = literal("tick").
|
||||
requires((player) -> SettingsManager.canUseCommand(player, CarpetSettings.commandTick)).
|
||||
requires((player) -> CommandHelper.canUseCommand(player, CarpetSettings.commandTick)).
|
||||
then(literal("rate").
|
||||
executes((c) -> queryTps(c.getSource())).
|
||||
then(argument("rate", floatArg(0.1F, 500.0F)).
|
||||
|
||||
@@ -2,7 +2,7 @@ package carpet.mixins;
|
||||
|
||||
import carpet.CarpetServer;
|
||||
import carpet.network.CarpetClient;
|
||||
import carpet.settings.SettingsManager;
|
||||
import carpet.api.settings.SettingsManager;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
@@ -25,7 +25,7 @@ public class LocalPlayer_clientCommandMixin
|
||||
LocalPlayer playerSource = (LocalPlayer)(Object) this;
|
||||
CarpetServer.settingsManager.inspectClientsideCommand(playerSource.createCommandSourceStack(), string);
|
||||
CarpetServer.extensions.forEach(e -> {
|
||||
SettingsManager sm = e.customSettingsManager();
|
||||
SettingsManager sm = e.extensionSettingsManager();
|
||||
if (sm != null) sm.inspectClientsideCommand(playerSource.createCommandSourceStack(), string);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -62,6 +62,6 @@ public abstract class MinecraftServer_coreMixin
|
||||
if (CarpetSettings.spawnChunksSize != 11)
|
||||
CarpetSettings.ChangeSpawnChunksValidator.changeSpawnSize(CarpetSettings.spawnChunksSize);
|
||||
|
||||
CarpetSettings.LightBatchValidator.applyLightBatchSizes();
|
||||
CarpetSettings.LightBatchValidator.applyLightBatchSizes(CarpetSettings.lightEngineMaxBatchSize);
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,10 @@ package carpet.network;
|
||||
import carpet.CarpetServer;
|
||||
import carpet.CarpetExtension;
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.api.settings.CarpetRule;
|
||||
import carpet.api.settings.InvalidRuleValueException;
|
||||
import carpet.helpers.TickSpeed;
|
||||
import carpet.settings.ParsedRule;
|
||||
import carpet.settings.SettingsManager;
|
||||
import carpet.api.settings.SettingsManager;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -42,8 +43,8 @@ public class ClientNetworkHandler
|
||||
else
|
||||
{
|
||||
for (CarpetExtension extension: CarpetServer.extensions) {
|
||||
SettingsManager eManager = extension.customSettingsManager();
|
||||
if (eManager != null && managerName.equals(eManager.getIdentifier()))
|
||||
SettingsManager eManager = extension.extensionSettingsManager();
|
||||
if (eManager != null && managerName.equals(eManager.identifier()))
|
||||
{
|
||||
manager = eManager;
|
||||
break;
|
||||
@@ -56,11 +57,11 @@ public class ClientNetworkHandler
|
||||
manager = CarpetServer.settingsManager;
|
||||
ruleName = ruleKey;
|
||||
}
|
||||
ParsedRule<?> rule = (manager != null) ? manager.getRule(ruleName) : null;
|
||||
CarpetRule<?> rule = (manager != null) ? manager.getCarpetRule(ruleName) : null;
|
||||
if (rule != null)
|
||||
{
|
||||
String value = ruleNBT.getString("Value");
|
||||
try { rule.set(null, value); } catch (Exception ignored) { }
|
||||
try { rule.set(null, value); } catch (InvalidRuleValueException ignored) { }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,10 +2,11 @@ package carpet.network;
|
||||
|
||||
import carpet.CarpetServer;
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.api.settings.CarpetRule;
|
||||
import carpet.api.settings.RuleHelper;
|
||||
import carpet.helpers.TickSpeed;
|
||||
import carpet.script.utils.SnoopyCommandSource;
|
||||
import carpet.settings.ParsedRule;
|
||||
import carpet.settings.SettingsManager;
|
||||
import carpet.api.settings.SettingsManager;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -75,11 +76,11 @@ public class ServerNetworkHandler
|
||||
else
|
||||
CarpetSettings.LOG.warn("Player "+playerEntity.getName().getString()+" joined with another carpet version: "+clientVersion);
|
||||
DataBuilder data = DataBuilder.create().withTickRate().withFrozenState().withTickPlayerActiveTimeout(); // .withSuperHotState()
|
||||
CarpetServer.settingsManager.getRules().forEach(data::withRule);
|
||||
CarpetServer.settingsManager.getCarpetRules().forEach(data::withRule);
|
||||
CarpetServer.extensions.forEach(e -> {
|
||||
SettingsManager eManager = e.customSettingsManager();
|
||||
SettingsManager eManager = e.extensionSettingsManager();
|
||||
if (eManager != null) {
|
||||
eManager.getRules().forEach(data::withRule);
|
||||
eManager.getCarpetRules().forEach(data::withRule);
|
||||
}
|
||||
});
|
||||
playerEntity.connection.send(new ClientboundCustomPayloadPacket(CarpetClient.CARPET_CHANNEL, data.build() ));
|
||||
@@ -130,7 +131,7 @@ public class ServerNetworkHandler
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateRuleWithConnectedClients(ParsedRule<?> rule)
|
||||
public static void updateRuleWithConnectedClients(CarpetRule<?> rule)
|
||||
{
|
||||
if (CarpetSettings.superSecretSetting) return;
|
||||
for (ServerPlayer player : remoteCarpetPlayers.keySet())
|
||||
@@ -275,7 +276,7 @@ public class ServerNetworkHandler
|
||||
tag.putInt("TickPlayerActiveTimeout", TickSpeed.player_active_timeout);
|
||||
return this;
|
||||
}
|
||||
private DataBuilder withRule(ParsedRule<?> rule)
|
||||
private DataBuilder withRule(CarpetRule<?> rule)
|
||||
{
|
||||
CompoundTag rules = (CompoundTag) tag.get("Rules");
|
||||
if (rules == null)
|
||||
@@ -283,13 +284,13 @@ public class ServerNetworkHandler
|
||||
rules = new CompoundTag();
|
||||
tag.put("Rules", rules);
|
||||
}
|
||||
String identifier = rule.settingsManager.getIdentifier();
|
||||
String key = rule.name;
|
||||
String identifier = rule.settingsManager().identifier();
|
||||
String key = rule.name();
|
||||
while (rules.contains(key)) { key = key+"2";}
|
||||
CompoundTag ruleNBT = new CompoundTag();
|
||||
ruleNBT.putString("Value", rule.getAsString());
|
||||
ruleNBT.putString("Manager",identifier);
|
||||
ruleNBT.putString("Rule",rule.name);
|
||||
ruleNBT.putString("Value", RuleHelper.toRuleString(rule.value()));
|
||||
ruleNBT.putString("Manager", identifier);
|
||||
ruleNBT.putString("Rule", rule.name());
|
||||
rules.put(key, ruleNBT);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package carpet.script;
|
||||
|
||||
import carpet.CarpetServer;
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.api.settings.CarpetRule;
|
||||
import carpet.api.settings.RuleHelper;
|
||||
import carpet.helpers.TickSpeed;
|
||||
import carpet.script.exception.IntegrityException;
|
||||
import carpet.script.exception.InternalExpressionException;
|
||||
@@ -17,7 +19,6 @@ import carpet.script.value.NumericValue;
|
||||
import carpet.script.value.StringValue;
|
||||
import carpet.script.value.Value;
|
||||
import carpet.script.value.ValueConversions;
|
||||
import carpet.settings.ParsedRule;
|
||||
import carpet.utils.CarpetProfiler;
|
||||
import carpet.utils.Messenger;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
@@ -917,9 +918,9 @@ public class CarpetEventServer
|
||||
public static final Event CARPET_RULE_CHANGES = new Event("carpet_rule_changes", 2, true)
|
||||
{
|
||||
@Override
|
||||
public void onCarpetRuleChanges(ParsedRule<?> rule, CommandSourceStack source)
|
||||
public void onCarpetRuleChanges(CarpetRule<?> rule, CommandSourceStack source)
|
||||
{
|
||||
String identifier = rule.settingsManager.getIdentifier();
|
||||
String identifier = rule.settingsManager().identifier();
|
||||
final String namespace;
|
||||
if (!identifier.equals("carpet"))
|
||||
{
|
||||
@@ -927,8 +928,8 @@ public class CarpetEventServer
|
||||
} else { namespace = "";}
|
||||
handler.call(
|
||||
() -> Arrays.asList(
|
||||
new StringValue(namespace+rule.name),
|
||||
new StringValue(rule.getAsString())
|
||||
new StringValue(namespace+rule.name()),
|
||||
new StringValue(RuleHelper.toRuleString(rule.value()))
|
||||
), () -> source
|
||||
);
|
||||
}
|
||||
@@ -1123,7 +1124,7 @@ public class CarpetEventServer
|
||||
public void onExplosion(ServerLevel world, Entity e, Supplier<LivingEntity> attacker, double x, double y, double z, float power, boolean createFire, List<BlockPos> affectedBlocks, List<Entity> affectedEntities, Explosion.BlockInteraction type) { }
|
||||
public void onWorldEvent(ServerLevel world, BlockPos pos) { }
|
||||
public void onWorldEventFlag(ServerLevel world, BlockPos pos, int flag) { }
|
||||
public void onCarpetRuleChanges(ParsedRule<?> rule, CommandSourceStack source) { }
|
||||
public void onCarpetRuleChanges(CarpetRule<?> rule, CommandSourceStack source) { }
|
||||
public void onCustomPlayerEvent(ServerPlayer player, Object ... args)
|
||||
{
|
||||
if (handler.reqArgs != (args.length+1))
|
||||
|
||||
@@ -21,6 +21,7 @@ import carpet.script.language.Threading;
|
||||
import carpet.script.utils.AppStoreManager;
|
||||
import carpet.script.value.FunctionValue;
|
||||
import carpet.utils.CarpetProfiler;
|
||||
import carpet.utils.CommandHelper;
|
||||
import carpet.utils.Messenger;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
@@ -140,8 +141,8 @@ public class CarpetScriptServer extends ScriptServer
|
||||
{
|
||||
CarpetServer.settingsManager.initializeScarpetRules();
|
||||
CarpetServer.extensions.forEach(e -> {
|
||||
if (e.customSettingsManager() != null) {
|
||||
e.customSettingsManager().initializeScarpetRules();
|
||||
if (e.extensionSettingsManager() != null) {
|
||||
e.extensionSettingsManager().initializeScarpetRules();
|
||||
}
|
||||
});
|
||||
if (CarpetSettings.scriptsAutoload)
|
||||
@@ -305,7 +306,7 @@ public class CarpetScriptServer extends ScriptServer
|
||||
}
|
||||
else if (isCommandAdded)
|
||||
{
|
||||
CarpetServer.settingsManager.notifyPlayersCommandsChanged();
|
||||
CommandHelper.notifyPlayersCommandsChanged(server);
|
||||
if (!isRuleApp) Messenger.m(source, "gi "+name+" app "+action+" with /"+name+" command");
|
||||
}
|
||||
else
|
||||
@@ -352,7 +353,7 @@ public class CarpetScriptServer extends ScriptServer
|
||||
modules.get(name).onClose();
|
||||
modules.remove(name);
|
||||
if (!isRuleApp) unloadableModules.remove(name);
|
||||
CarpetServer.settingsManager.notifyPlayersCommandsChanged();
|
||||
CommandHelper.notifyPlayersCommandsChanged(server);
|
||||
if (notifySource) Messenger.m(source, "gi Removed "+name+" app");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ import carpet.script.value.StringValue;
|
||||
import carpet.script.value.Value;
|
||||
import carpet.script.value.ValueConversions;
|
||||
import carpet.utils.BlockInfo;
|
||||
import carpet.utils.CommandHelper;
|
||||
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
@@ -1497,7 +1499,7 @@ public class WorldAccess {
|
||||
|
||||
boolean success = WorldTools.createWorld(cc.s.getServer(), worldKey, seed);
|
||||
if (!success) return Value.FALSE;
|
||||
CarpetServer.settingsManager.notifyPlayersCommandsChanged();
|
||||
CommandHelper.notifyPlayersCommandsChanged(cc.s.getServer());
|
||||
return Value.TRUE;
|
||||
});
|
||||
*/
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package carpet.script.utils;
|
||||
|
||||
import carpet.CarpetServer;
|
||||
import carpet.api.settings.CarpetRule;
|
||||
import carpet.api.settings.Validator;
|
||||
import carpet.script.CarpetScriptHost;
|
||||
import carpet.script.CarpetScriptServer;
|
||||
import carpet.script.exception.InternalExpressionException;
|
||||
import carpet.script.value.MapValue;
|
||||
import carpet.script.value.StringValue;
|
||||
import carpet.script.value.Value;
|
||||
import carpet.settings.ParsedRule;
|
||||
import carpet.settings.Validator;
|
||||
import carpet.utils.Messenger;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
@@ -57,7 +57,7 @@ public class AppStoreManager
|
||||
|
||||
public static class ScarpetAppStoreValidator extends Validator<String>
|
||||
{
|
||||
@Override public String validate(CommandSourceStack source, ParsedRule<String> currentRule, String newValue, String string)
|
||||
@Override public String validate(CommandSourceStack source, CarpetRule<String> currentRule, String newValue, String stringInput)
|
||||
{
|
||||
APP_STORE_ROOT = StoreNode.folder(null, "");
|
||||
if (newValue.equalsIgnoreCase("none"))
|
||||
|
||||
@@ -2,6 +2,8 @@ package carpet.script.utils;
|
||||
|
||||
import carpet.CarpetServer;
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.api.settings.CarpetRule;
|
||||
import carpet.api.settings.RuleHelper;
|
||||
import carpet.script.CarpetContext;
|
||||
import carpet.script.CarpetScriptHost;
|
||||
import carpet.script.value.BooleanValue;
|
||||
@@ -12,8 +14,7 @@ import carpet.script.value.NumericValue;
|
||||
import carpet.script.value.StringValue;
|
||||
import carpet.script.value.Value;
|
||||
import carpet.script.value.ValueConversions;
|
||||
import carpet.settings.ParsedRule;
|
||||
import carpet.settings.SettingsManager;
|
||||
import carpet.api.settings.SettingsManager;
|
||||
import com.sun.management.OperatingSystemMXBean;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.fabricmc.loader.api.ModContainer;
|
||||
@@ -172,18 +173,18 @@ public class SystemInfo {
|
||||
return new NumericValue(osBean.getProcessCpuLoad());
|
||||
});
|
||||
put("world_carpet_rules", c -> {
|
||||
Collection<ParsedRule<?>> rules = CarpetServer.settingsManager.getRules();
|
||||
Collection<CarpetRule<?>> rules = CarpetServer.settingsManager.getCarpetRules();
|
||||
MapValue carpetRules = new MapValue(Collections.emptyList());
|
||||
rules.forEach(rule -> {
|
||||
carpetRules.put(new StringValue(rule.name), new StringValue(rule.getAsString()));
|
||||
carpetRules.put(new StringValue(rule.name()), new StringValue(RuleHelper.toRuleString(rule.value())));
|
||||
});
|
||||
CarpetServer.extensions.forEach(e -> {
|
||||
SettingsManager manager = e.customSettingsManager();
|
||||
SettingsManager manager = e.extensionSettingsManager();
|
||||
if (manager == null) return;
|
||||
|
||||
Collection<ParsedRule<?>> extensionRules = manager.getRules();
|
||||
Collection<CarpetRule<?>> extensionRules = manager.getCarpetRules();
|
||||
extensionRules.forEach(rule -> {
|
||||
carpetRules.put(new StringValue(manager.getIdentifier()+":"+rule.name), new StringValue(rule.getAsString()));
|
||||
carpetRules.put(new StringValue(manager.identifier()+":"+rule.name()), new StringValue(RuleHelper.toRuleString(rule.value())));
|
||||
});
|
||||
});
|
||||
return carpetRules;
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
package carpet.settings;
|
||||
|
||||
public interface Condition {
|
||||
import carpet.CarpetSettings;
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link carpet.api.settings.Rule.Condition} instead
|
||||
*
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public interface Condition extends carpet.api.settings.Rule.Condition {
|
||||
boolean isTrue();
|
||||
|
||||
@Override
|
||||
default boolean shouldRegister() {
|
||||
CarpetSettings.LOG.warn("""
|
||||
Extension is referencing outdated Condition class! Class that caused this was: %s
|
||||
This won't be supported in later Carpet versions and will crash the game!""".formatted(getClass().getName()));
|
||||
return isTrue();
|
||||
}
|
||||
}
|
||||
@@ -1,91 +1,231 @@
|
||||
package carpet.settings;
|
||||
|
||||
import carpet.utils.Translations;
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.api.settings.CarpetRule;
|
||||
import carpet.api.settings.InvalidRuleValueException;
|
||||
import carpet.api.settings.RuleHelper;
|
||||
import carpet.api.settings.SettingsManager;
|
||||
import carpet.api.settings.Validators;
|
||||
import carpet.utils.Messenger;
|
||||
import carpet.utils.TranslationKeys;
|
||||
import carpet.utils.Translations;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.lang3.ClassUtils;
|
||||
|
||||
import static carpet.utils.Translations.tr;
|
||||
|
||||
/**
|
||||
* A parsed Carpet rule, with its field, name, value, and other useful stuff.
|
||||
* A Carpet rule parsed from a field, with its name, value, and other useful stuff.
|
||||
*
|
||||
* It is generated from the fields with the {@link Rule} annotation
|
||||
* It is used for the fields with the {@link Rule} annotation
|
||||
* when being parsed by {@link SettingsManager#parseSettingsClass(Class)}.
|
||||
*
|
||||
* @param <T> The field's type
|
||||
* @param <T> The field's (and rule's) type
|
||||
* @deprecated Use the type {@link CarpetRule} instead
|
||||
*/
|
||||
public final class ParsedRule<T> implements Comparable<ParsedRule> {
|
||||
@Deprecated(forRemoval = true) // to move to api.settings package and visibility to package private
|
||||
public final class ParsedRule<T> implements CarpetRule<T>, Comparable<ParsedRule<?>> {
|
||||
private static final Map<Class<?>, FromStringConverter<?>> CONVERTER_MAP = Map.ofEntries(
|
||||
Map.entry(String.class, str -> str),
|
||||
Map.entry(Boolean.class, str -> {
|
||||
return switch (str) {
|
||||
case "true" -> true;
|
||||
case "false" -> false;
|
||||
default -> throw new InvalidRuleValueException("Invalid boolean value");
|
||||
};
|
||||
}),
|
||||
numericalConverter(Integer.class, Integer::parseInt),
|
||||
numericalConverter(Double.class, Double::parseDouble),
|
||||
numericalConverter(Long.class, Long::parseLong),
|
||||
numericalConverter(Float.class, Float::parseFloat)
|
||||
);
|
||||
/**
|
||||
* @deprecated No replacement for this, since a {@link CarpetRule} may not always use a {@link Field}.
|
||||
* Use {@link #value()} to access the rule's value
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to private
|
||||
public final Field field;
|
||||
/**
|
||||
* @deprecated Use {@link CarpetRule#name()} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to private
|
||||
public final String name;
|
||||
/**
|
||||
* @deprecated Use {@link RuleHelper#translatedDescription(CarpetRule)}, or get it from the translation system
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to remove
|
||||
public final String description;
|
||||
public final String scarpetApp;
|
||||
/**
|
||||
* @deprecated Use {@link CarpetRule#extraInfo()} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to remove
|
||||
public final List<String> extraInfo;
|
||||
/**
|
||||
* @deprecated Use {@link CarpetRule#categories()} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to private
|
||||
public final List<String> categories;
|
||||
/**
|
||||
* @deprecated Use {@link CarpetRule#suggestions()} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to private (and rename?)
|
||||
public final List<String> options;
|
||||
/**
|
||||
* @deprecated No replacement for this
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to pckg private (for printRulesToLog, or get a different way)
|
||||
public boolean isStrict;
|
||||
/**
|
||||
* @deprecated Use {@link CarpetRule#canBeToggledClientSide()} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to private (and maybe rename?)
|
||||
public boolean isClient;
|
||||
/**
|
||||
* @deprecated Use {@link CarpetRule#type()} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to private (or remove and delegate to typedfield?)
|
||||
public final Class<T> type;
|
||||
public final List<Validator<T>> validators;
|
||||
/**
|
||||
* @deprecated Use {@link CarpetRule#defaultValue()} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to private
|
||||
public final T defaultValue;
|
||||
/**
|
||||
* @deprecated Use {@link CarpetRule#settingsManager()} instead.
|
||||
* This field may be {@code null} if the settings manager isn't an instance of the old type
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to remove in favour of realSettingsManager
|
||||
public final carpet.settings.SettingsManager settingsManager;
|
||||
/**
|
||||
* @deprecated No replacement for this. A Carpet rule may not use {@link Validator}
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to remove (in favour of realValidators)
|
||||
public final List<Validator<T>> validators;
|
||||
/**
|
||||
* @deprecated Use {@link CarpetRule#defaultValue()} and pass it to {@link RuleHelper#toRuleString(Object)} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to remove
|
||||
public final String defaultAsString;
|
||||
public final SettingsManager settingsManager;
|
||||
/**
|
||||
* @deprecated No replacement for this, Scarpet Rules should be managed by the rule implementation
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to private/subclass
|
||||
public final String scarpetApp;
|
||||
private final FromStringConverter<T> converter;
|
||||
private final SettingsManager realSettingsManager; // to rename to settingsManager
|
||||
/**
|
||||
* If you reference this field I'll steal your kneecaps
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public final List<carpet.api.settings.Validator<T>> realValidators; // to rename to validators and to package private for printRulesToLog
|
||||
private final boolean isLegacy; // to remove, only used for fallbacks
|
||||
|
||||
ParsedRule(Field field, Rule rule, SettingsManager settingsManager)
|
||||
@FunctionalInterface
|
||||
interface FromStringConverter<T> {
|
||||
T convert(String value) throws InvalidRuleValueException;
|
||||
}
|
||||
|
||||
record RuleAnnotation(boolean isLegacy, String name, String desc, String[] extra, String[] category, String[] options, boolean strict, String appSource, Class<? extends carpet.api.settings.Validator>[] validators) {
|
||||
}
|
||||
|
||||
/**
|
||||
* If you call this method I'll steal your kneecaps
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public static <T> ParsedRule<T> of(Field field, SettingsManager settingsManager) {
|
||||
RuleAnnotation rule;
|
||||
if (settingsManager instanceof carpet.settings.SettingsManager && field.isAnnotationPresent(Rule.class)) { // Legacy path
|
||||
Rule a = field.getAnnotation(Rule.class);
|
||||
rule = new RuleAnnotation(true, a.name(), a.desc(), a.extra(), a.category(), a.options(), a.strict(), a.appSource(), a.validate());
|
||||
} else if (field.isAnnotationPresent(carpet.api.settings.Rule.class)) {
|
||||
carpet.api.settings.Rule a = field.getAnnotation(carpet.api.settings.Rule.class);
|
||||
rule = new RuleAnnotation(false, null, null, null, a.categories(), a.options(), a.strict(), a.appSource(), a.validators());
|
||||
} else {
|
||||
// Don't allow to use old rule types in custom AND migrated settings manager
|
||||
throw new IllegalArgumentException("Old rule annotation is only supported in legacy SettngsManager!");
|
||||
}
|
||||
return new ParsedRule<>(field, rule, settingsManager);
|
||||
}
|
||||
|
||||
private ParsedRule(Field field, RuleAnnotation rule, SettingsManager settingsManager)
|
||||
{
|
||||
this.isLegacy = rule.isLegacy();
|
||||
this.name = !isLegacy || rule.name().isEmpty() ? field.getName() : rule.name();
|
||||
this.field = field;
|
||||
this.name = rule.name().isEmpty() ? field.getName() : rule.name();
|
||||
this.type = (Class<T>) field.getType();
|
||||
this.description = rule.desc();
|
||||
@SuppressWarnings("unchecked") // We are "defining" T here
|
||||
Class<T> type = (Class<T>)ClassUtils.primitiveToWrapper(field.getType());
|
||||
this.type = type;
|
||||
this.isStrict = rule.strict();
|
||||
this.extraInfo = List.of(rule.extra());
|
||||
this.categories = List.of(rule.category());
|
||||
this.scarpetApp = rule.appSource();
|
||||
this.settingsManager = settingsManager;
|
||||
this.validators = new ArrayList<>();
|
||||
for (Class v : rule.validate())
|
||||
this.validators.add((Validator<T>) callConstructor(v));
|
||||
this.realSettingsManager = settingsManager;
|
||||
if (!(settingsManager instanceof carpet.settings.SettingsManager)) {
|
||||
// this is awkward... but people using a custom, new (extends only new api) manager should not be using this anyway but the interface method
|
||||
this.settingsManager = null;
|
||||
} else {
|
||||
this.settingsManager = (carpet.settings.SettingsManager) settingsManager;
|
||||
}
|
||||
this.realValidators = Stream.of(rule.validators()).map(this::instantiateValidator).collect(Collectors.toList());
|
||||
this.defaultValue = value();
|
||||
FromStringConverter<T> converter0 = null;
|
||||
|
||||
if (categories.contains(RuleCategory.COMMAND))
|
||||
{
|
||||
this.validators.add(callConstructor(Validator._COMMAND.class));
|
||||
this.realValidators.add(new Validator._COMMAND<T>());
|
||||
if (this.type == String.class)
|
||||
{
|
||||
this.isStrict = false;
|
||||
this.validators.add((Validator<T>) callConstructor(Validator._COMMAND_LEVEL_VALIDATOR.class));
|
||||
this.realValidators.add(instantiateValidator(Validators.CommandLevel.class));
|
||||
}
|
||||
}
|
||||
if (!scarpetApp.isEmpty())
|
||||
{
|
||||
this.validators.add((Validator<T>) callConstructor(Validator._SCARPET.class));
|
||||
}
|
||||
|
||||
this.isClient = categories.contains(RuleCategory.CLIENT);
|
||||
if (this.isClient)
|
||||
{
|
||||
this.validators.add(callConstructor(Validator._CLIENT.class));
|
||||
this.realValidators.add(new Validator._CLIENT<>());
|
||||
}
|
||||
this.defaultValue = get();
|
||||
this.defaultAsString = convertToString(this.defaultValue);
|
||||
|
||||
if (!scarpetApp.isEmpty())
|
||||
{
|
||||
this.realValidators.add(new Validator.ScarpetValidator<>());
|
||||
}
|
||||
|
||||
if (rule.options().length > 0)
|
||||
{
|
||||
this.options = List.of(rule.options());
|
||||
}
|
||||
else if (this.type == boolean.class){
|
||||
this.options = List.of("true","false");
|
||||
else if (this.type == Boolean.class) {
|
||||
this.options = List.of("true", "false");
|
||||
}
|
||||
else if(this.type == String.class && categories.contains(RuleCategory.COMMAND))
|
||||
else if (this.type == String.class && categories.contains(RuleCategory.COMMAND))
|
||||
{
|
||||
this.options = List.of("true", "false", "ops");
|
||||
this.options = Validators.CommandLevel.OPTIONS;
|
||||
}
|
||||
else if (this.type.isEnum())
|
||||
{
|
||||
this.options = Arrays.stream(this.type.getEnumConstants()).map(e -> ((Enum) e).name().toLowerCase(Locale.ROOT)).collect(Collectors.toUnmodifiableList());
|
||||
this.options = Arrays.stream(this.type.getEnumConstants()).map(e -> ((Enum<?>) e).name().toLowerCase(Locale.ROOT)).collect(Collectors.toUnmodifiableList());
|
||||
converter0 = str -> {
|
||||
try {
|
||||
@SuppressWarnings({"unchecked", "rawtypes"}) // Raw necessary because of signature. Unchecked because compiler doesn't know T extends Enum
|
||||
T ret = (T)Enum.valueOf((Class<? extends Enum>) type, str.toUpperCase(Locale.ROOT));
|
||||
return ret;
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new InvalidRuleValueException("Valid values for this rule are: " + this.options);
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -93,157 +233,83 @@ public final class ParsedRule<T> implements Comparable<ParsedRule> {
|
||||
}
|
||||
if (isStrict && !this.options.isEmpty())
|
||||
{
|
||||
if (this.type == boolean.class || this.type == int.class || this.type == double.class || this.type == float.class)
|
||||
{
|
||||
this.validators.add(callConstructor(Validator._STRICT_IGNORECASE.class));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.validators.add(callConstructor(Validator._STRICT.class));
|
||||
}
|
||||
this.realValidators.add(0, new Validator.StrictValidator<>()); // at 0 prevents validators with side effects from running when invalid
|
||||
}
|
||||
if (converter0 == null) {
|
||||
@SuppressWarnings("unchecked")
|
||||
FromStringConverter<T> converterFromMap = (FromStringConverter<T>)CONVERTER_MAP.get(type);
|
||||
if (converterFromMap == null) throw new UnsupportedOperationException("Unsupported type for ParsedRule" + type);
|
||||
converter0 = converterFromMap;
|
||||
}
|
||||
this.converter = converter0;
|
||||
|
||||
// Language "constants"
|
||||
String nameKey = TranslationKeys.RULE_NAME_PATTERN.formatted(settingsManager().identifier(), name());
|
||||
String descKey = TranslationKeys.RULE_DESC_PATTERN.formatted(settingsManager().identifier(), name());
|
||||
String extraPrefix = TranslationKeys.RULE_EXTRA_PREFIX_PATTERN.formatted(settingsManager().identifier(), name());
|
||||
|
||||
// to remove
|
||||
this.description = isLegacy ? rule.desc() : Objects.requireNonNull(Translations.trOrNull(descKey), "No language key provided for " + descKey);
|
||||
this.extraInfo = isLegacy ? List.of(rule.extra()) : getTranslationArray(extraPrefix);
|
||||
this.defaultAsString = RuleHelper.toRuleString(this.defaultValue);
|
||||
this.validators = realValidators.stream().filter(Validator.class::isInstance).map(v -> (Validator<T>) v).toList();
|
||||
if (!isLegacy && !validators.isEmpty()) throw new IllegalArgumentException("Can't use legacy validators with new rules!");
|
||||
|
||||
// Language fallbacks - Also asserts the strings will be available in non-english languages, given current system has no fallback
|
||||
if (isLegacy && !rule.name().isEmpty()) Translations.registerFallbackTranslation(nameKey, name);
|
||||
Translations.registerFallbackTranslation(descKey, description);
|
||||
Iterator<String> infoIterator = extraInfo.iterator();
|
||||
for (int i = 0; infoIterator.hasNext(); i++) {
|
||||
Translations.registerFallbackTranslation(extraPrefix + i, infoIterator.next());
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T callConstructor(Class<T> cls)
|
||||
@SuppressWarnings({"unchecked", "rawtypes"}) // Needed because of the annotation
|
||||
private carpet.api.settings.Validator<T> instantiateValidator(Class<? extends carpet.api.settings.Validator> cls)
|
||||
{
|
||||
try
|
||||
{
|
||||
Constructor<T> constr = cls.getDeclaredConstructor();
|
||||
Constructor<? extends carpet.api.settings.Validator> constr = cls.getDeclaredConstructor();
|
||||
constr.setAccessible(true);
|
||||
return constr.newInstance();
|
||||
}
|
||||
catch (ReflectiveOperationException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public ParsedRule<T> set(CommandSourceStack source, String value)
|
||||
@Override
|
||||
public void set(CommandSourceStack source, String value) throws InvalidRuleValueException
|
||||
{
|
||||
if (settingsManager != null && settingsManager.locked)
|
||||
return null;
|
||||
if (type == String.class)
|
||||
{
|
||||
return set(source, (T) value, value);
|
||||
}
|
||||
else if (type == boolean.class)
|
||||
{
|
||||
return set(source, (T) (Object) Boolean.parseBoolean(value), value);
|
||||
}
|
||||
else if (type == int.class)
|
||||
{
|
||||
return set(source, (T) (Object) Integer.parseInt(value), value);
|
||||
}
|
||||
else if (type == double.class)
|
||||
{
|
||||
return set(source, (T) (Object) Double.parseDouble(value), value);
|
||||
}
|
||||
else if (type.isEnum())
|
||||
{
|
||||
String ucValue = value.toUpperCase(Locale.ROOT);
|
||||
return set(source, (T) (Object) Enum.valueOf((Class<? extends Enum>) type, ucValue), value);
|
||||
}
|
||||
else
|
||||
{
|
||||
Messenger.m(source, "r Unknown type " + type.getSimpleName());
|
||||
return null;
|
||||
}
|
||||
set(source, converter.convert(value), value);
|
||||
}
|
||||
|
||||
ParsedRule<T> set(CommandSourceStack source, T value, String stringValue)
|
||||
private void set(CommandSourceStack source, T value, String userInput) throws InvalidRuleValueException
|
||||
{
|
||||
try
|
||||
for (carpet.api.settings.Validator<T> validator : this.realValidators)
|
||||
{
|
||||
for (Validator<T> validator : this.validators)
|
||||
{
|
||||
value = validator.validate(source, this, value, stringValue);
|
||||
if (value == null)
|
||||
{
|
||||
if (source != null)
|
||||
{
|
||||
validator.notifyFailure(source, this, stringValue);
|
||||
if (validator.description() != null)
|
||||
Messenger.m(source, "r " + validator.description());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
value = validator.validate(source, this, value, userInput); // should this recalculate the string? Another validator may have changed value
|
||||
if (value == null) {
|
||||
if (source != null) validator.notifyFailure(source, this, userInput);
|
||||
throw new InvalidRuleValueException();
|
||||
}
|
||||
if (!value.equals(get()) || source == null)
|
||||
{
|
||||
}
|
||||
if (!value.equals(value()) || source == null)
|
||||
{
|
||||
try {
|
||||
this.field.set(null, value);
|
||||
if (source != null) settingsManager.notifyRuleChanged(source, this, stringValue);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException("Couldn't access field for rule: " + name, e);
|
||||
}
|
||||
if (source != null) settingsManager().notifyRuleChanged(source, this, userInput);
|
||||
}
|
||||
catch (IllegalAccessException e)
|
||||
{
|
||||
Messenger.m(source, "r Unable to access setting for "+name);
|
||||
return null;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The value of this {@link ParsedRule}, in its type
|
||||
*/
|
||||
public T get()
|
||||
{
|
||||
try
|
||||
{
|
||||
return (T) this.field.get(null);
|
||||
}
|
||||
catch (IllegalAccessException e)
|
||||
{
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The value of this {@link ParsedRule}, as a {@link String}
|
||||
*/
|
||||
public String getAsString()
|
||||
{
|
||||
return convertToString(get());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The value of this {@link ParsedRule}, converted to a {@link boolean}.
|
||||
* It will only return {@link true} if it's a true {@link boolean} or
|
||||
* a number greater than zero.
|
||||
*/
|
||||
public boolean getBoolValue()
|
||||
{
|
||||
if (type == boolean.class) return (Boolean) get();
|
||||
if (ClassUtils.primitiveToWrapper(type).isAssignableFrom(Number.class)) return ((Number) get()).doubleValue() > 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Wether or not this {@link ParsedRule} is in its default value
|
||||
*/
|
||||
public boolean isDefault()
|
||||
{
|
||||
return defaultValue.equals(get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets this rule to its default value
|
||||
*/
|
||||
public void resetToDefault(CommandSourceStack source)
|
||||
{
|
||||
set(source, defaultValue, defaultAsString);
|
||||
}
|
||||
|
||||
|
||||
private static String convertToString(Object value)
|
||||
{
|
||||
if (value instanceof Enum) return ((Enum) value).name().toLowerCase(Locale.ROOT);
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
return obj.getClass() == ParsedRule.class && ((ParsedRule) obj).name.equals(this.name);
|
||||
return obj instanceof ParsedRule && ((ParsedRule<?>) obj).name.equals(this.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -253,55 +319,187 @@ public final class ParsedRule<T> implements Comparable<ParsedRule> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ParsedRule o)
|
||||
public String toString()
|
||||
{
|
||||
return this.name.compareTo(o.name);
|
||||
return this.name + ": " + RuleHelper.toRuleString(value());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return this.name + ": " + getAsString();
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
private String translationKey()
|
||||
{
|
||||
return String.format("rule.%s.name", name);
|
||||
@Override
|
||||
public List<Component> extraInfo() {
|
||||
return getTranslationArray(TranslationKeys.RULE_EXTRA_PREFIX_PATTERN.formatted(settingsManager().identifier(), name()))
|
||||
.stream()
|
||||
.map(str -> Messenger.c("g " + str))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<String> getTranslationArray(String prefix) {
|
||||
List<String> ret = new ArrayList<>();
|
||||
for (int i = 0; Translations.hasTranslation(prefix + i); i++) {
|
||||
ret.add(Translations.tr(prefix + i));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> categories() {
|
||||
return categories;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> suggestions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SettingsManager settingsManager() {
|
||||
return realSettingsManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked") // T comes from the field
|
||||
public T value() {
|
||||
try {
|
||||
return (T) field.get(null);
|
||||
} catch (IllegalAccessException e) {
|
||||
// Can't happen at regular runtime because we'd have thrown it on construction
|
||||
throw new IllegalArgumentException("Couldn't access field for rule: " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeToggledClientSide() {
|
||||
return isClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T defaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(CommandSourceStack source, T value) throws InvalidRuleValueException {
|
||||
set(source, value, RuleHelper.toRuleString(value));
|
||||
}
|
||||
|
||||
private static <T> Map.Entry<Class<T>, FromStringConverter<T>> numericalConverter(Class<T> outputClass, Function<String, T> converter) {
|
||||
return Map.entry(outputClass, str -> {
|
||||
try {
|
||||
return converter.apply(str);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new InvalidRuleValueException("Invalid number for rule");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//TO REMOVE
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link CarpetRule#value()} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public T get()
|
||||
{
|
||||
return value();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link RuleHelper#toRuleString(Object) RuleHelper.convertToRuleString(rule.value())}
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public String getAsString()
|
||||
{
|
||||
return RuleHelper.toRuleString(value());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The value of this {@link ParsedRule}, converted to a {@link boolean}.
|
||||
* It will only return {@link true} if it's a true {@link boolean} or
|
||||
* a number greater than zero.
|
||||
* @deprecated Use {@link RuleHelper#getBooleanValue(CarpetRule)}
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public boolean getBoolValue()
|
||||
{
|
||||
return RuleHelper.getBooleanValue(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link RuleHelper#isInDefaultValue(CarpetRule)}
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public boolean isDefault()
|
||||
{
|
||||
return RuleHelper.isInDefaultValue(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link RuleHelper#resetToDefault(CarpetRule, CommandSourceStack)}
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public void resetToDefault(CommandSourceStack source)
|
||||
{
|
||||
RuleHelper.resetToDefault(this, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Forcing {@link Comparable} isn't a thing on {@link CarpetRule}s. Instead, pass a comparator by name to your
|
||||
* sort methods, you can get one by calling {@code Comparator.comparing(CarpetRule::name)}
|
||||
*/
|
||||
@Override
|
||||
@Deprecated(forRemoval = true)
|
||||
public int compareTo(ParsedRule<?> o)
|
||||
{
|
||||
if (!warnedComparable) {
|
||||
warnedComparable = true;
|
||||
CarpetSettings.LOG.warn("""
|
||||
Extension is relying on carpet rules to be comparable! This is not true for all carpet rules anymore, \
|
||||
and will crash the game in future versions or if an extension adds non-comparable rules!
|
||||
Fixing it is as simple as passing Comparator.comparing(CarpetRule::name) to the sorting method!""",
|
||||
new Throwable("Location:").fillInStackTrace());
|
||||
}
|
||||
return this.name.compareTo(o.name);
|
||||
}
|
||||
private static boolean warnedComparable = false;
|
||||
|
||||
/**
|
||||
* @return A {@link String} being the translated {@link ParsedRule#name} of this rule,
|
||||
* in Carpet's configured language.
|
||||
* @deprecated Use {@link RuleHelper#translatedName(CarpetRule)} instead
|
||||
*/
|
||||
public String translatedName(){
|
||||
String key = translationKey();
|
||||
return Translations.hasTranslation(key) ? tr(key) + String.format(" (%s)", name): name;
|
||||
@Deprecated(forRemoval = true)
|
||||
public String translatedName() {
|
||||
return RuleHelper.translatedName(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A {@link String} being the translated {@link ParsedRule#description} of this rule,
|
||||
* @return A {@link String} being the translated {@link ParsedRule#description description} of this rule,
|
||||
* in Carpet's configured language.
|
||||
* @deprecated Use {@link RuleHelper#translatedDescription(CarpetRule)} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public String translatedDescription()
|
||||
{
|
||||
return tr(String.format("rule.%s.desc", (name)), description);
|
||||
return RuleHelper.translatedDescription(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A {@link String} being the translated {@link ParsedRule#extraInfo} of this
|
||||
* {@link ParsedRule}, in Carpet's configured language.
|
||||
* @return A {@link String} being the translated {@link ParsedRule#extraInfo extraInfo} of this
|
||||
* {@link ParsedRule}, in Carpet's configured language.
|
||||
* @deprecated Use {@link CarpetRule#extraInfo()} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public List<String> translatedExtras()
|
||||
{
|
||||
if (!Translations.hasTranslations()) return extraInfo;
|
||||
String keyBase = String.format("rule.%s.extra.", name);
|
||||
List<String> extras = new ArrayList<>();
|
||||
int i = 0;
|
||||
while (Translations.hasTranslation(keyBase+i))
|
||||
{
|
||||
extras.add(Translations.tr(keyBase+i));
|
||||
i++;
|
||||
}
|
||||
return (extras.isEmpty()) ? extraInfo : extras;
|
||||
return extraInfo().stream().map(Component::getString).toList();
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,10 @@ import java.lang.annotation.Target;
|
||||
* - String
|
||||
* - a subclass of Enum
|
||||
* The default value of the rule will be the initial value of the field.
|
||||
*
|
||||
* @deprecated Use {@link carpet.api.settings.Rule} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Rule
|
||||
@@ -55,18 +58,18 @@ public @interface Rule
|
||||
/**
|
||||
* If specified, the rule will automatically enable or disable
|
||||
* a builtin Scarpet Rule App with this name.
|
||||
* Consider telling the rule name so users can edit globals
|
||||
* (in case there are relevant globals to edit ofc)
|
||||
*/
|
||||
String appSource() default "";
|
||||
|
||||
/**
|
||||
* The class of the validator checked when the rule is changed.
|
||||
*/
|
||||
Class<? extends Validator>[] validate() default {};
|
||||
@SuppressWarnings("rawtypes")
|
||||
Class<? extends carpet.api.settings.Validator>[] validate() default {};
|
||||
|
||||
/**
|
||||
* The class of the condition checked when the rule is parsed.
|
||||
* The class of the condition checked when the rule is parsed, before being added
|
||||
* to the Settings Manager.
|
||||
*/
|
||||
Class<? extends Condition>[] condition() default {};
|
||||
Class<? extends carpet.api.settings.Rule.Condition>[] condition() default {};
|
||||
}
|
||||
@@ -1,22 +1,59 @@
|
||||
package carpet.settings;
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link carpet.api.settings.RuleCategory} instead. Should be as simple as changing to that in the imports
|
||||
*
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public class RuleCategory
|
||||
{
|
||||
public static final String BUGFIX = "bugfix";
|
||||
public static final String SURVIVAL = "survival";
|
||||
public static final String CREATIVE = "creative";
|
||||
public static final String EXPERIMENTAL = "experimental";
|
||||
public static final String OPTIMIZATION = "optimization";
|
||||
public static final String FEATURE = "feature";
|
||||
public static final String COMMAND = "command";
|
||||
public static final String TNT = "tnt";
|
||||
public static final String DISPENSER = "dispenser";
|
||||
public static final String SCARPET = "scarpet";
|
||||
/**
|
||||
* Use {@link carpet.api.settings.RuleCategory#BUGFIX} instead
|
||||
*/
|
||||
public static final String BUGFIX = carpet.api.settings.RuleCategory.BUGFIX;
|
||||
/**
|
||||
* Use {@link carpet.api.settings.RuleCategory#SURVIVAL} instead
|
||||
*/
|
||||
public static final String SURVIVAL = carpet.api.settings.RuleCategory.SURVIVAL;
|
||||
/**
|
||||
* Use {@link carpet.api.settings.RuleCategory#CREATIVE} instead
|
||||
*/
|
||||
public static final String CREATIVE = carpet.api.settings.RuleCategory.CREATIVE;
|
||||
/**
|
||||
* Use {@link carpet.api.settings.RuleCategory#EXPERIMENTAL} instead
|
||||
*/
|
||||
public static final String EXPERIMENTAL = carpet.api.settings.RuleCategory.EXPERIMENTAL;
|
||||
/**
|
||||
* Use {@link carpet.api.settings.RuleCategory#OPTIMIZATION} instead
|
||||
*/
|
||||
public static final String OPTIMIZATION = carpet.api.settings.RuleCategory.OPTIMIZATION;
|
||||
/**
|
||||
* Use {@link carpet.api.settings.RuleCategory#FEATURE} instead
|
||||
*/
|
||||
public static final String FEATURE = carpet.api.settings.RuleCategory.FEATURE;
|
||||
/**
|
||||
* Use {@link carpet.api.settings.RuleCategory#COMMAND} instead
|
||||
*/
|
||||
public static final String COMMAND = carpet.api.settings.RuleCategory.COMMAND;
|
||||
/**
|
||||
* Use {@link carpet.api.settings.RuleCategory#TNT} instead
|
||||
*/
|
||||
public static final String TNT = carpet.api.settings.RuleCategory.TNT;
|
||||
/**
|
||||
* Use {@link carpet.api.settings.RuleCategory#DISPENSER} instead
|
||||
*/
|
||||
public static final String DISPENSER = carpet.api.settings.RuleCategory.DISPENSER;
|
||||
/**
|
||||
* Use {@link carpet.api.settings.RuleCategory#SCARPET} instead
|
||||
*/
|
||||
public static final String SCARPET = carpet.api.settings.RuleCategory.SCARPET;
|
||||
/**
|
||||
* Rules with this {@link RuleCategory} will have a client-side
|
||||
* counterpart, so they can be set independently without the server
|
||||
* being Carpet's
|
||||
* @deprecated Use {@link carpet.api.settings.RuleCategory#CLIENT} instead
|
||||
*/
|
||||
public static final String CLIENT = "client";
|
||||
@Deprecated(forRemoval = true)
|
||||
public static final String CLIENT = carpet.api.settings.RuleCategory.CLIENT;
|
||||
|
||||
}
|
||||
@@ -2,84 +2,29 @@ package carpet.settings;
|
||||
|
||||
import carpet.CarpetServer;
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.network.ServerNetworkHandler;
|
||||
import carpet.utils.Translations;
|
||||
import carpet.utils.Messenger;
|
||||
import com.google.common.collect.Sets;
|
||||
import carpet.api.settings.CarpetRule;
|
||||
import carpet.utils.CommandHelper;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
|
||||
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.commands.CommandBuildContext;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.SharedSuggestionProvider;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.TickTask;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.storage.LevelResource;
|
||||
import org.apache.logging.log4j.util.TriConsumer;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static carpet.utils.Translations.tr;
|
||||
import static carpet.script.CarpetEventServer.Event.CARPET_RULE_CHANGES;
|
||||
import static net.minecraft.commands.SharedSuggestionProvider.suggest;
|
||||
import static net.minecraft.commands.Commands.argument;
|
||||
import static net.minecraft.commands.Commands.literal;
|
||||
|
||||
/**
|
||||
* Manages and parses Carpet rules with their own command.
|
||||
* @see #SettingsManager(String, String, String)
|
||||
*
|
||||
* @deprecated Use {@link carpet.api.settings.SettingsManager} instead
|
||||
*/
|
||||
public class SettingsManager
|
||||
@Deprecated(forRemoval = true)
|
||||
public class SettingsManager extends carpet.api.settings.SettingsManager
|
||||
{
|
||||
private Map<String, ParsedRule<?>> rules = new HashMap<>();
|
||||
/**
|
||||
* Whether or not this {@link SettingsManager} is locked. That is specified
|
||||
* by writing "locked" at the beginning of the settings file. May not
|
||||
* correctly react to changes.
|
||||
* @deprecated Use {@link #locked()} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true) //to private (or protected?)
|
||||
public boolean locked;
|
||||
private final String version;
|
||||
private final String identifier;
|
||||
private final String fancyName;
|
||||
private MinecraftServer server;
|
||||
private List<TriConsumer<CommandSourceStack, ParsedRule<?>, String>> observers = new ArrayList<>();
|
||||
private static List<TriConsumer<CommandSourceStack, ParsedRule<?>, String>> staticObservers = new ArrayList<>();
|
||||
static record ConfigReadResult(Map<String, String> ruleMap, boolean locked) {}
|
||||
|
||||
/**
|
||||
* Creates a new {@link SettingsManager} without a fancy name.
|
||||
@@ -87,101 +32,56 @@ public class SettingsManager
|
||||
*
|
||||
* @param version A {@link String} with the mod's version
|
||||
* @param identifier A {@link String} with the mod's id, will be the command name
|
||||
* @deprecated This type is deprecated, use {@link carpet.api.settings.SettingsManager#SettingsManager(String, String, String)} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public SettingsManager(String version, String identifier)
|
||||
{
|
||||
this.version = version;
|
||||
this.identifier = identifier;
|
||||
this.fancyName = identifier;
|
||||
this(version, identifier, identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link SettingsManager} with a fancy name
|
||||
* @see #SettingsManager(String, String)
|
||||
*
|
||||
* @param version A {@link String} with the mod's version
|
||||
* @param identifier A {@link String} with the mod's id, will be the command name
|
||||
* @param fancyName A {@link String} being the mod's fancy name.
|
||||
*
|
||||
* @deprecated This type is deprecated, use {@link carpet.api.settings.SettingsManager#SettingsManager(String, String, String)} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public SettingsManager(String version, String identifier, String fancyName)
|
||||
{
|
||||
this.version = version;
|
||||
this.identifier = identifier;
|
||||
this.fancyName = fancyName;
|
||||
super(version, identifier, fancyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A {@link String} being this {@link SettingsManager}'s
|
||||
* identifier, which is also the command name
|
||||
*/
|
||||
public String getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a {@link MinecraftServer} to this {@link SettingsManager}.<br>
|
||||
* This is handled automatically by Carpet
|
||||
* @param server The {@link MinecraftServer} instance to be attached
|
||||
*/
|
||||
public void attachServer(MinecraftServer server)
|
||||
{
|
||||
this.server = server;
|
||||
loadConfigurationFromConf();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches the {@link MinecraftServer} of this {@link SettingsManager} and
|
||||
* resets its {@link ParsedRule}s to their default values.<br>
|
||||
* This is handled automatically by Carpet
|
||||
*/
|
||||
public void detachServer()
|
||||
{
|
||||
for (ParsedRule<?> rule : rules.values()) rule.resetToDefault(null);
|
||||
server = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all annotated fields with the {@link Rule} annotation
|
||||
* to this {@link SettingsManager} in order to handle them.
|
||||
* @param settingsClass The class that will be analyzed
|
||||
*/
|
||||
public void parseSettingsClass(Class settingsClass)
|
||||
{
|
||||
rule: for (Field f : settingsClass.getDeclaredFields())
|
||||
{
|
||||
Rule rule = f.getAnnotation(Rule.class);
|
||||
if (rule == null) continue;
|
||||
ParsedRule<?> parsed = new ParsedRule<>(f, rule, this);
|
||||
for (Class<? extends Condition> condition : rule.condition()) {
|
||||
try
|
||||
{
|
||||
Constructor<?> constr = condition.getDeclaredConstructor();
|
||||
constr.setAccessible(true);
|
||||
if (!((Condition) constr.newInstance()).isTrue())
|
||||
continue rule;
|
||||
}
|
||||
catch (ReflectiveOperationException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
rules.put(parsed.name, parsed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a custom rule observer to changes in rules from
|
||||
* <b>this specific</b> {@link SettingsManager} instance.
|
||||
* @see SettingsManager#addGlobalRuleObserver(TriConsumer)
|
||||
* <p>Adds a custom rule observer to changes in rules from
|
||||
* <b>this specific</b> {@link SettingsManager} instance.</p>
|
||||
*
|
||||
* <p>This <b>outdated</b> method may not be able to listen to all {@link CarpetRule} implementations</p>
|
||||
*
|
||||
* @see SettingsManager#registerGlobalRuleObserver(RuleObserver)
|
||||
*
|
||||
* @param observer A {@link TriConsumer} that will be called with
|
||||
* the used {@link CommandSourceStack}, the changed
|
||||
* {@link ParsedRule} and a {@link String} being the
|
||||
* value that the user typed.
|
||||
* @deprecated Use {@link SettingsManager#registerRuleObserver(RuleObserver)} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true) //to remove
|
||||
public void addRuleObserver(TriConsumer<CommandSourceStack, ParsedRule<?>, String> observer)
|
||||
{
|
||||
observers.add(observer);
|
||||
registerRuleObserver((source, rule, stringValue) -> {
|
||||
if (rule instanceof ParsedRule<?> pr)
|
||||
observer.accept(source, pr, stringValue);
|
||||
else
|
||||
CarpetSettings.LOG.warn("Failed to notify observer '" + observer.getClass().getName() + "' about rule change");
|
||||
});
|
||||
CarpetSettings.LOG.warn("""
|
||||
Extension added outdated rule observer, this is deprecated and will crash in later carpet versions \
|
||||
(way before the rest of the old settings api because of relying on log4j)!
|
||||
The observer class name is '%s'""".formatted(observer.getClass().getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,187 +93,87 @@ public class SettingsManager
|
||||
* the used {@link CommandSourceStack}, the changed
|
||||
* {@link ParsedRule} and a {@link String} being the
|
||||
* value that the user typed.
|
||||
* @deprecated Use {@link SettingsManager#registerRuleObserver(RuleObserver)} instead, given this one can't deal with {@link CarpetRule}
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to remove. This isn't really used anywhere
|
||||
public static void addGlobalRuleObserver(TriConsumer<CommandSourceStack, ParsedRule<?>, String> observer)
|
||||
{
|
||||
staticObservers.add(observer);
|
||||
}
|
||||
|
||||
|
||||
void notifyRuleChanged(CommandSourceStack source, ParsedRule<?> rule, String userTypedValue)
|
||||
{
|
||||
observers.forEach(observer -> observer.accept(source, rule, userTypedValue));
|
||||
staticObservers.forEach(observer -> observer.accept(source, rule, userTypedValue));
|
||||
ServerNetworkHandler.updateRuleWithConnectedClients(rule);
|
||||
switchScarpetRuleIfNeeded(source, rule);
|
||||
if (CARPET_RULE_CHANGES.isNeeded()) CARPET_RULE_CHANGES.onCarpetRuleChanges(rule, source);
|
||||
}
|
||||
|
||||
private void switchScarpetRuleIfNeeded(CommandSourceStack source, ParsedRule<?> rule)
|
||||
{
|
||||
if (!rule.scarpetApp.isEmpty())
|
||||
{
|
||||
if (rule.getBoolValue() || (rule.type == String.class && !rule.get().equals("false")))
|
||||
{
|
||||
CarpetServer.scriptServer.addScriptHost(source, rule.scarpetApp, s -> canUseCommand(s, rule.get()), false, false, true, null);
|
||||
} else {
|
||||
CarpetServer.scriptServer.removeScriptHost(source, rule.scarpetApp, false, true);
|
||||
}
|
||||
}
|
||||
registerGlobalRuleObserver((source, rule, stringValue) -> {
|
||||
if (rule instanceof ParsedRule<?> pr)
|
||||
observer.accept(source, pr, stringValue);
|
||||
else
|
||||
CarpetSettings.LOG.warn("Failed to notify observer '" + observer.getClass().getName() + "' about rule change");
|
||||
});
|
||||
CarpetSettings.LOG.warn("""
|
||||
Extension added outdated rule observer, this is deprecated and will crash in later carpet versions \
|
||||
(way before the rest of the old settings api because of relying on log4j)!
|
||||
The observer class name is '%s'""".formatted(observer.getClass().getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes Scarpet rules in this {@link SettingsManager}, if any.<br>
|
||||
* This is handled automatically by Carpet.
|
||||
* @deprecated Use {@link #identifier()} instead
|
||||
*/
|
||||
public void initializeScarpetRules() {
|
||||
for (ParsedRule<?> rule : rules.values())
|
||||
{
|
||||
if (!rule.scarpetApp.isEmpty()) {
|
||||
switchScarpetRuleIfNeeded(server.createCommandSourceStack(), rule);
|
||||
}
|
||||
}
|
||||
@Deprecated(forRemoval = true)
|
||||
public String getIdentifier() {
|
||||
return identifier();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return An {@link Iterable} list of all categories
|
||||
* from rules in this {@link SettingsManager}
|
||||
*/
|
||||
public Iterable<String> getCategories()
|
||||
{
|
||||
Set<String> categories = new HashSet<>();
|
||||
getRules().stream().map(r -> r.categories).forEach(categories::addAll);
|
||||
return categories;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets a registered rule in this {@link SettingsManager} by its name.
|
||||
* <p>Gets a registered {@link ParsedRule} in this {@link SettingsManager} by its name.</p>
|
||||
* @param name The rule name
|
||||
* @return The {@link ParsedRule} with that name
|
||||
* @deprecated Use {@link #getCarpetRule(String)} instead. This method is not able to return rules not implemented by {@link ParsedRule}
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public ParsedRule<?> getRule(String name)
|
||||
{
|
||||
return rules.get(name);
|
||||
return getCarpetRule(name) instanceof ParsedRule<?> pr ? pr : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A {@link Collection} of the registered rules in this
|
||||
* {@link SettingsManager}.
|
||||
* @return A {@link Collection} of the registered {@link ParsedRule}s in this {@link SettingsManager}.
|
||||
* @deprecated Use {@link #getCarpetRules()} instead. This method won't be able to return rules not implemented by {@link ParsedRule}
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public Collection<ParsedRule<?>> getRules()
|
||||
{
|
||||
return rules.values().stream().sorted().collect(Collectors.toList());
|
||||
return List.of(getCarpetRules().stream().filter(ParsedRule.class::isInstance).map(ParsedRule.class::cast).toArray(ParsedRule[]::new));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A {@link Collection} of {@link ParsedRule}s that have defaults
|
||||
* for this world different than the rule's default value.
|
||||
* @deprecated Use {@link #dumpAllRulesToStream(java.io.PrintStream, String)} instead
|
||||
*/
|
||||
public Collection<ParsedRule<?>> findStartupOverrides()
|
||||
{
|
||||
Set<String> defaults = readSettingsFromConf(getFile()).ruleMap().keySet();
|
||||
return rules.values().stream().filter(r -> defaults.contains(r.name)).
|
||||
sorted().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A collection of {@link ParsedRule} that are not in
|
||||
* their default value
|
||||
*/
|
||||
public Collection<ParsedRule<?>> getNonDefault()
|
||||
{
|
||||
return rules.values().stream().filter(r -> !r.isDefault()).sorted().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Path getFile()
|
||||
{
|
||||
return server.getWorldPath(LevelResource.ROOT).resolve(identifier+".conf");
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables all {@link ParsedRule} with the {@link RuleCategory#COMMAND} category,
|
||||
* called when the {@link SettingsManager} is {@link #locked}.
|
||||
*/
|
||||
public void disableBooleanCommands()
|
||||
{
|
||||
for (ParsedRule<?> rule : rules.values())
|
||||
{
|
||||
if (!rule.categories.contains(RuleCategory.COMMAND))
|
||||
continue;
|
||||
if (rule.type == boolean.class)
|
||||
((ParsedRule<Boolean>) rule).set(server.createCommandSourceStack(), false, "false");
|
||||
if (rule.type == String.class && rule.options.contains("false"))
|
||||
((ParsedRule<String>) rule).set(server.createCommandSourceStack(), "false", "false");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void writeSettingsToConf(Map<String, String> values)
|
||||
{
|
||||
if (locked)
|
||||
return;
|
||||
try (BufferedWriter fw = Files.newBufferedWriter(getFile()))
|
||||
{
|
||||
for (String key: values.keySet())
|
||||
{
|
||||
fw.write(key+" "+values.get(key));
|
||||
fw.newLine();
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
CarpetSettings.LOG.error("[CM]: failed write "+identifier+".conf config file", e);
|
||||
}
|
||||
///todo is it really needed? resendCommandTree();
|
||||
@Deprecated(forRemoval = true)
|
||||
public int printAllRulesToLog(String category) {
|
||||
return dumpAllRulesToStream(System.out, category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies all players that the commands changed by resending the command tree.
|
||||
* @deprecated While there's not an API replacement for this (at least yet),
|
||||
* you can use {@link CommandHelper#notifyPlayersCommandsChanged(MinecraftServer)} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public void notifyPlayersCommandsChanged()
|
||||
{
|
||||
if (server == null || server.getPlayerList() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
server.tell(new TickTask(this.server.getTickCount(), () ->
|
||||
{
|
||||
try {
|
||||
for (ServerPlayer entityplayermp : server.getPlayerList().getPlayers()) {
|
||||
server.getCommands().sendCommands(entityplayermp);
|
||||
}
|
||||
}
|
||||
catch (NullPointerException ignored) {}
|
||||
}));
|
||||
CommandHelper.notifyPlayersCommandsChanged(CarpetServer.minecraft_server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the the {@link CommandSourceStack} can execute
|
||||
* Returns whether the {@link CommandSourceStack} can execute
|
||||
* a command given the required permission level, according to
|
||||
* Carpet's standard for permissions.
|
||||
* @param source The origin {@link CommandSourceStack}
|
||||
* @param commandLevel A {@link String} being the permission level (either 0-4, a
|
||||
* {@link boolean} value or "ops".
|
||||
* @param commandLevel The permission level
|
||||
* @return Whether or not the {@link CommandSourceStack} meets the required level
|
||||
*
|
||||
* @deprecated While there's not an API replacement for this (at least yet),
|
||||
* you can use {@link CommandHelper#canUseCommand(CommandSourceStack, Object)} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public static boolean canUseCommand(CommandSourceStack source, Object commandLevel)
|
||||
{
|
||||
if (commandLevel instanceof Boolean) return (Boolean) commandLevel;
|
||||
String commandLevelString = commandLevel.toString();
|
||||
switch (commandLevelString)
|
||||
{
|
||||
case "true": return true;
|
||||
case "false": return false;
|
||||
case "ops": return source.hasPermission(2); // typical for other cheaty commands
|
||||
case "0":
|
||||
case "1":
|
||||
case "2":
|
||||
case "3":
|
||||
case "4":
|
||||
return source.hasPermission(Integer.parseInt(commandLevelString));
|
||||
}
|
||||
return false;
|
||||
return CommandHelper.canUseCommand(source, commandLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -381,7 +181,13 @@ public class SettingsManager
|
||||
* Carpet's standard for permissions (either 0-4, a {@link boolean},
|
||||
* or "ops".
|
||||
* @return An {@link int} with the translated Vanilla permission level
|
||||
*
|
||||
* @deprecated While there's not an API replacement for this (at least yet),
|
||||
* you can use the similar {@link CommandHelper#canUseCommand(CommandSourceStack, Object)} instead
|
||||
* @apiNote Note that this method returns {@code 2} for {@code false} and {@code 0} for {@code true}.
|
||||
* You probably want to use {@link #canUseCommand(CommandSourceStack, Object)}e
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public static int getCommandLevel(String commandLevel)
|
||||
{
|
||||
switch (commandLevel)
|
||||
@@ -398,183 +204,6 @@ public class SettingsManager
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
private void loadConfigurationFromConf()
|
||||
{
|
||||
for (ParsedRule<?> rule : rules.values()) rule.resetToDefault(server.createCommandSourceStack());
|
||||
ConfigReadResult conf = readSettingsFromConf(getFile());
|
||||
locked = false;
|
||||
if (conf.locked())
|
||||
{
|
||||
CarpetSettings.LOG.info("[CM]: "+fancyName+" features are locked by the administrator");
|
||||
disableBooleanCommands();
|
||||
}
|
||||
for (String key: conf.ruleMap().keySet())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (rules.get(key).set(server.createCommandSourceStack(), conf.ruleMap().get(key)) != null)
|
||||
CarpetSettings.LOG.info("[CM]: loaded setting " + key + " as " + conf.ruleMap().get(key) + " from " + identifier + ".conf");
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
CarpetSettings.LOG.error("[CM Error]: Failed to load setting: "+key+", "+exc);
|
||||
}
|
||||
}
|
||||
locked = conf.locked();
|
||||
}
|
||||
|
||||
|
||||
private ConfigReadResult readSettingsFromConf(Path path)
|
||||
{
|
||||
try (BufferedReader reader = Files.newBufferedReader(path))
|
||||
{
|
||||
String line = "";
|
||||
boolean confLocked = false;
|
||||
Map<String,String> result = new HashMap<String, String>();
|
||||
while ((line = reader.readLine()) != null)
|
||||
{
|
||||
line = line.replaceAll("[\\r\\n]", "");
|
||||
if ("locked".equalsIgnoreCase(line))
|
||||
{
|
||||
confLocked = true;
|
||||
}
|
||||
String[] fields = line.split("\\s+",2);
|
||||
if (fields.length > 1)
|
||||
{
|
||||
if (!rules.containsKey(fields[0]))
|
||||
{
|
||||
CarpetSettings.LOG.error("[CM]: "+fancyName+" Setting " + fields[0] + " is not a valid - ignoring...");
|
||||
continue;
|
||||
}
|
||||
ParsedRule rule = rules.get(fields[0]);
|
||||
|
||||
if (!(rule.options.contains(fields[1])) && rule.isStrict)
|
||||
{
|
||||
CarpetSettings.LOG.error("[CM]: The value of " + fields[1] + " for " + fields[0] + "("+fancyName+") is not valid - ignoring...");
|
||||
continue;
|
||||
}
|
||||
result.put(fields[0],fields[1]);
|
||||
|
||||
}
|
||||
}
|
||||
return new ConfigReadResult(result, confLocked);
|
||||
}
|
||||
catch (NoSuchFileException e)
|
||||
{
|
||||
if (path.equals(getFile()) && FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT)
|
||||
{
|
||||
Path defaultsPath = FabricLoader.getInstance().getConfigDir().resolve("carpet/default_"+identifier+".conf");
|
||||
try {
|
||||
if (Files.notExists(defaultsPath))
|
||||
{
|
||||
Files.createDirectories(defaultsPath.getParent());
|
||||
Files.createFile(defaultsPath);
|
||||
}
|
||||
return readSettingsFromConf(defaultsPath);
|
||||
} catch (IOException e2) {
|
||||
CarpetSettings.LOG.error("Exception when loading fallback default config: ", e2);
|
||||
}
|
||||
}
|
||||
return new ConfigReadResult(new HashMap<>(), false);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
CarpetSettings.LOG.error("Exception while loading Carpet rules from config", e);
|
||||
return new ConfigReadResult(new HashMap<>(), false);
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<ParsedRule<?>> getRulesMatching(String search) {
|
||||
String lcSearch = search.toLowerCase(Locale.ROOT);
|
||||
return rules.values().stream().filter(rule ->
|
||||
{
|
||||
if (rule.name.toLowerCase(Locale.ROOT).contains(lcSearch)) return true; // substring match, case insensitive
|
||||
for (String c : rule.categories) if (c.equals(search)) return true; // category exactly, case sensitive
|
||||
return Sets.newHashSet(rule.description.toLowerCase(Locale.ROOT).split("\\W+")).contains(lcSearch); // contains full term in description, but case insensitive
|
||||
}).sorted().collect(Collectors.toUnmodifiableList());
|
||||
}
|
||||
|
||||
/**
|
||||
* A method to pretty print in markdown (useful for Github wiki/readme) the current
|
||||
* registered rules for a category to the log. Contains the name, description,
|
||||
* categories, type, defaults, wether or not they are strict, their suggested
|
||||
* values, and the descriptions of their validators.
|
||||
* @param category A {@link String} being the category to print, {@link null} to print
|
||||
* all registered rules.
|
||||
* @return {@link int} 1 if things didn't fail, and all the info to System.out
|
||||
*/
|
||||
public int printAllRulesToLog(String category)
|
||||
{
|
||||
PrintStream ps = System.out;
|
||||
ps.println("# "+fancyName+" Settings");
|
||||
for (Map.Entry<String, ParsedRule<?>> e : new TreeMap<>(rules).entrySet())
|
||||
{
|
||||
ParsedRule<?> rule = e.getValue();
|
||||
if (category != null && !rule.categories.contains(category))
|
||||
continue;
|
||||
ps.println("## " + rule.name);
|
||||
ps.println(rule.description+" ");
|
||||
for (String extra : rule.extraInfo)
|
||||
ps.println(extra + " ");
|
||||
ps.println("* Type: `" + rule.type.getSimpleName() + "` ");
|
||||
ps.println("* Default value: `" + rule.defaultAsString + "` ");
|
||||
String optionString = rule.options.stream().map(s -> "`" + s + "`").collect(Collectors.joining(", "));
|
||||
if (!optionString.isEmpty()) ps.println((rule.isStrict?"* Required":"* Suggested")+" options: " + optionString + " ");
|
||||
ps.println("* Categories: " + rule.categories.stream().map(s -> "`" + s.toUpperCase(Locale.ROOT) + "`").collect(Collectors.joining(", ")) + " ");
|
||||
boolean preamble = false;
|
||||
for (Validator<?> validator : rule.validators)
|
||||
{
|
||||
if(validator.description() != null)
|
||||
{
|
||||
if (!preamble)
|
||||
{
|
||||
ps.println("* Additional notes: ");
|
||||
preamble = true;
|
||||
}
|
||||
ps.println(" * "+validator.description()+" ");
|
||||
}
|
||||
}
|
||||
ps.println(" ");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
private ParsedRule<?> contextRule(CommandContext<CommandSourceStack> ctx) throws CommandSyntaxException
|
||||
{
|
||||
String ruleName = StringArgumentType.getString(ctx, "rule");
|
||||
ParsedRule<?> rule = getRule(ruleName);
|
||||
if (rule == null)
|
||||
throw new SimpleCommandExceptionType(Messenger.c("rb "+ tr("ui.unknown_rule","Unknown rule")+": "+ruleName)).create();
|
||||
return rule;
|
||||
}
|
||||
|
||||
static CompletableFuture<Suggestions> suggestMatchingContains(Stream<String> stream, SuggestionsBuilder suggestionsBuilder) {
|
||||
List<String> regularSuggestionList = new ArrayList<>();
|
||||
List<String> smartSuggestionList = new ArrayList<>();
|
||||
String query = suggestionsBuilder.getRemaining().toLowerCase(Locale.ROOT);
|
||||
stream.forEach((listItem) -> {
|
||||
// Regex camelCase Search
|
||||
var words = Arrays.stream(listItem.split("(?<!^)(?=[A-Z])")).map(s -> s.toLowerCase(Locale.ROOT)).collect(Collectors.toList());
|
||||
var prefixes = new ArrayList<String>(words.size());
|
||||
for (int i = 0; i < words.size(); i++)
|
||||
prefixes.add(String.join("",words.subList(i, words.size())));
|
||||
if (prefixes.stream().anyMatch(s -> s.startsWith(query))) {
|
||||
smartSuggestionList.add(listItem);
|
||||
}
|
||||
// Regular prefix matching, reference: CommandSource.suggestMatching
|
||||
if (SharedSuggestionProvider.matchesSubStr(query, listItem.toLowerCase(Locale.ROOT))) {
|
||||
regularSuggestionList.add(listItem);
|
||||
}
|
||||
});
|
||||
var filteredSuggestionList = regularSuggestionList.isEmpty() ? smartSuggestionList : regularSuggestionList;
|
||||
Objects.requireNonNull(suggestionsBuilder);
|
||||
filteredSuggestionList.forEach(suggestionsBuilder::suggest);
|
||||
return suggestionsBuilder.buildFuture();
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
public void registerCommand(CommandDispatcher<CommandSourceStack> dispatcher)
|
||||
{
|
||||
@@ -582,216 +211,4 @@ public class SettingsManager
|
||||
context.missingTagAccessPolicy(CommandBuildContext.MissingTagAccessPolicy.RETURN_EMPTY);
|
||||
registerCommand(dispatcher, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the settings command for this {@link SettingsManager}.<br>
|
||||
* It is handled automatically by Carpet.
|
||||
* @param dispatcher The current {@link CommandDispatcher}
|
||||
* @param commandBuildContext The current {@link CommandBuildContext}
|
||||
*/
|
||||
public void registerCommand(final CommandDispatcher<CommandSourceStack> dispatcher, final CommandBuildContext commandBuildContext)
|
||||
{
|
||||
if (dispatcher.getRoot().getChildren().stream().anyMatch(node -> node.getName().equalsIgnoreCase(identifier)))
|
||||
{
|
||||
CarpetSettings.LOG.error("Failed to add settings command for " + identifier + ". It is masking previous command.");
|
||||
return;
|
||||
}
|
||||
|
||||
LiteralArgumentBuilder<CommandSourceStack> literalargumentbuilder = literal(identifier).requires((player) ->
|
||||
canUseCommand(player, CarpetSettings.carpetCommandPermissionLevel) && !locked);
|
||||
|
||||
literalargumentbuilder.executes((context)-> listAllSettings(context.getSource())).
|
||||
then(literal("list").
|
||||
executes( (c) -> listSettings(c.getSource(), String.format(tr("ui.all_%(mod)s_settings","All %s Settings"), fancyName),
|
||||
getRules())).
|
||||
then(literal("defaults").
|
||||
executes( (c)-> listSettings(c.getSource(),
|
||||
String.format(tr("ui.current_%(mod)s_startup_settings_from_%(conf)s","Current %s Startup Settings from %s"), fancyName, (identifier+".conf")),
|
||||
findStartupOverrides()))).
|
||||
then(argument("tag",StringArgumentType.word()).
|
||||
suggests( (c, b)->suggest(getCategories(), b)).
|
||||
executes( (c) -> listSettings(c.getSource(),
|
||||
String.format(tr("ui.%(mod)s_settings_matching_'%(query)s'","%s Settings matching \"%s\""), fancyName, tr("category." + StringArgumentType.getString(c, "tag"),StringArgumentType.getString(c, "tag"))),
|
||||
getRulesMatching(StringArgumentType.getString(c, "tag")))))).
|
||||
then(literal("removeDefault").
|
||||
requires(s -> !locked).
|
||||
then(argument("rule", StringArgumentType.word()).
|
||||
suggests( (c, b) -> suggestMatchingContains(getRules().stream().map(r -> r.name), b)).
|
||||
executes((c) -> removeDefault(c.getSource(), contextRule(c))))).
|
||||
then(literal("setDefault").
|
||||
requires(s -> !locked).
|
||||
then(argument("rule", StringArgumentType.word()).
|
||||
suggests( (c, b) -> suggestMatchingContains(getRules().stream().map(r -> r.name), b)).
|
||||
then(argument("value", StringArgumentType.greedyString()).
|
||||
suggests((c, b)-> suggest(contextRule(c).options, b)).
|
||||
executes((c) -> setDefault(c.getSource(), contextRule(c), StringArgumentType.getString(c, "value")))))).
|
||||
then(argument("rule", StringArgumentType.word()).
|
||||
suggests( (c, b) -> suggestMatchingContains(getRules().stream().map(r -> r.name), b)).
|
||||
requires(s -> !locked ).
|
||||
executes( (c) -> displayRuleMenu(c.getSource(), contextRule(c))).
|
||||
then(argument("value", StringArgumentType.greedyString()).
|
||||
suggests((c, b)-> suggest(contextRule(c).options,b)).
|
||||
executes((c) -> setRule(c.getSource(), contextRule(c), StringArgumentType.getString(c, "value")))));
|
||||
|
||||
dispatcher.register(literalargumentbuilder);
|
||||
}
|
||||
|
||||
private int displayRuleMenu(CommandSourceStack source, ParsedRule<?> rule)
|
||||
{
|
||||
Player player;
|
||||
String displayName = rule.translatedName();
|
||||
|
||||
Messenger.m(source, "");
|
||||
Messenger.m(source, "wb "+ displayName ,"!/"+identifier+" "+rule.name,"^g refresh");
|
||||
Messenger.m(source, "w "+ rule.translatedDescription());
|
||||
|
||||
rule.translatedExtras().forEach(s -> Messenger.m(source, "g "+s));
|
||||
|
||||
List<Component> tags = new ArrayList<>();
|
||||
tags.add(Messenger.c("w "+ tr("ui.tags", "Tags")+": "));
|
||||
for (String t: rule.categories)
|
||||
{
|
||||
String translated = tr("category." + t, t);
|
||||
tags.add(Messenger.c("c ["+ translated +"]", "^g "+ String.format(tr("list_all_%s_settings","list all %s settings"), translated),"!/"+identifier+" list "+t));
|
||||
tags.add(Messenger.c("w , "));
|
||||
}
|
||||
tags.remove(tags.size()-1);
|
||||
Messenger.m(source, tags.toArray(new Object[0]));
|
||||
|
||||
Messenger.m(source, "w "+ tr("ui.current_value", "Current value")+": ",String.format("%s %s (%s value)",rule.getBoolValue()?"lb":"nb", rule.getAsString(),rule.isDefault()?"default":"modified"));
|
||||
List<Component> options = new ArrayList<>();
|
||||
options.add(Messenger.c("w Options: ", "y [ "));
|
||||
for (String o: rule.options)
|
||||
{
|
||||
options.add(makeSetRuleButton(rule, o, false));
|
||||
options.add(Messenger.c("w "));
|
||||
}
|
||||
options.remove(options.size()-1);
|
||||
options.add(Messenger.c("y ]"));
|
||||
Messenger.m(source, options.toArray(new Object[0]));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private int setRule(CommandSourceStack source, ParsedRule<?> rule, String newValue)
|
||||
{
|
||||
if (rule.set(source, newValue) != null)
|
||||
Messenger.m(source, "w "+rule.toString()+", ", "c ["+ tr("ui.change_permanently","change permanently")+"?]",
|
||||
"^w "+String.format(tr("ui.click_to_keep_the_settings_in_%(conf)s_to_save_across_restarts","Click to keep the settings in %s to save across restarts"), identifier+".conf"),
|
||||
"?/"+identifier+" setDefault "+rule.name+" "+rule.getAsString());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// stores different defaults in the file
|
||||
private int setDefault(CommandSourceStack source, ParsedRule<?> rule, String stringValue)
|
||||
{
|
||||
if (locked) return 0;
|
||||
if (!rules.containsKey(rule.name)) return 0;
|
||||
ConfigReadResult conf = readSettingsFromConf(getFile());
|
||||
conf.ruleMap().put(rule.name, stringValue);
|
||||
writeSettingsToConf(conf.ruleMap()); // this may feels weird, but if conf
|
||||
// is locked, it will never reach this point.
|
||||
rule.set(source,stringValue);
|
||||
Messenger.m(source ,"gi "+String.format(tr("ui.rule_%(rule)s_will_now_default_to_%(value)s","Rule %s will now default to %s"), rule.translatedName(), stringValue));
|
||||
return 1;
|
||||
}
|
||||
// removes overrides of the default values in the file
|
||||
private int removeDefault(CommandSourceStack source, ParsedRule<?> rule)
|
||||
{
|
||||
if (locked) return 0;
|
||||
if (!rules.containsKey(rule.name)) return 0;
|
||||
ConfigReadResult conf = readSettingsFromConf(getFile());
|
||||
conf.ruleMap().remove(rule.name);
|
||||
writeSettingsToConf(conf.ruleMap());
|
||||
rules.get(rule.name).resetToDefault(source);
|
||||
Messenger.m(source ,"gi "+String.format(tr("ui.rule_%(rule)s_not_set_restart","Rule %s will now not be set on restart"), rule.translatedName()));
|
||||
return 1;
|
||||
}
|
||||
|
||||
private Component displayInteractiveSetting(ParsedRule<?> rule)
|
||||
{
|
||||
String displayName = rule.translatedName();
|
||||
List<Object> args = new ArrayList<>();
|
||||
args.add("w - "+ displayName +" ");
|
||||
args.add("!/"+identifier+" "+rule.name);
|
||||
args.add("^y "+rule.translatedDescription());
|
||||
for (String option: rule.options)
|
||||
{
|
||||
args.add(makeSetRuleButton(rule, option, true));
|
||||
args.add("w ");
|
||||
}
|
||||
if (!rule.options.contains(rule.getAsString()))
|
||||
{
|
||||
args.add(makeSetRuleButton(rule, rule.getAsString(), true));
|
||||
args.add("w ");
|
||||
}
|
||||
args.remove(args.size()-1);
|
||||
return Messenger.c(args.toArray(new Object[0]));
|
||||
}
|
||||
|
||||
private Component makeSetRuleButton(ParsedRule<?> rule, String option, boolean brackets)
|
||||
{
|
||||
String style = rule.isDefault()?"g":(option.equalsIgnoreCase(rule.defaultAsString)?"e":"y");
|
||||
if (option.equalsIgnoreCase(rule.getAsString()))
|
||||
{
|
||||
style = style + "u";
|
||||
if (option.equalsIgnoreCase(rule.defaultAsString))
|
||||
style = style + "b";
|
||||
}
|
||||
String baseText = style + (brackets ? " [" : " ") + option + (brackets ? "]" : "");
|
||||
if (locked)
|
||||
return Messenger.c(baseText, "^g "+fancyName+" " + tr("ui.settings_are_locked","settings are locked"));
|
||||
if (option.equalsIgnoreCase(rule.getAsString()))
|
||||
return Messenger.c(baseText);
|
||||
return Messenger.c(baseText, "^g "+ tr("ui.switch_to","Switch to") +" " + option+(option.equalsIgnoreCase(rule.defaultAsString)?" (default)":""), "?/"+identifier+" " + rule.name + " " + option);
|
||||
}
|
||||
|
||||
private int listSettings(CommandSourceStack source, String title, Collection<ParsedRule<?>> settings_list)
|
||||
{
|
||||
|
||||
Messenger.m(source,String.format("wb %s:",title));
|
||||
settings_list.forEach(e -> Messenger.m(source,displayInteractiveSetting(e)));
|
||||
return settings_list.size();
|
||||
}
|
||||
private int listAllSettings(CommandSourceStack source)
|
||||
{
|
||||
int count = listSettings(source, String.format(tr("ui.current_%(mod)s_settings","Current %s Settings"), fancyName), getNonDefault());
|
||||
|
||||
if (version != null)
|
||||
Messenger.m(source, "g "+fancyName+" "+ tr("ui.version", "version") + ": "+ version);
|
||||
|
||||
List<Object> tags = new ArrayList<>();
|
||||
tags.add("w " + tr("ui.browse_categories", "Browse Categories") + ":\n");
|
||||
for (String t : getCategories())
|
||||
{
|
||||
String catKey = "category." + t;
|
||||
String translated = tr(catKey, t);
|
||||
String translatedPlus = Translations.hasTranslation(catKey) ? String.format("%s (%s)",tr(catKey, t), t) : t;
|
||||
tags.add("c [" + translated +"]");
|
||||
tags.add("^g " + String.format(tr("ui.list_all_%(cat)s_settings","list all %s settings"), translatedPlus));
|
||||
tags.add("!/"+identifier+" list " + t);
|
||||
tags.add("w ");
|
||||
}
|
||||
tags.remove(tags.size() - 1);
|
||||
Messenger.m(source, tags.toArray(new Object[0]));
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public void inspectClientsideCommand(CommandSourceStack source, String string)
|
||||
{
|
||||
if (string.startsWith("/"+identifier+" "))
|
||||
{
|
||||
String[] res = string.split("\\s+", 3);
|
||||
if (res.length == 3)
|
||||
{
|
||||
String setting = res[1];
|
||||
String strOption = res[2];
|
||||
if (rules.containsKey(setting) && rules.get(setting).isClient)
|
||||
{
|
||||
rules.get(setting).set(source, strOption);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,82 @@
|
||||
package carpet.settings;
|
||||
|
||||
import carpet.CarpetServer;
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.api.settings.CarpetRule;
|
||||
import carpet.api.settings.Validators;
|
||||
import carpet.utils.CommandHelper;
|
||||
import carpet.utils.Messenger;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
|
||||
public abstract class Validator<T>
|
||||
/**
|
||||
* @deprecated Use {@link carpet.api.settings.Validator} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public abstract class Validator<T> extends carpet.api.settings.Validator<T>
|
||||
{
|
||||
{
|
||||
// Print deprecation warning once while instantiating the class
|
||||
CarpetSettings.LOG.warn("""
|
||||
Validator '%s' is implementing the old Validator class! This class is deprecated and will be removed \
|
||||
and crash in later Carpet versions!""".formatted(getClass().getName()));
|
||||
}
|
||||
/**
|
||||
* Validate the new value of a rule
|
||||
* @return true if valid, false if new rule invalid.
|
||||
* @return a value if the given one was valid or could be cleanly adapted, null if new value is invalid.
|
||||
*/
|
||||
public abstract T validate(CommandSourceStack source, ParsedRule<T> currentRule, T newValue, String string);
|
||||
public String description() {return null;}
|
||||
public void notifyFailure(CommandSourceStack source, ParsedRule<T> currentRule, String providedValue)
|
||||
{
|
||||
Messenger.m(source, "r Wrong value for " + currentRule.name + ": " + providedValue);
|
||||
@Override
|
||||
public final T validate(CommandSourceStack source, CarpetRule<T> changingRule, T newValue, String stringInput) {
|
||||
// Compatibility code
|
||||
if (!(changingRule instanceof ParsedRule<T> parsedRule))
|
||||
// Throwing here is not an issue because Carpet's current implementation only calls validators with ParsedRule.
|
||||
// This would be thrown if a different implementation tries to use it, and then it's their issue in multiple ways
|
||||
throw new IllegalArgumentException("Passed a non-ParsedRule to a validator using the outdated method!");
|
||||
return validate(source, parsedRule, newValue, stringInput);
|
||||
}
|
||||
/**
|
||||
* @deprecated Implement {@link #validate(CommandSourceStack, CarpetRule, Object, String)} instead! It will get abstract soon!
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public abstract T validate(CommandSourceStack source, ParsedRule<T> currentRule, T newValue, String string);
|
||||
|
||||
public static class _COMMAND<T> extends Validator<T>
|
||||
/**
|
||||
* @deprecated Use {@link Validators.CommandLevel} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to remove
|
||||
public static class _COMMAND_LEVEL_VALIDATOR extends Validators.CommandLevel {}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link Validators.NonNegativeNumber} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to remove
|
||||
public static class NONNEGATIVE_NUMBER<T extends Number> extends Validators.NonNegativeNumber<T> {}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link Validators.Probablity} instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true) // to remove
|
||||
public static class PROBABILITY <T extends Number> extends Validators.Probablity<T> {}
|
||||
|
||||
// The ones below are part of the implementation of ParsedRule or printRulesToLog, so they need to be close to it to stay hidden
|
||||
// They will need to be moved when moving ParsedRule
|
||||
|
||||
static class _COMMAND<T> extends carpet.api.settings.Validator<T>
|
||||
{
|
||||
@Override
|
||||
public T validate(CommandSourceStack source, ParsedRule<T> currentRule, T newValue, String string)
|
||||
public T validate(CommandSourceStack source, CarpetRule<T> currentRule, T newValue, String string)
|
||||
{
|
||||
if (CarpetServer.settingsManager != null && source != null)
|
||||
CarpetServer.settingsManager.notifyPlayersCommandsChanged();
|
||||
if (source != null)
|
||||
CommandHelper.notifyPlayersCommandsChanged(source.getServer());
|
||||
return newValue;
|
||||
}
|
||||
@Override
|
||||
public String description() { return "It has an accompanying command";}
|
||||
}
|
||||
|
||||
public static class _CLIENT<T> extends Validator<T>
|
||||
// maybe remove this one and make printRulesToLog check for canBeToggledClientSide instead
|
||||
static class _CLIENT<T> extends carpet.api.settings.Validator<T>
|
||||
{
|
||||
@Override
|
||||
public T validate(CommandSourceStack source, ParsedRule<T> currentRule, T newValue, String string)
|
||||
public T validate(CommandSourceStack source, CarpetRule<T> currentRule, T newValue, String string)
|
||||
{
|
||||
return newValue;
|
||||
}
|
||||
@@ -46,24 +86,9 @@ public abstract class Validator<T>
|
||||
" separately for the desired effect";}
|
||||
}
|
||||
|
||||
public static class _COMMAND_LEVEL_VALIDATOR extends Validator<String> {
|
||||
private static List<String> OPTIONS = List.of("true", "false", "ops", "0", "1", "2", "3", "4");
|
||||
@Override public String validate(CommandSourceStack source, ParsedRule<String> currentRule, String newValue, String userString) {
|
||||
if (!OPTIONS.contains(userString.toLowerCase(Locale.ROOT)))
|
||||
{
|
||||
Messenger.m(source, "r Valid options for command type rules is 'true' or 'false'");
|
||||
Messenger.m(source, "r Optionally you can choose 'ops' to only allow operators");
|
||||
Messenger.m(source, "r or provide a custom required permission level");
|
||||
return null;
|
||||
}
|
||||
return userString.toLowerCase(Locale.ROOT);
|
||||
}
|
||||
@Override public String description() { return "Can be limited to 'ops' only, or a custom permission level";}
|
||||
}
|
||||
|
||||
public static class _SCARPET<T> extends Validator<T> {
|
||||
static class ScarpetValidator<T> extends carpet.api.settings.Validator<T> { //TODO remove? The additional info isn't that useful tbh
|
||||
@Override
|
||||
public T validate(CommandSourceStack source, ParsedRule<T> currentRule, T newValue, String string)
|
||||
public T validate(CommandSourceStack source, CarpetRule<T> currentRule, T newValue, String string)
|
||||
{
|
||||
return newValue;
|
||||
}
|
||||
@@ -72,65 +97,17 @@ public abstract class Validator<T>
|
||||
}
|
||||
}
|
||||
|
||||
public static class WIP<T> extends Validator<T>
|
||||
static class StrictValidator<T> extends carpet.api.settings.Validator<T>
|
||||
{
|
||||
@Override
|
||||
public T validate(CommandSourceStack source, ParsedRule<T> currentRule, T newValue, String string)
|
||||
public T validate(CommandSourceStack source, CarpetRule<T> currentRule, T newValue, String string)
|
||||
{
|
||||
Messenger.m(source, "r "+currentRule.name+" is missing a few bits - we are still working on it.");
|
||||
return newValue;
|
||||
}
|
||||
@Override
|
||||
public String description() { return "A few bits still needs implementing - we are working on it";}
|
||||
}
|
||||
public static class _STRICT<T> extends Validator<T>
|
||||
{
|
||||
@Override
|
||||
public T validate(CommandSourceStack source, ParsedRule<T> currentRule, T newValue, String string)
|
||||
{
|
||||
if (!currentRule.options.contains(string))
|
||||
if (!currentRule.suggestions().contains(string))
|
||||
{
|
||||
Messenger.m(source, "r Valid options: " + currentRule.options.toString());
|
||||
Messenger.m(source, "r Valid options: " + currentRule.suggestions().toString());
|
||||
return null;
|
||||
}
|
||||
return newValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static class _STRICT_IGNORECASE<T> extends Validator<T>
|
||||
{
|
||||
@Override
|
||||
public T validate(CommandSourceStack source, ParsedRule<T> currentRule, T newValue, String string)
|
||||
{
|
||||
if (!currentRule.options.stream().map(s->s.toLowerCase(Locale.ROOT)).collect(Collectors.toSet())
|
||||
.contains(string.toLowerCase(Locale.ROOT)))
|
||||
{
|
||||
Messenger.m(source, "r Valid options (case insensitive): " + currentRule.options.toString());
|
||||
return null;
|
||||
}
|
||||
return newValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static class NONNEGATIVE_NUMBER <T extends Number> extends Validator<T>
|
||||
{
|
||||
@Override
|
||||
public T validate(CommandSourceStack source, ParsedRule<T> currentRule, T newValue, String string)
|
||||
{
|
||||
return newValue.doubleValue() >= 0 ? newValue : null;
|
||||
}
|
||||
@Override
|
||||
public String description() { return "Must be a positive number";}
|
||||
}
|
||||
|
||||
public static class PROBABILITY <T extends Number> extends Validator<T>
|
||||
{
|
||||
@Override
|
||||
public T validate(CommandSourceStack source, ParsedRule<T> currentRule, T newValue, String string)
|
||||
{
|
||||
return (newValue.doubleValue() >= 0 && newValue.doubleValue() <= 1 )? newValue : null;
|
||||
}
|
||||
@Override
|
||||
public String description() { return "Must be between 0 and 1";}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package carpet.utils;
|
||||
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.TickTask;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
|
||||
/**
|
||||
* A few helpful methods to work with settings and commands.
|
||||
*
|
||||
* This is not any kind of API, but it's unlikely to change
|
||||
*
|
||||
*/
|
||||
public final class CommandHelper {
|
||||
private CommandHelper() {}
|
||||
/**
|
||||
* Notifies all players that the commands changed by resending the command tree.
|
||||
*/
|
||||
public static void notifyPlayersCommandsChanged(MinecraftServer server)
|
||||
{
|
||||
if (server == null || server.getPlayerList() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
server.tell(new TickTask(server.getTickCount(), () ->
|
||||
{
|
||||
try {
|
||||
for (ServerPlayer player : server.getPlayerList().getPlayers()) {
|
||||
server.getCommands().sendCommands(player);
|
||||
}
|
||||
}
|
||||
catch (NullPointerException ignored) {}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given source has enough permission level to run a command that requires the given commandLevel
|
||||
*/
|
||||
public static boolean canUseCommand(CommandSourceStack source, Object commandLevel)
|
||||
{
|
||||
if (commandLevel instanceof Boolean) return (Boolean) commandLevel;
|
||||
String commandLevelString = commandLevel.toString();
|
||||
switch (commandLevelString)
|
||||
{
|
||||
case "true": return true;
|
||||
case "false": return false;
|
||||
case "ops": return source.hasPermission(2); // typical for other cheaty commands
|
||||
case "0":
|
||||
case "1":
|
||||
case "2":
|
||||
case "3":
|
||||
case "4":
|
||||
return source.hasPermission(Integer.parseInt(commandLevelString));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package carpet.utils;
|
||||
|
||||
/**
|
||||
* This is not public API!
|
||||
*/
|
||||
public final class TranslationKeys {
|
||||
public static final String BASE_RULE_NAMESPACE = "%s.rule.";
|
||||
public static final String BASE_RULE_PATTERN = BASE_RULE_NAMESPACE + "%s."; // [settingsManager].rule.[name]
|
||||
public static final String RULE_NAME_PATTERN = BASE_RULE_PATTERN + "name";
|
||||
public static final String RULE_DESC_PATTERN = BASE_RULE_PATTERN + "desc";
|
||||
public static final String RULE_EXTRA_PREFIX_PATTERN = BASE_RULE_PATTERN + "extra.";
|
||||
public static final String CATEGORY_PATTERN = "%s.category.%s"; //[settingsManager].category.[name]
|
||||
|
||||
// Settings command
|
||||
private static final String SETTINGS_BASE = "carpet.settings.command.";
|
||||
public static final String BROWSE_CATEGORIES = SETTINGS_BASE + "browse_categories";
|
||||
public static final String VERSION = SETTINGS_BASE + "version";
|
||||
public static final String LIST_ALL_CATEGORY = SETTINGS_BASE + "list_all_category";
|
||||
public static final String CURRENT_SETTINGS_HEADER = SETTINGS_BASE + "current_settings_header";
|
||||
public static final String SWITCH_TO = SETTINGS_BASE + "switch_to";
|
||||
public static final String UNKNOWN_RULE = SETTINGS_BASE + "unknown_rule";
|
||||
public static final String CURRENT_FROM_FILE_HEADER = SETTINGS_BASE + "current_from_file_header";
|
||||
public static final String MOD_SETTINGS_MATCHING = SETTINGS_BASE + "mod_settings_matching";
|
||||
public static final String ALL_MOD_SETTINGS = SETTINGS_BASE + "all_mod_settings";
|
||||
public static final String TAGS = SETTINGS_BASE + "tags";
|
||||
public static final String CHANGE_PERMANENTLY = SETTINGS_BASE + "change_permanently";
|
||||
public static final String CHANGE_PERMANENTLY_HOVER = SETTINGS_BASE + "change_permanently_tooltip";
|
||||
public static final String DEFAULT_SET = SETTINGS_BASE + "default_set";
|
||||
public static final String DEFAULT_REMOVED = SETTINGS_BASE + "default_removed";
|
||||
public static final String CURRENT_VALUE = SETTINGS_BASE + "current_value";
|
||||
}
|
||||
@@ -10,33 +10,38 @@ import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
|
||||
public class Translations
|
||||
{
|
||||
private static Map<String, String> translationMap;
|
||||
private static Map<String, String> translationMap = Collections.emptyMap();
|
||||
|
||||
public static String tr(String key)
|
||||
{
|
||||
return translationMap == null ? key : translationMap.getOrDefault(key, key);
|
||||
return translationMap.getOrDefault(key, key);
|
||||
}
|
||||
|
||||
public static String trOrNull(String key)
|
||||
{
|
||||
return translationMap.get(key);
|
||||
}
|
||||
|
||||
public static String tr(String key, String str)
|
||||
{
|
||||
return translationMap == null ? str : translationMap.getOrDefault(key, str);
|
||||
return translationMap.getOrDefault(key, str);
|
||||
}
|
||||
|
||||
public static boolean hasTranslations()
|
||||
{
|
||||
return translationMap != null;
|
||||
return !translationMap.isEmpty();
|
||||
}
|
||||
|
||||
public static boolean hasTranslation(String key)
|
||||
{
|
||||
return translationMap != null && translationMap.containsKey(key);
|
||||
return translationMap.containsKey(key);
|
||||
}
|
||||
|
||||
public static Map<String, String> getTranslationFromResourcePath(String path)
|
||||
@@ -45,44 +50,59 @@ public class Translations
|
||||
try
|
||||
{
|
||||
dataJSON = IOUtils.toString(
|
||||
Objects.requireNonNull(Translations.class.getClassLoader().getResourceAsStream(String.format("assets/carpet/lang/%s.json", CarpetSettings.language))),
|
||||
Objects.requireNonNull(Translations.class.getClassLoader().getResourceAsStream(path)),
|
||||
StandardCharsets.UTF_8);
|
||||
} catch (NullPointerException | IOException e) {
|
||||
return null;
|
||||
return Map.of();
|
||||
}
|
||||
Gson gson = new GsonBuilder().enableComplexMapKeySerialization().create();
|
||||
Gson gson = new GsonBuilder().setLenient().create();
|
||||
return gson.fromJson(dataJSON, new TypeToken<Map<String, String>>() {}.getType());
|
||||
}
|
||||
|
||||
|
||||
public static void updateLanguage(CommandSourceStack source)
|
||||
public static void updateLanguage()
|
||||
{
|
||||
if (CarpetSettings.language.equalsIgnoreCase("none"))
|
||||
{
|
||||
translationMap = null;
|
||||
return;
|
||||
}
|
||||
Map<String, String> translations = new HashMap<>();
|
||||
Map<String, String> trans = getTranslationFromResourcePath(String.format("assets/carpet/lang/%s.json", CarpetSettings.language));
|
||||
if (trans != null) trans.forEach(translations::put);
|
||||
translations.putAll(getTranslationFromResourcePath(String.format("assets/carpet/lang/%s.json", CarpetSettings.language)));
|
||||
|
||||
for (CarpetExtension ext : CarpetServer.extensions)
|
||||
{
|
||||
Map<String, String> extMappings = ext.canHasTranslations(CarpetSettings.language);
|
||||
if (extMappings != null)
|
||||
{
|
||||
extMappings.forEach((key, value) ->
|
||||
{
|
||||
if (!translations.containsKey(key)) translations.put(key, value);
|
||||
});
|
||||
if (extMappings == null) continue; // would be nice to get rid of this, but too many extensions return null where they don't know they do
|
||||
boolean warned = false;
|
||||
for (var entry : extMappings.entrySet()) {
|
||||
var key = entry.getKey();
|
||||
// Migrate the old format
|
||||
if (!key.startsWith("carpet.")) {
|
||||
if (key.startsWith("rule.")) {
|
||||
// default to carpet's settings manager. Custom managers are really uncommon and the known ones don't provide translations anyway
|
||||
key = TranslationKeys.BASE_RULE_NAMESPACE.formatted("carpet") + key.substring(5);
|
||||
} else if (key.startsWith("category.")) {
|
||||
key = TranslationKeys.CATEGORY_PATTERN.formatted("carpet", key.substring(9));
|
||||
}
|
||||
if (!warned && key != entry.getKey()) {
|
||||
CarpetSettings.LOG.warn("""
|
||||
Found outdated translation keys in extension '%s'!
|
||||
These won't be supported in a later Carpet version!
|
||||
Carpet will now try to map them to the correct keys in a best-effort basis""".formatted(ext.getClass().getName()));
|
||||
warned = true;
|
||||
}
|
||||
}
|
||||
translations.putIfAbsent(key, entry.getValue());
|
||||
}
|
||||
}
|
||||
translations.entrySet().removeIf(e -> e.getKey().startsWith("//"));
|
||||
if (translations.isEmpty())
|
||||
{
|
||||
translationMap = null;
|
||||
return;
|
||||
}
|
||||
translations.keySet().removeIf(e -> {
|
||||
if (e.startsWith("//")) {
|
||||
CarpetSettings.LOG.warn("""
|
||||
Found translation key starting with // while preparing translations!
|
||||
Doing this is deprecated and may cause issues in later versions! Consider settings GSON to "lenient" mode and
|
||||
using regular comments instead!
|
||||
Translation key is '%s'""".formatted(e));
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
});
|
||||
// Remove after deprecated settings api is removed
|
||||
addFallbacksTo(translations);
|
||||
translationMap = translations;
|
||||
}
|
||||
|
||||
@@ -91,4 +111,20 @@ public class Translations
|
||||
// will put some validations for availble languages at some point
|
||||
return true;
|
||||
}
|
||||
|
||||
// fallbacks for old rules that don't define rule descriptions or stuff in language files yet
|
||||
// to be removed when old settings system is removed and translation refactor is finished
|
||||
|
||||
private static final Map<String, String> FALLBACKS = new HashMap<>();
|
||||
/**
|
||||
* @deprecated if you compile against this method I'll steal your kneecaps
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public static void registerFallbackTranslation(String key, String description) {
|
||||
FALLBACKS.put(key, description);
|
||||
}
|
||||
|
||||
private static void addFallbacksTo(Map<String, String> translationMap) {
|
||||
FALLBACKS.forEach(translationMap::putIfAbsent);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +1,36 @@
|
||||
// This file isn't necessary at all, because the language module would use the strings coded in the source if language "none" is selected.
|
||||
// So en_us is the default language and probably no change will be made after loading this file.
|
||||
// But it could be a good example for writing the language file for other languages.
|
||||
|
||||
{
|
||||
"// Rules:": "",
|
||||
"rule.portalSuffocationFix.name": "",
|
||||
"// the key is genetated as:" : "String.format('rule.%s.name', rule.name)",
|
||||
// TODO Rules
|
||||
|
||||
// Categories:
|
||||
|
||||
"rule.portalSuffocationFix.desc": "",
|
||||
"// the key is genetated as:" : "String.format('rule.%s.desc', rule.name)",
|
||||
"carpet.category.bugfix": "bugfix",
|
||||
"carpet.category.survival": "survival",
|
||||
"carpet.category.creative": "creative",
|
||||
"carpet.category.experimental": "experimental",
|
||||
"carpet.category.optimization": "optimization",
|
||||
"carpet.category.feature": "feature",
|
||||
"carpet.category.command": "command",
|
||||
"carpet.category.tnt": "TNT",
|
||||
"carpet.category.dispenser": "dispenser",
|
||||
"carpet.category.scarpet": "scarpet",
|
||||
"carpet.category.client": "client",
|
||||
|
||||
// Settings command UI elements
|
||||
|
||||
"rule.portalSuffocationFix.extra.0": "",
|
||||
"rule.portalSuffocationFix.extra.1": "",
|
||||
"// the key is genetated as:" : "String.format('rule.%s.extra.%i', rule.name, index)",
|
||||
|
||||
|
||||
"// Categories:": "",
|
||||
|
||||
"category.bugfix": "bugfix",
|
||||
"category.survival": "survival",
|
||||
"category.creative": "creative",
|
||||
"category.experimental": "experimental",
|
||||
"category.optimization": "optimization",
|
||||
"category.feature": "feature",
|
||||
"category.command": "command",
|
||||
"category.tnt": "TNT",
|
||||
"category.dispenser": "dispenser",
|
||||
"category.scarpet": "scarpet",
|
||||
"category.client": "client",
|
||||
|
||||
"// Settings command UI elements": "",
|
||||
|
||||
"ui.browse_categories": "Browse categories",
|
||||
"ui.list_all_%(cat)s_settings": "list all %s settings",
|
||||
"ui.version": "Version",
|
||||
"ui.current_%(mod)s_settings": "Current %s settings",
|
||||
"ui.switch_to": "Switch to",
|
||||
"ui.settings_are_locked": "settings are locked",
|
||||
"ui.is_set_to": "is set to",
|
||||
"ui.unknown_rule": "Unknown rule",
|
||||
"ui.current_%(mod)s_startup_settings_from_%(conf)s": "Current %s Startup Settings from %s",
|
||||
"ui.%(mod)s_settings_matching_'%(query)s'": "%s settings matching \"%s\" ",
|
||||
"ui.all_%(mod)s_settings": "All %s settings",
|
||||
"ui.tags": "Tags",
|
||||
"ui.change_permanently": "Change permanently",
|
||||
"ui.click_to_keep_the_settings_in_%(conf)s_to_save_across_restarts": "click to keep the settings in %s to save across restarts",
|
||||
"ui.rule_%(rule)s_will_now_default_to_%(value)s": "Rule %s will now default to %s",
|
||||
"ui.rule_%(rule)s_not_set_restart": "Rule %s will now not be set on restart",
|
||||
"ui.current_value": "current value"
|
||||
"carpet.settings.command.browse_categories": "Browse categories",
|
||||
"carpet.settings.command.version": "version",
|
||||
"carpet.settings.command.list_all_category": "list all %s settings",
|
||||
"carpet.settings.command.current_settings_header": "Current %s settings",
|
||||
"carpet.settings.command.switch_to": "Switch to %s",
|
||||
"carpet.settings.command.unknown_rule": "Unknown rule",
|
||||
"carpet.settings.command.current_from_file_header": "Current %s Startup Settings from %s",
|
||||
"carpet.settings.command.mod_settings_matching": "%s settings matching \"%s\" ",
|
||||
"carpet.settings.command.all_mod_settings": "All %s settings",
|
||||
"carpet.settings.command.tags": "Tags",
|
||||
"carpet.settings.command.change_permanently": "Change permanently",
|
||||
"carpet.settings.command.change_permanently_tooltip": "click to keep the settings in %s to save across restarts",
|
||||
"carpet.settings.command.default_set": "Rule %s will now default to %s",
|
||||
"carpet.settings.command.default_removed": "Rule %s will no longer be set on restart",
|
||||
"carpet.settings.command.current_value": "current value"
|
||||
|
||||
}
|
||||
@@ -1,275 +1,273 @@
|
||||
{
|
||||
"rule.antiCheatDisabled.name": "禁用反作弊移动监测",
|
||||
"rule.antiCheatDisabled.desc": "防止玩家因为“本服务器未启用飞行”,“移动过快”等原因被踢出",
|
||||
"carpet.rule.antiCheatDisabled.name": "禁用反作弊移动监测",
|
||||
"carpet.rule.antiCheatDisabled.desc": "防止玩家因为“本服务器未启用飞行”,“移动过快”等原因被踢出",
|
||||
|
||||
"rule.carpets.name": "多功能地毯",
|
||||
"rule.carpets.desc": "通过放置地毯方块快捷执行部分功能",
|
||||
"carpet.rule.carpets.name": "多功能地毯",
|
||||
"carpet.rule.carpets.desc": "通过放置地毯方块快捷执行部分功能",
|
||||
|
||||
"rule.combineXPOrbs.name": "经验球合并",
|
||||
"rule.combineXPOrbs.desc": "会将多个小经验球合并为更大的经验球",
|
||||
"carpet.rule.combineXPOrbs.name": "经验球合并",
|
||||
"carpet.rule.combineXPOrbs.desc": "会将多个小经验球合并为更大的经验球",
|
||||
|
||||
"rule.commandDistance.name": "距离测量",
|
||||
"rule.commandDistance.desc": "启用/distance命令以测量游戏中两点间距离",
|
||||
"rule.commandDistance.extra.0": "可以通过放置棕色地毯的方式使用本功能,前提是“多功能地毯”已启用",
|
||||
"carpet.rule.commandDistance.name": "距离测量",
|
||||
"carpet.rule.commandDistance.desc": "启用/distance命令以测量游戏中两点间距离",
|
||||
"carpet.rule.commandDistance.extra.0": "可以通过放置棕色地毯的方式使用本功能,前提是“多功能地毯”已启用",
|
||||
|
||||
"rule.commandDraw.name": "画图",
|
||||
"rule.commandDraw.desc": "启用/draw命令以允许绘制一些简单图形",
|
||||
"carpet.rule.commandDraw.name": "画图",
|
||||
"carpet.rule.commandDraw.desc": "启用/draw命令以允许绘制一些简单图形",
|
||||
|
||||
"rule.commandInfo.name": "获取方块数据",
|
||||
"rule.commandInfo.desc": "启用/info命令以获取方块数据",
|
||||
"rule.commandInfo.extra.0": "可以通过放置灰色地毯的方式使用本功能,前提是“多功能地毯”已启用",
|
||||
"carpet.rule.commandInfo.name": "获取方块数据",
|
||||
"carpet.rule.commandInfo.desc": "启用/info命令以获取方块数据",
|
||||
"carpet.rule.commandInfo.extra.0": "可以通过放置灰色地毯的方式使用本功能,前提是“多功能地毯”已启用",
|
||||
|
||||
"rule.commandLog.name": "游戏数据监视器",
|
||||
"rule.commandLog.desc": "启用/log命令以在聊天栏或Tab栏监视游戏部分数据",
|
||||
"carpet.rule.commandLog.name": "游戏数据监视器",
|
||||
"carpet.rule.commandLog.desc": "启用/log命令以在聊天栏或Tab栏监视游戏部分数据",
|
||||
|
||||
"rule.commandPerimeterInfo.name": "刷怪区域搜寻器",
|
||||
"rule.commandPerimeterInfo.desc": "启用/perimeterinfo命令以搜寻周围的可刷怪区域",
|
||||
"carpet.rule.commandPerimeterInfo.name": "刷怪区域搜寻器",
|
||||
"carpet.rule.commandPerimeterInfo.desc": "启用/perimeterinfo命令以搜寻周围的可刷怪区域",
|
||||
|
||||
"rule.commandPlayer.name": "玩家控制",
|
||||
"rule.commandPlayer.desc": "启用/player命令以控制/召唤玩家",
|
||||
"carpet.rule.commandPlayer.name": "玩家控制",
|
||||
"carpet.rule.commandPlayer.desc": "启用/player命令以控制/召唤玩家",
|
||||
|
||||
"rule.commandScript.name": "Carpet脚本控制器",
|
||||
"rule.commandScript.desc": "启用/script相关命令",
|
||||
"rule.commandScript.extra.0": "一个Scarpet语言的游戏内API",
|
||||
"carpet.rule.commandScript.name": "Carpet脚本控制器",
|
||||
"carpet.rule.commandScript.desc": "启用/script相关命令",
|
||||
"carpet.rule.commandScript.extra.0": "一个Scarpet语言的游戏内API",
|
||||
|
||||
"rule.commandSpawn.name": "刷怪追踪器",
|
||||
"rule.commandSpawn.desc": "启用/spawn命令以追踪怪物生成",
|
||||
"carpet.rule.commandSpawn.name": "刷怪追踪器",
|
||||
"carpet.rule.commandSpawn.desc": "启用/spawn命令以追踪怪物生成",
|
||||
|
||||
"rule.commandTick.name": "游戏时钟控制器",
|
||||
"rule.commandTick.desc": "启用/tick命令以控制游戏时钟",
|
||||
"carpet.rule.commandTick.name": "游戏时钟控制器",
|
||||
"carpet.rule.commandTick.desc": "启用/tick命令以控制游戏时钟",
|
||||
|
||||
"rule.commandTrackAI.name": "AI追踪器",
|
||||
"rule.commandTrackAI.desc": "启用/track命令以追踪生物的AI",
|
||||
"carpet.rule.commandTrackAI.name": "AI追踪器",
|
||||
"carpet.rule.commandTrackAI.desc": "启用/track命令以追踪生物的AI",
|
||||
|
||||
"rule.ctrlQCraftingFix.name": "Ctrl+Q合成修复",
|
||||
"rule.ctrlQCraftingFix.desc": "在合成时允许使用Ctrl+Q快捷键扔出结果格内所有物品",
|
||||
"carpet.rule.ctrlQCraftingFix.name": "Ctrl+Q合成修复",
|
||||
"carpet.rule.ctrlQCraftingFix.desc": "在合成时允许使用Ctrl+Q快捷键扔出结果格内所有物品",
|
||||
|
||||
"rule.customMOTD.name": "MOTD更改",
|
||||
"rule.customMOTD.desc": "设置一个不同的MOTD信息",
|
||||
"rule.customMOTD.extra.0": "使用“_”时将发送默认MOTD(从server.properties中读取)",
|
||||
"carpet.rule.customMOTD.name": "MOTD更改",
|
||||
"carpet.rule.customMOTD.desc": "设置一个不同的MOTD信息",
|
||||
"carpet.rule.customMOTD.extra.0": "使用“_”时将发送默认MOTD(从server.properties中读取)",
|
||||
|
||||
"rule.defaultLoggers.name": "默认游戏监视器",
|
||||
"rule.defaultLoggers.desc": "为所有新进入玩家设置部分监视器默认开启",
|
||||
"rule.defaultLoggers.extra.0": "比如设置“tps,mobcaps”作为默认监视器",
|
||||
"carpet.rule.defaultLoggers.name": "默认游戏监视器",
|
||||
"carpet.rule.defaultLoggers.desc": "为所有新进入玩家设置部分监视器默认开启",
|
||||
"carpet.rule.defaultLoggers.extra.0": "比如设置“tps,mobcaps”作为默认监视器",
|
||||
|
||||
"rule.desertShrubs.name": "树苗在沙漠干枯",
|
||||
"rule.desertShrubs.desc": "树苗将在附近无水源的沙漠群系枯萎",
|
||||
"carpet.rule.desertShrubs.name": "树苗在沙漠干枯",
|
||||
"carpet.rule.desertShrubs.desc": "树苗将在附近无水源的沙漠群系枯萎",
|
||||
|
||||
"rule.disableSpawnChunks.name": "允许出生点区块卸载",
|
||||
"rule.disableSpawnChunks.desc": "允许出生点区块卸载",
|
||||
"carpet.rule.disableSpawnChunks.name": "允许出生点区块卸载",
|
||||
"carpet.rule.disableSpawnChunks.desc": "允许出生点区块卸载",
|
||||
|
||||
"rule.explosionNoBlockDamage.name": "防爆",
|
||||
"rule.explosionNoBlockDamage.desc": "任何形式的爆炸都不会摧毁方块",
|
||||
"carpet.rule.explosionNoBlockDamage.name": "防爆",
|
||||
"carpet.rule.explosionNoBlockDamage.desc": "任何形式的爆炸都不会摧毁方块",
|
||||
|
||||
"rule.extremeBehaviours.name": "随机极端情况",
|
||||
"rule.extremeBehaviours.desc": "大幅度提升极端情况出现概率,仅用于测试",
|
||||
"rule.extremeBehaviours.extra.0": "发射器和投掷器将使用最大随机刻",
|
||||
"carpet.rule.extremeBehaviours.name": "随机极端情况",
|
||||
"carpet.rule.extremeBehaviours.desc": "大幅度提升极端情况出现概率,仅用于测试",
|
||||
"carpet.rule.extremeBehaviours.extra.0": "发射器和投掷器将使用最大随机刻",
|
||||
|
||||
"rule.fastRedstoneDust.name": "红石粉卡顿优化",
|
||||
"rule.fastRedstoneDust.desc": "红石粉卡顿优化 作者:Theosib",
|
||||
"carpet.rule.fastRedstoneDust.name": "红石粉卡顿优化",
|
||||
"carpet.rule.fastRedstoneDust.desc": "红石粉卡顿优化 作者:Theosib",
|
||||
|
||||
"rule.fillLimit.name": "fill/clone上限更改",
|
||||
"rule.fillLimit.desc": "将fill/clone上限更改为自己需要的值",
|
||||
"rule.fillLimit.extra.0": "你必须在1-20M之间取一个值",
|
||||
"carpet.rule.fillLimit.name": "fill/clone上限更改",
|
||||
"carpet.rule.fillLimit.desc": "将fill/clone上限更改为自己需要的值",
|
||||
"carpet.rule.fillLimit.extra.0": "你必须在1-20M之间取一个值",
|
||||
|
||||
"rule.fillUpdates.name": "fill更新开关",
|
||||
"rule.fillUpdates.desc": "fill/clone/setblock命令以及结构方块执行时是否产生更新",
|
||||
"rule.fillUpdates.extra.0": "设置为true有更新,设置为false时为无更新",
|
||||
"carpet.rule.fillUpdates.name": "fill更新开关",
|
||||
"carpet.rule.fillUpdates.desc": "fill/clone/setblock命令以及结构方块执行时是否产生更新",
|
||||
"carpet.rule.fillUpdates.extra.0": "设置为true有更新,设置为false时为无更新",
|
||||
|
||||
"rule.flatWorldStructureSpawning.name": "结构在超平坦世界生成",
|
||||
"rule.flatWorldStructureSpawning.desc": "允许结构在超平坦世界生成",
|
||||
"carpet.rule.flatWorldStructureSpawning.name": "结构在超平坦世界生成",
|
||||
"carpet.rule.flatWorldStructureSpawning.desc": "允许结构在超平坦世界生成",
|
||||
|
||||
"rule.flippinCactus.name": "仙人掌扳手",
|
||||
"rule.flippinCactus.desc": "允许使用仙人掌调整方块朝向,并且不会产生更新",
|
||||
"carpet.rule.flippinCactus.name": "仙人掌扳手",
|
||||
"carpet.rule.flippinCactus.desc": "允许使用仙人掌调整方块朝向,并且不会产生更新",
|
||||
|
||||
"rule.hardcodeTNTangle.name": "硬编码TNT角度",
|
||||
"rule.hardcodeTNTangle.desc": "把TNT水平随机角度设为固定值,可用于测试机器",
|
||||
"rule.hardcodeTNTangle.extra.0": "默认为-1,必须在0与360之间,或者-1",
|
||||
"carpet.rule.hardcodeTNTangle.name": "硬编码TNT角度",
|
||||
"carpet.rule.hardcodeTNTangle.desc": "把TNT水平随机角度设为固定值,可用于测试机器",
|
||||
"carpet.rule.hardcodeTNTangle.extra.0": "默认为-1,必须在0与360之间,或者-1",
|
||||
|
||||
"rule.hopperCounters.name": "漏斗计数器",
|
||||
"rule.hopperCounters.desc": "漏斗指向的羊毛会计算经过的物品",
|
||||
"rule.hopperCounters.extra.0": "在羊毛上放置红色或绿色地毯,使用/counter命令可启用",
|
||||
"rule.hopperCounters.extra.1": "使用 /counter <颜色> reset重置计数器,使用/counter <颜色>查询",
|
||||
"rule.hopperCounters.extra.2": "在生存模式,在同色羊毛上放绿地毯可以查询,放红地毯重置",
|
||||
"rule.hopperCounters.extra.3": "数据是全局性在玩家间共享的,并且有16颜色可用",
|
||||
"rule.hopperCounters.extra.4": "物品会被销毁,每个漏斗每刻销毁一组物品",
|
||||
"carpet.rule.hopperCounters.name": "漏斗计数器",
|
||||
"carpet.rule.hopperCounters.desc": "漏斗指向的羊毛会计算经过的物品",
|
||||
"carpet.rule.hopperCounters.extra.0": "在羊毛上放置红色或绿色地毯,使用/counter命令可启用",
|
||||
"carpet.rule.hopperCounters.extra.1": "使用 /counter <颜色> reset重置计数器,使用/counter <颜色>查询",
|
||||
"carpet.rule.hopperCounters.extra.2": "在生存模式,在同色羊毛上放绿地毯可以查询,放红地毯重置",
|
||||
"carpet.rule.hopperCounters.extra.3": "数据是全局性在玩家间共享的,并且有16颜色可用",
|
||||
"carpet.rule.hopperCounters.extra.4": "物品会被销毁,每个漏斗每刻销毁一组物品",
|
||||
|
||||
"rule.huskSpawningInTemples.name": "尸壳在神殿里生成",
|
||||
"rule.huskSpawningInTemples.desc": "只有尸壳会在沙漠神殿生成",
|
||||
"carpet.rule.huskSpawningInTemples.name": "尸壳在神殿里生成",
|
||||
"carpet.rule.huskSpawningInTemples.desc": "只有尸壳会在沙漠神殿生成",
|
||||
|
||||
"rule.lagFreeSpawning.name": "无卡顿生成",
|
||||
"rule.lagFreeSpawning.desc": "生物生成需要更少的CPU和内存",
|
||||
"carpet.rule.lagFreeSpawning.name": "无卡顿生成",
|
||||
"carpet.rule.lagFreeSpawning.desc": "生物生成需要更少的CPU和内存",
|
||||
|
||||
"rule.leadFix.name": "栓绳修复",
|
||||
"rule.leadFix.desc": "修复栓绳在未加载区块中,变得不可见的问题",
|
||||
"carpet.rule.leadFix.name": "栓绳修复",
|
||||
"carpet.rule.leadFix.desc": "修复栓绳在未加载区块中,变得不可见的问题",
|
||||
|
||||
"rule.maxEntityCollisions.name": "最大实体挤压数",
|
||||
"rule.maxEntityCollisions.desc": "设定最大实体挤压限制,0表示无限大",
|
||||
"carpet.rule.maxEntityCollisions.name": "最大实体挤压数",
|
||||
"carpet.rule.maxEntityCollisions.desc": "设定最大实体挤压限制,0表示无限大",
|
||||
|
||||
"rule.mergeTNT.name": "合并TNT",
|
||||
"rule.mergeTNT.desc": "合并静止的点燃的TNT实体",
|
||||
"carpet.rule.mergeTNT.name": "合并TNT",
|
||||
"carpet.rule.mergeTNT.desc": "合并静止的点燃的TNT实体",
|
||||
|
||||
"rule.missingTools.name": "工具缺失修复",
|
||||
"rule.missingTools.desc": "活塞,玻璃和海绵可以用合适的工具更快地破坏",
|
||||
"carpet.rule.missingTools.name": "工具缺失修复",
|
||||
"carpet.rule.missingTools.desc": "活塞,玻璃和海绵可以用合适的工具更快地破坏",
|
||||
|
||||
"rule.movableBlockEntities.name": "容器移动",
|
||||
"rule.movableBlockEntities.desc": "活塞可推动方块实体,比如箱子,漏斗",
|
||||
"carpet.rule.movableBlockEntities.name": "容器移动",
|
||||
"carpet.rule.movableBlockEntities.desc": "活塞可推动方块实体,比如箱子,漏斗",
|
||||
|
||||
"rule.onePlayerSleeping.name": "单个玩家睡觉",
|
||||
"rule.onePlayerSleeping.desc": "服务器里一个玩家睡觉即可跳过夜晚",
|
||||
"carpet.rule.onePlayerSleeping.name": "单个玩家睡觉",
|
||||
"carpet.rule.onePlayerSleeping.desc": "服务器里一个玩家睡觉即可跳过夜晚",
|
||||
|
||||
"rule.optimizedTNT.name": "TNT优化",
|
||||
"rule.optimizedTNT.desc": "TNT在相同的地点或在流体里,爆炸造成更小的延迟",
|
||||
"carpet.rule.optimizedTNT.name": "TNT优化",
|
||||
"carpet.rule.optimizedTNT.desc": "TNT在相同的地点或在流体里,爆炸造成更小的延迟",
|
||||
|
||||
"rule.persistentParrots.name": "鹦鹉停留",
|
||||
"rule.persistentParrots.desc": "鹦鹉不会离开你的肩膀,除非你受到伤害",
|
||||
"carpet.rule.persistentParrots.name": "鹦鹉停留",
|
||||
"carpet.rule.persistentParrots.desc": "鹦鹉不会离开你的肩膀,除非你受到伤害",
|
||||
|
||||
"rule.placementRotationFix.name": "放置旋转修复",
|
||||
"rule.placementRotationFix.desc": "修复了玩家放置方块时快速转身造成的问题",
|
||||
"carpet.rule.placementRotationFix.name": "放置旋转修复",
|
||||
"carpet.rule.placementRotationFix.desc": "修复了玩家放置方块时快速转身造成的问题",
|
||||
|
||||
"rule.pushLimit.name": "自定义推动上限",
|
||||
"rule.pushLimit.desc": "自定义活塞推动上限",
|
||||
"rule.pushLimit.extra.0": "你必须在1-1024中选取一个值",
|
||||
"carpet.rule.pushLimit.name": "自定义推动上限",
|
||||
"carpet.rule.pushLimit.desc": "自定义活塞推动上限",
|
||||
"carpet.rule.pushLimit.extra.0": "你必须在1-1024中选取一个值",
|
||||
|
||||
"rule.quasiConnectivity.name": "半连接激活开关",
|
||||
"rule.quasiConnectivity.desc": "活塞,发射器和投掷器在他们上方方块激活时是否响应",
|
||||
"rule.quasiConnectivity.extra.0": "设置为true为开启,设置为false为禁用",
|
||||
"carpet.rule.quasiConnectivity.name": "半连接激活开关",
|
||||
"carpet.rule.quasiConnectivity.desc": "活塞,发射器和投掷器在他们上方方块激活时是否响应",
|
||||
"carpet.rule.quasiConnectivity.extra.0": "设置为true为开启,设置为false为禁用",
|
||||
|
||||
"rule.railPowerLimit.name": "自定义动力铁轨激活距离",
|
||||
"rule.railPowerLimit.desc": "动力铁轨激活上限可自定义",
|
||||
"rule.railPowerLimit.extra.0": "你必须在1-1024中选取一个值",
|
||||
"carpet.rule.railPowerLimit.name": "自定义动力铁轨激活距离",
|
||||
"carpet.rule.railPowerLimit.desc": "动力铁轨激活上限可自定义",
|
||||
"carpet.rule.railPowerLimit.extra.0": "你必须在1-1024中选取一个值",
|
||||
|
||||
"rule.renewableCoral.name": "珊瑚可再生",
|
||||
"rule.renewableCoral.desc": "使用骨粉可催熟珊瑚以生成珊瑚结构",
|
||||
"carpet.rule.renewableCoral.name": "珊瑚可再生",
|
||||
"carpet.rule.renewableCoral.desc": "使用骨粉可催熟珊瑚以生成珊瑚结构",
|
||||
|
||||
"rule.renewableSponges.name": "海绵可再生",
|
||||
"rule.renewableSponges.desc": "守卫者将在被雷击后会变为远古守卫者",
|
||||
"carpet.rule.renewableSponges.name": "海绵可再生",
|
||||
"carpet.rule.renewableSponges.desc": "守卫者将在被雷击后会变为远古守卫者",
|
||||
|
||||
"rule.rotatorBlock.name": "仙人掌扳手发射器版",
|
||||
"rule.rotatorBlock.desc": "发射器中的仙人掌可以旋转方块(尽可能以逆时针)",
|
||||
"carpet.rule.rotatorBlock.name": "仙人掌扳手发射器版",
|
||||
"carpet.rule.rotatorBlock.desc": "发射器中的仙人掌可以旋转方块(尽可能以逆时针)",
|
||||
|
||||
"rule.scriptsAutoload.name": "Scarpet脚本自动加载",
|
||||
"rule.scriptsAutoload.desc": "在服务器/世界加载的时候将会自动从世界文件中加载Scarpet脚本文件",
|
||||
"rule.scriptsAutoload.extra.0": "/script命令必须启用",
|
||||
"carpet.rule.scriptsAutoload.name": "Scarpet脚本自动加载",
|
||||
"carpet.rule.scriptsAutoload.desc": "在服务器/世界加载的时候将会自动从世界文件中加载Scarpet脚本文件",
|
||||
"carpet.rule.scriptsAutoload.extra.0": "/script命令必须启用",
|
||||
|
||||
"rule.shulkerSpawningInEndCities.name": "潜影贝重生",
|
||||
"rule.shulkerSpawningInEndCities.desc": "潜影贝将会在末地城中重生",
|
||||
"carpet.rule.shulkerSpawningInEndCities.name": "潜影贝重生",
|
||||
"carpet.rule.shulkerSpawningInEndCities.desc": "潜影贝将会在末地城中重生",
|
||||
|
||||
"rule.silverFishDropGravel.name": "蠹虫钻出方块掉落沙砾",
|
||||
"rule.silverFishDropGravel.desc": "蠹虫钻出方块时会掉落沙砾",
|
||||
"carpet.rule.silverFishDropGravel.name": "蠹虫钻出方块掉落沙砾",
|
||||
"carpet.rule.silverFishDropGravel.desc": "蠹虫钻出方块时会掉落沙砾",
|
||||
|
||||
"rule.smoothClientAnimations.name": "平滑客户端动画",
|
||||
"rule.smoothClientAnimations.desc": "低TPS设置时可以使客户端动画更加平滑",
|
||||
"rule.smoothClientAnimations.extra.0": "仅在单人游戏中生效,并且会降低客户端运行速度",
|
||||
"carpet.rule.smoothClientAnimations.name": "平滑客户端动画",
|
||||
"carpet.rule.smoothClientAnimations.desc": "低TPS设置时可以使客户端动画更加平滑",
|
||||
"carpet.rule.smoothClientAnimations.extra.0": "仅在单人游戏中生效,并且会降低客户端运行速度",
|
||||
|
||||
"rule.stackableShulkerBoxes.name": "潜影盒堆叠",
|
||||
"rule.stackableShulkerBoxes.desc": "空潜影盒仍在地上时将自动堆叠,直至堆叠为1组",
|
||||
"rule.stackableShulkerBoxes.extra.0": "在物品GUI中,请使用Shift+单击以移动整组潜影盒",
|
||||
"carpet.rule.stackableShulkerBoxes.name": "潜影盒堆叠",
|
||||
"carpet.rule.stackableShulkerBoxes.desc": "空潜影盒仍在地上时将自动堆叠,直至堆叠为1组",
|
||||
"carpet.rule.stackableShulkerBoxes.extra.0": "在物品GUI中,请使用Shift+单击以移动整组潜影盒",
|
||||
|
||||
"rule.summonNaturalLightning.name": "召唤闪电自然化",
|
||||
"rule.summonNaturalLightning.desc": "召唤的闪电将拥有所有自然生成闪电拥有的特性",
|
||||
"carpet.rule.summonNaturalLightning.name": "召唤闪电自然化",
|
||||
"carpet.rule.summonNaturalLightning.desc": "召唤的闪电将拥有所有自然生成闪电拥有的特性",
|
||||
|
||||
"rule.tntDoNotUpdate.name": "TNT放置时不产生更新",
|
||||
"rule.tntDoNotUpdate.desc": "TNT在放置时不会产生任何方块更新(不会激活)",
|
||||
"carpet.rule.tntDoNotUpdate.name": "TNT放置时不产生更新",
|
||||
"carpet.rule.tntDoNotUpdate.desc": "TNT在放置时不会产生任何方块更新(不会激活)",
|
||||
|
||||
"rule.tntPrimerMomentumRemoved.name": "移除TNT点燃时随机动量",
|
||||
"rule.tntPrimerMomentumRemoved.desc": "TNT点燃时的随机动量将被移除",
|
||||
"carpet.rule.tntPrimerMomentumRemoved.name": "移除TNT点燃时随机动量",
|
||||
"carpet.rule.tntPrimerMomentumRemoved.desc": "TNT点燃时的随机动量将被移除",
|
||||
|
||||
"rule.tntRandomRange.name": "TNT爆炸范围设置",
|
||||
"rule.tntRandomRange.desc": "设置TNT随机爆炸范围为一个固定的值,设为-1以禁用",
|
||||
"rule.tntRandomRange.extra.0": "必须启用TNT优化功能",
|
||||
"rule.tntRandomRange.extra.1": "不能为负值,除了-1",
|
||||
"carpet.rule.tntRandomRange.name": "TNT爆炸范围设置",
|
||||
"carpet.rule.tntRandomRange.desc": "设置TNT随机爆炸范围为一个固定的值,设为-1以禁用",
|
||||
"carpet.rule.tntRandomRange.extra.0": "必须启用TNT优化功能",
|
||||
"carpet.rule.tntRandomRange.extra.1": "不能为负值,除了-1",
|
||||
|
||||
"rule.unloadedEntityFix.name": "未加载实体修复",
|
||||
"rule.unloadedEntityFix.desc": "进入或者被推入未加载区块的实体不会消失",
|
||||
"carpet.rule.unloadedEntityFix.name": "未加载实体修复",
|
||||
"carpet.rule.unloadedEntityFix.desc": "进入或者被推入未加载区块的实体不会消失",
|
||||
|
||||
"rule.viewDistance.name": "自定义视距",
|
||||
"rule.viewDistance.desc": "改变服务器视距,设为0以使用默认值",
|
||||
"rule.viewDistance.extra.0": "你必须在0(使用服务器默认设置)-32取一个整数",
|
||||
"carpet.rule.viewDistance.name": "自定义视距",
|
||||
"carpet.rule.viewDistance.desc": "改变服务器视距,设为0以使用默认值",
|
||||
"carpet.rule.viewDistance.extra.0": "你必须在0(使用服务器默认设置)-32取一个整数",
|
||||
|
||||
"rule.simulationDistance.name": "自定义模拟视距",
|
||||
"rule.simulationDistance.desc": "改变服务器模拟视距,设为0以使用默认值",
|
||||
"rule.simulationDistance.extra.0": "你必须在0(使用服务器默认设置)-32取一个整数",
|
||||
"carpet.rule.simulationDistance.name": "自定义模拟视距",
|
||||
"carpet.rule.simulationDistance.desc": "改变服务器模拟视距,设为0以使用默认值",
|
||||
"carpet.rule.simulationDistance.extra.0": "你必须在0(使用服务器默认设置)-32取一个整数",
|
||||
|
||||
"rule.xpNoCooldown.name": "经验吸收无冷却",
|
||||
"rule.xpNoCooldown.desc": "玩家吸收经验球时不会有冷却时间",
|
||||
"carpet.rule.xpNoCooldown.name": "经验吸收无冷却",
|
||||
"carpet.rule.xpNoCooldown.desc": "玩家吸收经验球时不会有冷却时间",
|
||||
|
||||
"rule.portalSuffocationFix.name": "传送门窒息修复",
|
||||
"rule.portalSuffocationFix.desc": "使下界传送门正确地传送实体",
|
||||
"rule.portalSuffocationFix.extra.0": "传送的实体将不再卡在黑曜石里面",
|
||||
"carpet.rule.portalSuffocationFix.name": "传送门窒息修复",
|
||||
"carpet.rule.portalSuffocationFix.desc": "使下界传送门正确地传送实体",
|
||||
"carpet.rule.portalSuffocationFix.extra.0": "传送的实体将不再卡在黑曜石里面",
|
||||
|
||||
"rule.portalCreativeDelay.name": "创造玩家不被地狱门传送",
|
||||
"rule.portalCreativeDelay.desc": "创造模式的玩家将不会立即被地狱门传送",
|
||||
"carpet.rule.portalCreativeDelay.name": "创造玩家不被地狱门传送",
|
||||
"carpet.rule.portalCreativeDelay.desc": "创造模式的玩家将不会立即被地狱门传送",
|
||||
|
||||
"rule.commandProfile.name": "游戏性能监视器",
|
||||
"rule.commandProfile.desc": "启用/profile命令以监视游戏性能",
|
||||
"rule.commandProfile.extra.0": "/tick命令功能的部分增强版",
|
||||
"carpet.rule.commandProfile.name": "游戏性能监视器",
|
||||
"carpet.rule.commandProfile.desc": "启用/profile命令以监视游戏性能",
|
||||
"carpet.rule.commandProfile.extra.0": "/tick命令功能的部分增强版",
|
||||
|
||||
"rule.commandScriptACE.name": "地毯脚本使用权限",
|
||||
"rule.commandScriptACE.desc": "限制无权限的玩家执行地毯脚本",
|
||||
"rule.commandScriptACE.extra.0": "无此权限的玩家无法执行/script run指令或者加载脚本",
|
||||
"carpet.rule.commandScriptACE.name": "地毯脚本使用权限",
|
||||
"carpet.rule.commandScriptACE.desc": "限制无权限的玩家执行地毯脚本",
|
||||
"carpet.rule.commandScriptACE.extra.0": "无此权限的玩家无法执行/script run指令或者加载脚本",
|
||||
|
||||
"rule.creativeFlyDrag.name": "创造飞行阻力系数",
|
||||
"rule.creativeFlyDrag.desc": "允许使用本功能更改创造模式下飞行时的空气阻力系数",
|
||||
"rule.creativeFlyDrag.extra.0": "阻力增加会降低您的飞行速度,\n所以需要相应地调整速度,\n使用1.0阻力系数的时候,使用11的速度系数似乎与原版速度相匹配。\n本功能仅在客户端也安装Carpet Mod(并且版本>=服务器安装版本)的情况下生效",
|
||||
"carpet.rule.creativeFlyDrag.name": "创造飞行阻力系数",
|
||||
"carpet.rule.creativeFlyDrag.desc": "允许使用本功能更改创造模式下飞行时的空气阻力系数",
|
||||
"carpet.rule.creativeFlyDrag.extra.0": "阻力增加会降低您的飞行速度,\n所以需要相应地调整速度,\n使用1.0阻力系数的时候,使用11的速度系数似乎与原版速度相匹配。\n本功能仅在客户端也安装Carpet Mod(并且版本>=服务器安装版本)的情况下生效",
|
||||
|
||||
"rule.creativeFlySpeed.name": "创造飞行速度系数",
|
||||
"rule.creativeFlySpeed.desc": "允许使用本功能更改创造模式下飞行速度系数",
|
||||
"rule.creativeFlySpeed.extra.0": "本功能仅在客户端也安装Carpet Mod(并且版本>=服务器安装版本)的情况下生效",
|
||||
"carpet.rule.creativeFlySpeed.name": "创造飞行速度系数",
|
||||
"carpet.rule.creativeFlySpeed.desc": "允许使用本功能更改创造模式下飞行速度系数",
|
||||
"carpet.rule.creativeFlySpeed.extra.0": "本功能仅在客户端也安装Carpet Mod(并且版本>=服务器安装版本)的情况下生效",
|
||||
|
||||
"rule.creativeNoClip.name": "创造玩家无碰撞检测",
|
||||
"rule.creativeNoClip.desc": "创造玩家可以穿过方块",
|
||||
"rule.creativeNoClip.extra.0": "这个选项需要服务端和客户端同时支持才生效。\n仅在服务端设置是无效的,而仅在客户端设置的情况下\n你可以利用一些和活板门有关的技巧来穿墙",
|
||||
"carpet.rule.creativeNoClip.name": "创造玩家无碰撞检测",
|
||||
"carpet.rule.creativeNoClip.desc": "创造玩家可以穿过方块",
|
||||
"carpet.rule.creativeNoClip.extra.0": "这个选项需要服务端和客户端同时支持才生效。\n仅在服务端设置是无效的,而仅在客户端设置的情况下\n你可以利用一些和活板门有关的技巧来穿墙",
|
||||
|
||||
"rule.fogOff.name": "关闭雾气",
|
||||
"rule.fogOff.desc": "启用本功能以关闭地狱与末地的雾气",
|
||||
"rule.fogOff.extra.0": "提高了可见度,但看起来很奇怪",
|
||||
|
||||
"rule.forceloadLimit.name": "永久加载区块上限更改",
|
||||
"rule.forceloadLimit.desc": "允许使用本功能更改/forceload命令标记的永久加载区块上限",
|
||||
|
||||
"rule.interactionUpdates.name": "放置方块产生更新",
|
||||
"rule.interactionUpdates.desc": "放置方块是否产生更新的开关,设为false以避免产生更新",
|
||||
|
||||
"rule.language.name": "语言",
|
||||
"rule.language.desc": "为地毯Mod设置所需语言",
|
||||
|
||||
"rule.portalSurvivalDelay.name": "生存模式地狱门传送延迟",
|
||||
"rule.portalSurvivalDelay.desc": "设置生存模式下地狱门传送的延迟(单位:tick)",
|
||||
|
||||
"rule.spawnChunksSize.name": "出生点永久加载区块半径",
|
||||
"rule.spawnChunksSize.desc": "更改出生点永久加载区块半径",
|
||||
"rule.spawnChunksSize.extra.0": "定义新半径\n设为0以禁用出生点区块加载",
|
||||
|
||||
|
||||
"category.bugfix": "漏洞修复",
|
||||
"category.survival": "生存",
|
||||
"category.creative": "创造",
|
||||
"category.experimental": "试验性",
|
||||
"category.optimization": "优化",
|
||||
"category.feature": "特性",
|
||||
"category.command": "指令",
|
||||
"category.tnt": "TNT",
|
||||
"category.dispenser": "发射器",
|
||||
"category.scarpet": "Scarpet脚本语言",
|
||||
"category.client": "客户端",
|
||||
|
||||
"ui.browse_categories": "浏览分类",
|
||||
"ui.list_all_%(cat)s_settings": "列出所有 %s 选项",
|
||||
"ui.version": "版本",
|
||||
"ui.current_%(mod)s_settings": "目前启用的 %s 选项",
|
||||
"ui.switch_to": "切换至",
|
||||
"ui.settings_are_locked": "选项被锁定",
|
||||
"ui.is_set_to": "被设置为",
|
||||
"ui.unknown_rule": "未知的规则选项",
|
||||
"ui.current_%(mod)s_startup_settings_from_%(conf)s": "目前启用的%s选项,从%s中读取得",
|
||||
"ui.%(mod)s_settings_matching_'%(query)s'": "%s中符合“%s”的选项:",
|
||||
"ui.all_%(mod)s_settings": "所有的%s选项",
|
||||
"ui.tags": "分类",
|
||||
"ui.change_permanently": "永久更改",
|
||||
"ui.click_to_keep_the_settings_in_%(conf)s_to_save_across_restarts": "点击此按钮以使此选项保存于%s,重启后依旧生效",
|
||||
"ui.rule_%(rule)s_will_now_default_to_%(value)s": "规则“%s”将会被默认设置为%s",
|
||||
"ui.rule_%(rule)s_not_set_restart": "规则“%s”将在重启后恢复默认值",
|
||||
"ui.current_value": "当前值"
|
||||
"carpet.rule.fogOff.name": "关闭雾气",
|
||||
"carpet.rule.fogOff.desc": "启用本功能以关闭地狱与末地的雾气",
|
||||
"carpet.rule.fogOff.extra.0": "提高了可见度,但看起来很奇怪",
|
||||
|
||||
"carpet.rule.forceloadLimit.name": "永久加载区块上限更改",
|
||||
"carpet.rule.forceloadLimit.desc": "允许使用本功能更改/forceload命令标记的永久加载区块上限",
|
||||
|
||||
"carpet.rule.interactionUpdates.name": "放置方块产生更新",
|
||||
"carpet.rule.interactionUpdates.desc": "放置方块是否产生更新的开关,设为false以避免产生更新",
|
||||
|
||||
"carpet.rule.language.name": "语言",
|
||||
"carpet.rule.language.desc": "为地毯Mod设置所需语言",
|
||||
|
||||
"carpet.rule.portalSurvivalDelay.name": "生存模式地狱门传送延迟",
|
||||
"carpet.rule.portalSurvivalDelay.desc": "设置生存模式下地狱门传送的延迟(单位:tick)",
|
||||
|
||||
"carpet.rule.spawnChunksSize.name": "出生点永久加载区块半径",
|
||||
"carpet.rule.spawnChunksSize.desc": "更改出生点永久加载区块半径",
|
||||
"carpet.rule.spawnChunksSize.extra.0": "定义新半径\n设为0以禁用出生点区块加载",
|
||||
|
||||
|
||||
"carpet.category.bugfix": "漏洞修复",
|
||||
"carpet.category.survival": "生存",
|
||||
"carpet.category.creative": "创造",
|
||||
"carpet.category.experimental": "试验性",
|
||||
"carpet.category.optimization": "优化",
|
||||
"carpet.category.feature": "特性",
|
||||
"carpet.category.command": "指令",
|
||||
"carpet.category.tnt": "TNT",
|
||||
"carpet.category.dispenser": "发射器",
|
||||
"carpet.category.scarpet": "Scarpet脚本语言",
|
||||
"carpet.category.client": "客户端",
|
||||
|
||||
"carpet.settings.command.browse_categories": "浏览分类",
|
||||
"carpet.settings.command.version": "版本",
|
||||
"carpet.settings.command.list_all_category": "列出所有 %s 选项",
|
||||
"carpet.settings.command.current_settings_header": "目前启用的 %s 选项",
|
||||
"carpet.settings.command.switch_to": "切换至%s",
|
||||
"carpet.settings.command.unknown_rule": "未知的规则选项",
|
||||
"carpet.settings.command.current_from_file_header": "目前启用的%s选项,从%s中读取得",
|
||||
"carpet.settings.command.mod_settings_matching": "%s中符合“%s”的选项:",
|
||||
"carpet.settings.command.all_mod_settings": "所有的%s选项",
|
||||
"carpet.settings.command.tags": "分类",
|
||||
"carpet.settings.command.change_permanently": "永久更改",
|
||||
"carpet.settings.command.change_permanently_tooltip": "点击此按钮以使此选项保存于%s,重启后依旧生效",
|
||||
"carpet.settings.command.default_set": "规则“%s”将会被默认设置为%s",
|
||||
"carpet.settings.command.default_removed": "规则“%s”将在重启后恢复默认值",
|
||||
"carpet.settings.command.current_value": "当前值"
|
||||
|
||||
}
|
||||
@@ -1,322 +1,321 @@
|
||||
{
|
||||
"rule.antiCheatDisabled.name": "關閉伺服器作弊檢測",
|
||||
"rule.antiCheatDisabled.desc": "防止玩家因為“本伺服器不允許飛行”,“移動速度過快”等原因被請出伺服器",
|
||||
"carpet.rule.antiCheatDisabled.name": "關閉伺服器作弊檢測",
|
||||
"carpet.rule.antiCheatDisabled.desc": "防止玩家因為“本伺服器不允許飛行”,“移動速度過快”等原因被請出伺服器",
|
||||
|
||||
"rule.carpets.name": "放置地毯顯示進階數據",
|
||||
"rule.carpets.desc": "通過把地毯放置在地上取得更進階信息",
|
||||
"carpet.rule.carpets.name": "放置地毯顯示進階數據",
|
||||
"carpet.rule.carpets.desc": "通過把地毯放置在地上取得更進階信息",
|
||||
|
||||
"rule.combineXPOrbs.name": "合併小經驗球",
|
||||
"rule.combineXPOrbs.desc": "會將大量的小經驗球合併成等效的大經驗球",
|
||||
"carpet.rule.combineXPOrbs.name": "合併小經驗球",
|
||||
"carpet.rule.combineXPOrbs.desc": "會將大量的小經驗球合併成等效的大經驗球",
|
||||
|
||||
"rule.commandCameramode.name": "旁觀者模式",
|
||||
"rule.commandCameramode.desc": "允許玩家使用/c切換成旁觀者模式,利用/s切換回生存模式",
|
||||
"rule.commandCameramode.extra.0": "/c及/s不需要op權限也可使用",
|
||||
"carpet.rule.commandCameramode.name": "旁觀者模式",
|
||||
"carpet.rule.commandCameramode.desc": "允許玩家使用/c切換成旁觀者模式,利用/s切換回生存模式",
|
||||
"carpet.rule.commandCameramode.extra.0": "/c及/s不需要op權限也可使用",
|
||||
|
||||
"rule.commandDistance.name": "距離測定",
|
||||
"rule.commandDistance.desc": "使用/distance指令測定兩點的距離",
|
||||
"rule.commandDistance.extra.0": "可以使用放置棕色地毯來使用此功能,前提是“放置地毯顯示進階數據”已啟用",
|
||||
"carpet.rule.commandDistance.name": "距離測定",
|
||||
"carpet.rule.commandDistance.desc": "使用/distance指令測定兩點的距離",
|
||||
"carpet.rule.commandDistance.extra.0": "可以使用放置棕色地毯來使用此功能,前提是“放置地毯顯示進階數據”已啟用",
|
||||
|
||||
"rule.commandDraw.name": "繪製圖形",
|
||||
"rule.commandDraw.desc": "使用/draw指令以允許繪製一些簡易圖形",
|
||||
"carpet.rule.commandDraw.name": "繪製圖形",
|
||||
"carpet.rule.commandDraw.desc": "使用/draw指令以允許繪製一些簡易圖形",
|
||||
|
||||
"rule.commandInfo.name": "獲取方塊進階資訊",
|
||||
"rule.commandInfo.desc": "允許使用/info指令以獲得方塊進階資訊",
|
||||
"rule.commandInfo.extra.0": "可以使用放置灰色地毯來使用此功能,前提是“放置地毯顯示進階數據”已啟用",
|
||||
"carpet.rule.commandInfo.name": "獲取方塊進階資訊",
|
||||
"carpet.rule.commandInfo.desc": "允許使用/info指令以獲得方塊進階資訊",
|
||||
"carpet.rule.commandInfo.extra.0": "可以使用放置灰色地毯來使用此功能,前提是“放置地毯顯示進階數據”已啟用",
|
||||
|
||||
"rule.commandLog.name": "遊戲數據追蹤器",
|
||||
"rule.commandLog.desc": "允許使用/log指令使在Tab欄位追蹤遊戲部分數據",
|
||||
"carpet.rule.commandLog.name": "遊戲數據追蹤器",
|
||||
"carpet.rule.commandLog.desc": "允許使用/log指令使在Tab欄位追蹤遊戲部分數據",
|
||||
|
||||
"rule.commandPerimeterInfo.name": "刷怪區域追蹤器",
|
||||
"rule.commandPerimeterInfo.desc": "允許使用/perimeterinfo指令以探查周圍可生怪區域",
|
||||
"carpet.rule.commandPerimeterInfo.name": "刷怪區域追蹤器",
|
||||
"carpet.rule.commandPerimeterInfo.desc": "允許使用/perimeterinfo指令以探查周圍可生怪區域",
|
||||
|
||||
"rule.commandPlayer.name": "機器人控制",
|
||||
"rule.commandPlayer.desc": "允許使用/player指令以控制/召喚已存在的玩家id作為機器人",
|
||||
"carpet.rule.commandPlayer.name": "機器人控制",
|
||||
"carpet.rule.commandPlayer.desc": "允許使用/player指令以控制/召喚已存在的玩家id作為機器人",
|
||||
|
||||
"rule.commandScript.name": "地毯腳本控制器",
|
||||
"rule.commandScript.desc": "允許使用/script相關指令",
|
||||
"rule.commandScript.extra.0": "一個Scarpet程式語言的遊戲內API",
|
||||
"carpet.rule.commandScript.name": "地毯腳本控制器",
|
||||
"carpet.rule.commandScript.desc": "允許使用/script相關指令",
|
||||
"carpet.rule.commandScript.extra.0": "一個Scarpet程式語言的遊戲內API",
|
||||
|
||||
"rule.commandSpawn.name": "刷怪追蹤器",
|
||||
"rule.commandSpawn.desc": "允許使用/spawn指令用於怪物生成追蹤",
|
||||
"carpet.rule.commandSpawn.name": "刷怪追蹤器",
|
||||
"carpet.rule.commandSpawn.desc": "允許使用/spawn指令用於怪物生成追蹤",
|
||||
|
||||
"rule.commandTick.name": "遊戲時鐘控制器",
|
||||
"rule.commandTick.desc": "允許使用/tick指令控制遊戲內的伺服器端運行速度",
|
||||
"carpet.rule.commandTick.name": "遊戲時鐘控制器",
|
||||
"carpet.rule.commandTick.desc": "允許使用/tick指令控制遊戲內的伺服器端運行速度",
|
||||
|
||||
"rule.commandTrackAI.name": "AI追蹤器",
|
||||
"rule.commandTrackAI.desc": "允許使用/track指令以追蹤生物的AI",
|
||||
"carpet.rule.commandTrackAI.name": "AI追蹤器",
|
||||
"carpet.rule.commandTrackAI.desc": "允許使用/track指令以追蹤生物的AI",
|
||||
|
||||
"rule.ctrlQCraftingFix.name": "ctrlQ合成修復",
|
||||
"rule.ctrlQCraftingFix.desc": "在合成時允許使用ctrl+Q快捷鍵扔出結果格內所有物品",
|
||||
"carpet.rule.ctrlQCraftingFix.name": "ctrlQ合成修復",
|
||||
"carpet.rule.ctrlQCraftingFix.desc": "在合成時允許使用ctrl+Q快捷鍵扔出結果格內所有物品",
|
||||
|
||||
"rule.customMOTD.name": "MOTD更改",
|
||||
"rule.customMOTD.desc": "設置一個不同的MOTD信息",
|
||||
"rule.customMOTD.extra.0": "設置成'_'時將採用默認的MOTD(從server.properties中讀取的)",
|
||||
"carpet.rule.customMOTD.name": "MOTD更改",
|
||||
"carpet.rule.customMOTD.desc": "設置一個不同的MOTD信息",
|
||||
"carpet.rule.customMOTD.extra.0": "設置成'_'時將採用默認的MOTD(從server.properties中讀取的)",
|
||||
|
||||
"rule.defaultLoggers.name": "默認開啟的遊戲數據追蹤器",
|
||||
"rule.defaultLoggers.desc": "為所有新加入的玩家設置部分遊戲數據追蹤器默認為開啟狀態",
|
||||
"rule.defaultLoggers.extra.0": "比如設置'tps,mobcaps'最為默認遊戲數據追蹤",
|
||||
"carpet.rule.defaultLoggers.name": "默認開啟的遊戲數據追蹤器",
|
||||
"carpet.rule.defaultLoggers.desc": "為所有新加入的玩家設置部分遊戲數據追蹤器默認為開啟狀態",
|
||||
"carpet.rule.defaultLoggers.extra.0": "比如設置'tps,mobcaps'最為默認遊戲數據追蹤",
|
||||
|
||||
"rule.desertShrubs.name": "樹苗在沙漠中乾枯",
|
||||
"rule.desertShrubs.desc": "樹苗將在附近無水源狀態的沙漠生態群系變成枯灌木",
|
||||
"carpet.rule.desertShrubs.name": "樹苗在沙漠中乾枯",
|
||||
"carpet.rule.desertShrubs.desc": "樹苗將在附近無水源狀態的沙漠生態群系變成枯灌木",
|
||||
|
||||
"rule.disableSpawnChunks.name": "允許出生點區塊被卸載",
|
||||
"rule.disableSpawnChunks.desc": "允許出生點區塊被卸載",
|
||||
"carpet.rule.disableSpawnChunks.name": "允許出生點區塊被卸載",
|
||||
"carpet.rule.disableSpawnChunks.desc": "允許出生點區塊被卸載",
|
||||
|
||||
"rule.explosionNoBlockDamage.name": "爆炸不破壞方塊",
|
||||
"rule.explosionNoBlockDamage.desc": "任何形式的爆炸都無法摧毀方塊",
|
||||
"carpet.rule.explosionNoBlockDamage.name": "爆炸不破壞方塊",
|
||||
"carpet.rule.explosionNoBlockDamage.desc": "任何形式的爆炸都無法摧毀方塊",
|
||||
|
||||
"rule.extremeBehaviours.name": "隨機參數極端化",
|
||||
"rule.extremeBehaviours.desc": "大幅度提升極端情況出現的概率,僅用於測試",
|
||||
"rule.extremeBehaviours.extra.0": "發射器和投擲器將使用最大隨機刻",
|
||||
"carpet.rule.extremeBehaviours.name": "隨機參數極端化",
|
||||
"carpet.rule.extremeBehaviours.desc": "大幅度提升極端情況出現的概率,僅用於測試",
|
||||
"carpet.rule.extremeBehaviours.extra.0": "發射器和投擲器將使用最大隨機刻",
|
||||
|
||||
"rule.fastRedstoneDust.name": "紅石粉卡頓優化",
|
||||
"rule.fastRedstoneDust.desc": "紅石粉卡頓優化 作者 Theosib",
|
||||
"carpet.rule.fastRedstoneDust.name": "紅石粉卡頓優化",
|
||||
"carpet.rule.fastRedstoneDust.desc": "紅石粉卡頓優化 作者 Theosib",
|
||||
|
||||
"rule.fillLimit.name": "fill/clone上限更改",
|
||||
"rule.fillLimit.desc": "將fill/clone上限更改為自己設定的值",
|
||||
"rule.fillLimit.extra.0": "你必須在1-2千萬之間取一個整數值",
|
||||
"carpet.rule.fillLimit.name": "fill/clone上限更改",
|
||||
"carpet.rule.fillLimit.desc": "將fill/clone上限更改為自己設定的值",
|
||||
"carpet.rule.fillLimit.extra.0": "你必須在1-2千萬之間取一個整數值",
|
||||
|
||||
"rule.fillUpdates.name": "fill更新設置",
|
||||
"rule.fillUpdates.desc": "fill/clone/setblock指令以及結構方塊執行時是否產生更新",
|
||||
"rule.fillUpdates.extra.0": "設置為true時有更新 設置為false時為無更新",
|
||||
"carpet.rule.fillUpdates.name": "fill更新設置",
|
||||
"carpet.rule.fillUpdates.desc": "fill/clone/setblock指令以及結構方塊執行時是否產生更新",
|
||||
"carpet.rule.fillUpdates.extra.0": "設置為true時有更新 設置為false時為無更新",
|
||||
|
||||
"rule.flatWorldStructureSpawning.name": "特定建築怪在超平坦世界生成",
|
||||
"rule.flatWorldStructureSpawning.desc": "允許特定建築的怪物在超平坦世界生成",
|
||||
"carpet.rule.flatWorldStructureSpawning.name": "特定建築怪在超平坦世界生成",
|
||||
"carpet.rule.flatWorldStructureSpawning.desc": "允許特定建築的怪物在超平坦世界生成",
|
||||
|
||||
"rule.flippinCactus.name": "仙人掌扳手",
|
||||
"rule.flippinCactus.desc": "允許使用仙人掌調整方塊朝向,並且不會造成更新",
|
||||
"carpet.rule.flippinCactus.name": "仙人掌扳手",
|
||||
"carpet.rule.flippinCactus.desc": "允許使用仙人掌調整方塊朝向,並且不會造成更新",
|
||||
|
||||
"rule.hardcodeTNTangle.name": "強制TNT角度",
|
||||
"rule.hardcodeTNTangle.desc": "把TNT水平隨機角度設定為固定值,可用於測試機器",
|
||||
"rule.hardcodeTNTangle.extra.0": "默認為-1,必須在0與360之間,或者-1",
|
||||
"carpet.rule.hardcodeTNTangle.name": "強制TNT角度",
|
||||
"carpet.rule.hardcodeTNTangle.desc": "把TNT水平隨機角度設定為固定值,可用於測試機器",
|
||||
"carpet.rule.hardcodeTNTangle.extra.0": "默認為-1,必須在0與360之間,或者-1",
|
||||
|
||||
"rule.hopperCounters.name": "漏斗計數器",
|
||||
"rule.hopperCounters.desc": "漏斗指向的羊毛方塊會計算吸收的物品並清除",
|
||||
"rule.hopperCounters.extra.0": "在羊毛方塊上放置紅色或綠色地毯,使用/counter指令可啟用",
|
||||
"rule.hopperCounters.extra.1": "使用 /counter <color?> reset重置計數器,使用/counter <color?>查詢資訊",
|
||||
"rule.hopperCounters.extra.2": "在生存模式,在同色羊毛上放置綠色地毯可以查詢,放紅色地毯重置",
|
||||
"rule.hopperCounters.extra.3": "數據是全伺服器玩家共享的,並且有16色可以使用",
|
||||
"rule.hopperCounters.extra.4": "物品會被銷毀,每個漏斗每tick銷毀一組物品",
|
||||
"carpet.rule.hopperCounters.name": "漏斗計數器",
|
||||
"carpet.rule.hopperCounters.desc": "漏斗指向的羊毛方塊會計算吸收的物品並清除",
|
||||
"carpet.rule.hopperCounters.extra.0": "在羊毛方塊上放置紅色或綠色地毯,使用/counter指令可啟用",
|
||||
"carpet.rule.hopperCounters.extra.1": "使用 /counter <color?> reset重置計數器,使用/counter <color?>查詢資訊",
|
||||
"carpet.rule.hopperCounters.extra.2": "在生存模式,在同色羊毛上放置綠色地毯可以查詢,放紅色地毯重置",
|
||||
"carpet.rule.hopperCounters.extra.3": "數據是全伺服器玩家共享的,並且有16色可以使用",
|
||||
"carpet.rule.hopperCounters.extra.4": "物品會被銷毀,每個漏斗每tick銷毀一組物品",
|
||||
|
||||
"rule.huskSpawningInTemples.name": "屍殼在沙漠神殿裡生成",
|
||||
"rule.huskSpawningInTemples.desc": "只有屍殼會在沙漠神殿生成",
|
||||
"carpet.rule.huskSpawningInTemples.name": "屍殼在沙漠神殿裡生成",
|
||||
"carpet.rule.huskSpawningInTemples.desc": "只有屍殼會在沙漠神殿生成",
|
||||
|
||||
"rule.lagFreeSpawning.name": "延遲自然生成",
|
||||
"rule.lagFreeSpawning.desc": "生物生成需要更少的CPU和RAM",
|
||||
"carpet.rule.lagFreeSpawning.name": "延遲自然生成",
|
||||
"carpet.rule.lagFreeSpawning.desc": "生物生成需要更少的CPU和RAM",
|
||||
|
||||
"rule.leadFix.name": "栓繩修復",
|
||||
"rule.leadFix.desc": "修復栓繩在未加載區塊中,變得不可見的問題",
|
||||
"carpet.rule.leadFix.name": "栓繩修復",
|
||||
"carpet.rule.leadFix.desc": "修復栓繩在未加載區塊中,變得不可見的問題",
|
||||
|
||||
"rule.maxEntityCollisions.name": "最大實體擠壓數量",
|
||||
"rule.maxEntityCollisions.desc": "設定最大實體擠壓限制,0表示無限大",
|
||||
"carpet.rule.maxEntityCollisions.name": "最大實體擠壓數量",
|
||||
"carpet.rule.maxEntityCollisions.desc": "設定最大實體擠壓限制,0表示無限大",
|
||||
|
||||
"rule.mergeTNT.name": "合併TNT",
|
||||
"rule.mergeTNT.desc": "合併靜止的點燃的tnt實體",
|
||||
"carpet.rule.mergeTNT.name": "合併TNT",
|
||||
"carpet.rule.mergeTNT.desc": "合併靜止的點燃的tnt實體",
|
||||
|
||||
"rule.missingTools.name": "工具缺失修復",
|
||||
"rule.missingTools.desc": "活塞,玻璃和海綿可以使用合適的工具更快地破壞",
|
||||
"carpet.rule.missingTools.name": "工具缺失修復",
|
||||
"carpet.rule.missingTools.desc": "活塞,玻璃和海綿可以使用合適的工具更快地破壞",
|
||||
|
||||
"rule.movableBlockEntities.name": "容器方塊移動",
|
||||
"rule.movableBlockEntities.desc": "活塞可以推動具有容器標籤的方塊,比如箱子,漏斗",
|
||||
"carpet.rule.movableBlockEntities.name": "容器方塊移動",
|
||||
"carpet.rule.movableBlockEntities.desc": "活塞可以推動具有容器標籤的方塊,比如箱子,漏斗",
|
||||
|
||||
"rule.onePlayerSleeping.name": "單個玩家睡覺跳過夜晚",
|
||||
"rule.onePlayerSleeping.desc": "只要伺服器中其中一人睡覺即可跳過全伺服器夜晚",
|
||||
"carpet.rule.onePlayerSleeping.name": "單個玩家睡覺跳過夜晚",
|
||||
"carpet.rule.onePlayerSleeping.desc": "只要伺服器中其中一人睡覺即可跳過全伺服器夜晚",
|
||||
|
||||
"rule.optimizedTNT.name": "TNT優化",
|
||||
"rule.optimizedTNT.desc": "tnt在相同位置或者在流體裡,爆炸將造成更低延遲",
|
||||
"carpet.rule.optimizedTNT.name": "TNT優化",
|
||||
"carpet.rule.optimizedTNT.desc": "tnt在相同位置或者在流體裡,爆炸將造成更低延遲",
|
||||
|
||||
"rule.persistentParrots.name": "鸚鵡停留",
|
||||
"rule.persistentParrots.desc": "鸚鵡不會離開你的肩膀,除非你受到傷害",
|
||||
"carpet.rule.persistentParrots.name": "鸚鵡停留",
|
||||
"carpet.rule.persistentParrots.desc": "鸚鵡不會離開你的肩膀,除非你受到傷害",
|
||||
|
||||
"rule.placementRotationFix.name": "放置旋轉問題修復",
|
||||
"rule.placementRotationFix.desc": "修復玩家放置方塊時快速轉身造成的問題",
|
||||
"carpet.rule.placementRotationFix.name": "放置旋轉問題修復",
|
||||
"carpet.rule.placementRotationFix.desc": "修復玩家放置方塊時快速轉身造成的問題",
|
||||
|
||||
"rule.pushLimit.name": "自訂義推動上限",
|
||||
"rule.pushLimit.desc": "自訂義活塞推動上限",
|
||||
"rule.pushLimit.extra.0": "你必須在1-1024中選取一個值,預設是12",
|
||||
"carpet.rule.pushLimit.name": "自訂義推動上限",
|
||||
"carpet.rule.pushLimit.desc": "自訂義活塞推動上限",
|
||||
"carpet.rule.pushLimit.extra.0": "你必須在1-1024中選取一個值,預設是12",
|
||||
|
||||
"rule.quasiConnectivity.name": "半連接激活開關",
|
||||
"rule.quasiConnectivity.desc": "活塞,發射器和投擲器在他們上方方塊激活時是否響應",
|
||||
"rule.quasiConnectivity.extra.0": "設置為true為開啟 設置為false為禁用",
|
||||
"carpet.rule.quasiConnectivity.name": "半連接激活開關",
|
||||
"carpet.rule.quasiConnectivity.desc": "活塞,發射器和投擲器在他們上方方塊激活時是否響應",
|
||||
"carpet.rule.quasiConnectivity.extra.0": "設置為true為開啟 設置為false為禁用",
|
||||
|
||||
"rule.railPowerLimit.name": "自訂義動力鐵軌激活距離",
|
||||
"rule.railPowerLimit.desc": "動力鐵軌激活距離能在此更改",
|
||||
"rule.railPowerLimit.extra.0": "你必須在1-1024中選取一個數值",
|
||||
"carpet.rule.railPowerLimit.name": "自訂義動力鐵軌激活距離",
|
||||
"carpet.rule.railPowerLimit.desc": "動力鐵軌激活距離能在此更改",
|
||||
"carpet.rule.railPowerLimit.extra.0": "你必須在1-1024中選取一個數值",
|
||||
|
||||
"rule.renewableCoral.name": "可再生珊瑚",
|
||||
"rule.renewableCoral.desc": "使用骨粉可催熟珊瑚以長成珊瑚方塊結構",
|
||||
"carpet.rule.renewableCoral.name": "可再生珊瑚",
|
||||
"carpet.rule.renewableCoral.desc": "使用骨粉可催熟珊瑚以長成珊瑚方塊結構",
|
||||
|
||||
"rule.renewableSponges.name": "可再生海綿",
|
||||
"rule.renewableSponges.desc": "深海守衛將在被雷擊後變成遠古深海守衛",
|
||||
"carpet.rule.renewableSponges.name": "可再生海綿",
|
||||
"carpet.rule.renewableSponges.desc": "深海守衛將在被雷擊後變成遠古深海守衛",
|
||||
|
||||
"rule.rotatorBlock.name": "仙人掌扳手發射器版",
|
||||
"rule.rotatorBlock.desc": "發射器中的仙人掌可以旋轉方塊(盡可能以逆時針)",
|
||||
"carpet.rule.rotatorBlock.name": "仙人掌扳手發射器版",
|
||||
"carpet.rule.rotatorBlock.desc": "發射器中的仙人掌可以旋轉方塊(盡可能以逆時針)",
|
||||
|
||||
"rule.scriptsAutoload.name": "Scarpet腳本自動加載",
|
||||
"rule.scriptsAutoload.desc": "在伺服器/世界加載的時候將會自動從世界文件中加載Scarpet腳本文件",
|
||||
"rule.scriptsAutoload.extra.0": "/script指令必須為啟用狀態",
|
||||
"carpet.rule.scriptsAutoload.name": "Scarpet腳本自動加載",
|
||||
"carpet.rule.scriptsAutoload.desc": "在伺服器/世界加載的時候將會自動從世界文件中加載Scarpet腳本文件",
|
||||
"carpet.rule.scriptsAutoload.extra.0": "/script指令必須為啟用狀態",
|
||||
|
||||
"rule.shulkerSpawningInEndCities.name": "可重生界伏蚌",
|
||||
"rule.shulkerSpawningInEndCities.desc": "界伏蚌將會在終界城市中重生",
|
||||
"carpet.rule.shulkerSpawningInEndCities.name": "可重生界伏蚌",
|
||||
"carpet.rule.shulkerSpawningInEndCities.desc": "界伏蚌將會在終界城市中重生",
|
||||
|
||||
"rule.silverFishDropGravel.name": "蠹魚鑽出方塊掉落礫石",
|
||||
"rule.silverFishDropGravel.desc": "蠹魚在鑽出方塊時會掉落礫石",
|
||||
"carpet.rule.silverFishDropGravel.name": "蠹魚鑽出方塊掉落礫石",
|
||||
"carpet.rule.silverFishDropGravel.desc": "蠹魚在鑽出方塊時會掉落礫石",
|
||||
|
||||
"rule.smoothClientAnimations.name": "更平暢玩家端動畫",
|
||||
"rule.smoothClientAnimations.desc": "降低TPS設置時可以使玩家端動畫更加平暢",
|
||||
"rule.smoothClientAnimations.extra.0": "僅在單人模式中生效,並且會降低玩家端運行速度",
|
||||
"carpet.rule.smoothClientAnimations.name": "更平暢玩家端動畫",
|
||||
"carpet.rule.smoothClientAnimations.desc": "降低TPS設置時可以使玩家端動畫更加平暢",
|
||||
"carpet.rule.smoothClientAnimations.extra.0": "僅在單人模式中生效,並且會降低玩家端運行速度",
|
||||
|
||||
"rule.stackableShulkerBoxes.name": "可堆疊界伏盒",
|
||||
"rule.stackableShulkerBoxes.desc": "空的界伏盒扔在地上時將會自動堆疊,直到堆疊為1組",
|
||||
"rule.stackableShulkerBoxes.extra.0": "在物品GUI中,請使用shift鍵+單擊以移動整組界伏盒",
|
||||
"carpet.rule.stackableShulkerBoxes.name": "可堆疊界伏盒",
|
||||
"carpet.rule.stackableShulkerBoxes.desc": "空的界伏盒扔在地上時將會自動堆疊,直到堆疊為1組",
|
||||
"carpet.rule.stackableShulkerBoxes.extra.0": "在物品GUI中,請使用shift鍵+單擊以移動整組界伏盒",
|
||||
|
||||
"rule.summonNaturalLightning.name": "召喚閃電自然化",
|
||||
"rule.summonNaturalLightning.desc": "召喚的閃電將具有自然生成時的特性",
|
||||
"carpet.rule.summonNaturalLightning.name": "召喚閃電自然化",
|
||||
"carpet.rule.summonNaturalLightning.desc": "召喚的閃電將具有自然生成時的特性",
|
||||
|
||||
"rule.tntDoNotUpdate.name": "TNT放置時不會被更新",
|
||||
"rule.tntDoNotUpdate.desc": "TNT在放置時不會產生任何方塊更新(不會激活)",
|
||||
"carpet.rule.tntDoNotUpdate.name": "TNT放置時不會被更新",
|
||||
"carpet.rule.tntDoNotUpdate.desc": "TNT在放置時不會產生任何方塊更新(不會激活)",
|
||||
|
||||
"rule.tntPrimerMomentumRemoved.name": "移除TNT點燃時隨機動量",
|
||||
"rule.tntPrimerMomentumRemoved.desc": "TNT點燃時的隨機動量將被移除",
|
||||
"carpet.rule.tntPrimerMomentumRemoved.name": "移除TNT點燃時隨機動量",
|
||||
"carpet.rule.tntPrimerMomentumRemoved.desc": "TNT點燃時的隨機動量將被移除",
|
||||
|
||||
"rule.tntRandomRange.name": "TNT爆炸範圍設置",
|
||||
"rule.tntRandomRange.desc": "設置TNT隨機爆炸範圍為一個固定的值,設為-1以禁用",
|
||||
"rule.tntRandomRange.extra.0": "必須啟用optimizedTNT功能",
|
||||
"rule.tntRandomRange.extra.1": "不能設置為負數,除了-1",
|
||||
"carpet.rule.tntRandomRange.name": "TNT爆炸範圍設置",
|
||||
"carpet.rule.tntRandomRange.desc": "設置TNT隨機爆炸範圍為一個固定的值,設為-1以禁用",
|
||||
"carpet.rule.tntRandomRange.extra.0": "必須啟用optimizedTNT功能",
|
||||
"carpet.rule.tntRandomRange.extra.1": "不能設置為負數,除了-1",
|
||||
|
||||
"rule.unloadedEntityFix.name": "未加載實體修復",
|
||||
"rule.unloadedEntityFix.desc": "進入或者被推入未加載區塊的實體不會消失",
|
||||
"carpet.rule.unloadedEntityFix.name": "未加載實體修復",
|
||||
"carpet.rule.unloadedEntityFix.desc": "進入或者被推入未加載區塊的實體不會消失",
|
||||
|
||||
"rule.viewDistance.name": "自訂義視野",
|
||||
"rule.viewDistance.desc": "改變伺服器端視野距離,設置為0以使用默認值",
|
||||
"rule.viewDistance.extra.0": "你必須在0(使用伺服器默認設置)-32取一個整數",
|
||||
"carpet.rule.viewDistance.name": "自訂義視野",
|
||||
"carpet.rule.viewDistance.desc": "改變伺服器端視野距離,設置為0以使用默認值",
|
||||
"carpet.rule.viewDistance.extra.0": "你必須在0(使用伺服器默認設置)-32取一個整數",
|
||||
|
||||
"rule.xpNoCooldown.name": "經驗球吸收無冷卻",
|
||||
"rule.xpNoCooldown.desc": "玩家吸收經驗球不會有冷卻時間",
|
||||
"carpet.rule.xpNoCooldown.name": "經驗球吸收無冷卻",
|
||||
"carpet.rule.xpNoCooldown.desc": "玩家吸收經驗球不會有冷卻時間",
|
||||
|
||||
"rule.portalSuffocationFix.name": "傳送門窒息修復",
|
||||
"rule.portalSuffocationFix.desc": "使地獄傳送門正確地傳送實體",
|
||||
"rule.portalSuffocationFix.extra.0": "傳送的實體將不再卡在黑曜石裡",
|
||||
"carpet.rule.portalSuffocationFix.name": "傳送門窒息修復",
|
||||
"carpet.rule.portalSuffocationFix.desc": "使地獄傳送門正確地傳送實體",
|
||||
"carpet.rule.portalSuffocationFix.extra.0": "傳送的實體將不再卡在黑曜石裡",
|
||||
|
||||
"rule.portalCreativeDelay.name": "創造模式玩家地獄門傳送延遲",
|
||||
"rule.portalCreativeDelay.desc": "創造模式下使用地獄傳送門將同生存時產生延遲",
|
||||
"carpet.rule.portalCreativeDelay.name": "創造模式玩家地獄門傳送延遲",
|
||||
"carpet.rule.portalCreativeDelay.desc": "創造模式下使用地獄傳送門將同生存時產生延遲",
|
||||
|
||||
"rule.commandProfile.name": "遊戲性能監視器",
|
||||
"rule.commandProfile.desc": "啟用/profile命令以監視遊戲性能",
|
||||
"rule.commandProfile.extra.0": "/tick命令功能的部分增強版",
|
||||
"carpet.rule.commandProfile.name": "遊戲性能監視器",
|
||||
"carpet.rule.commandProfile.desc": "啟用/profile命令以監視遊戲性能",
|
||||
"carpet.rule.commandProfile.extra.0": "/tick命令功能的部分增強版",
|
||||
|
||||
"rule.commandScriptACE.name": "地毯腳本使用權限",
|
||||
"rule.commandScriptACE.desc": "限制無權限的玩家執行地毯腳本",
|
||||
"rule.commandScriptACE.extra.0": "無此權限的玩家無法執行/script run指令或者加載腳本",
|
||||
"carpet.rule.commandScriptACE.name": "地毯腳本使用權限",
|
||||
"carpet.rule.commandScriptACE.desc": "限制無權限的玩家執行地毯腳本",
|
||||
"carpet.rule.commandScriptACE.extra.0": "無此權限的玩家無法執行/script run指令或者加載腳本",
|
||||
|
||||
"rule.creativeFlyDrag.name": "創造飛行阻力系數",
|
||||
"rule.creativeFlyDrag.desc": "允許使用本功能更改創造模式下飛行時的空氣阻力系數",
|
||||
"rule.creativeFlyDrag.extra.0": "阻力增加會降低您的飛行速度,\n所以需要相應地調整速度,\n使用 1.0 阻力系數的時候,使用 11 的速度系數似乎與原版速度相匹配.\n本功能僅在游戏端也安裝地毯MOD(版本>=伺服器安裝版本)的情況下生效",
|
||||
"carpet.rule.creativeFlyDrag.name": "創造飛行阻力系數",
|
||||
"carpet.rule.creativeFlyDrag.desc": "允許使用本功能更改創造模式下飛行時的空氣阻力系數",
|
||||
"carpet.rule.creativeFlyDrag.extra.0": "阻力增加會降低您的飛行速度,\n所以需要相應地調整速度,\n使用 1.0 阻力系數的時候,使用 11 的速度系數似乎與原版速度相匹配.\n本功能僅在游戏端也安裝地毯MOD(版本>=伺服器安裝版本)的情況下生效",
|
||||
|
||||
"rule.creativeFlySpeed.name": "創造飛行速度系數",
|
||||
"rule.creativeFlySpeed.desc": "允許使用本功能更改創造模式下飛行速度系數",
|
||||
"rule.creativeFlySpeed.extra.0": "本功能僅在游戏端也安裝地毯MOD(版本>=服務器安裝版本)的情況下生效",
|
||||
"carpet.rule.creativeFlySpeed.name": "創造飛行速度系數",
|
||||
"carpet.rule.creativeFlySpeed.desc": "允許使用本功能更改創造模式下飛行速度系數",
|
||||
"carpet.rule.creativeFlySpeed.extra.0": "本功能僅在游戏端也安裝地毯MOD(版本>=服務器安裝版本)的情況下生效",
|
||||
|
||||
"rule.creativeNoClip.name": "創造玩家無碰撞檢測",
|
||||
"rule.creativeNoClip.desc": "創造玩家可以穿過方塊",
|
||||
"rule.creativeNoClip.extra.0": "這個選項需要伺服器和游戏端同時支持才生效.\n僅在伺服器設置是無效的,而僅在游戏端設置的情況下\n妳可以利用壹些和活板門有關的技巧來穿墻",
|
||||
"carpet.rule.creativeNoClip.name": "創造玩家無碰撞檢測",
|
||||
"carpet.rule.creativeNoClip.desc": "創造玩家可以穿過方塊",
|
||||
"carpet.rule.creativeNoClip.extra.0": "這個選項需要伺服器和游戏端同時支持才生效.\n僅在伺服器設置是無效的,而僅在游戏端設置的情況下\n妳可以利用壹些和活板門有關的技巧來穿墻",
|
||||
|
||||
"rule.fogOff.name": "關閉霧氣",
|
||||
"rule.fogOff.desc": "啟用本功能以關閉地獄與終界的霧氣",
|
||||
"rule.fogOff.extra.0": "提高了可見度,但看起來很奇怪",
|
||||
"carpet.rule.fogOff.name": "關閉霧氣",
|
||||
"carpet.rule.fogOff.desc": "啟用本功能以關閉地獄與終界的霧氣",
|
||||
"carpet.rule.fogOff.extra.0": "提高了可見度,但看起來很奇怪",
|
||||
|
||||
"rule.forceloadLimit.name": "永加載區塊上限更改",
|
||||
"rule.forceloadLimit.desc": "允許使用本功能更改/forceload命令標記的永久加載區塊上限",
|
||||
"carpet.rule.forceloadLimit.name": "永加載區塊上限更改",
|
||||
"carpet.rule.forceloadLimit.desc": "允許使用本功能更改/forceload命令標記的永久加載區塊上限",
|
||||
|
||||
"rule.interactionUpdates.name": "放置方塊產生更新",
|
||||
"rule.interactionUpdates.desc": "放置方塊是否產生更新的開關,設為false以避免產生更新",
|
||||
|
||||
"rule.language.name": "語言",
|
||||
"rule.language.desc": "為地毯Mod設置所需語言",
|
||||
|
||||
"rule.portalSurvivalDelay.name": "生存模式地獄門傳送延遲",
|
||||
"rule.portalSurvivalDelay.desc": "設置生存模式下地獄門傳送的延遲(單位:tick)",
|
||||
|
||||
"rule.spawnChunksSize.name": "出生點永加載區塊半徑",
|
||||
"rule.spawnChunksSize.desc": "更改出生點永加載區塊半徑",
|
||||
"rule.spawnChunksSize.extra.0": "定義新半徑\n設為0以禁用出生點區塊加載",
|
||||
|
||||
"rule.chainStone.name": "強化鎖鏈",
|
||||
"rule.chainStone.desc": "長條鎖鏈在末端間將會相互連接",
|
||||
"rule.chainStone.extra.0": "並且也會與直接連接的方塊連接\n當啟用 stick_to_all : 將無視視覺上連接狀態,使全部連接",
|
||||
|
||||
"rule.piglinsSpawningInBastions.name": "豬布林堡壘生成",
|
||||
"rule.piglinsSpawningInBastions.desc": "豬布林將在堡壘遺跡生成",
|
||||
"rule.piglinsSpawningInBastions.extra.0": "包括 : 豬布林,蠻兵,及一些豬布獸",
|
||||
|
||||
"rule.renewableBlackstone.name": "可再生黑石",
|
||||
"rule.renewableBlackstone.desc": "地獄玄武岩生成機,但在下方沒有靈魂沙",
|
||||
"rule.renewableBlackstone.extra.0": "..將會轉換成黑石",
|
||||
|
||||
"rule.lightEngineMaxBatchSize.name": "光照引擎最大批次大小",
|
||||
"rule.lightEngineMaxBatchSize.desc": "更改光照引擎最大批次大小",
|
||||
"rule.lightEngineMaxBatchSize.extra.0": "允許更高光照抑制容限",
|
||||
"rule.lightEngineMaxBatchSize.extra.1": "設置成5-遊戲預設容限",
|
||||
|
||||
"rule.cleanLogs.name": "清除數據追蹤器",
|
||||
"rule.cleanLogs.desc": "從數據追蹤器中刪除令人討厭的信息",
|
||||
"rule.cleanLogs.extra.0": "在'達到最大聲音對象池247'時將不再顯示",
|
||||
"rule.cleanLogs.extra.1": "在一般農場與裝置是正常顯示",
|
||||
|
||||
"rule.structureBlockOutlineDistance.name": "結構方塊外框顯示距離",
|
||||
"rule.structureBlockOutlineDistance.desc": "自訂義結構方塊外框顯示距離",
|
||||
"rule.structureBlockOutlineDistance.extra.0": "需要客戶端支持才能正常工作",
|
||||
|
||||
"rule.superSecretSetting.name": "最高機密設置",
|
||||
|
||||
"rule.lightningKillsDropsFix.name": "閃電破壞掉落物修正",
|
||||
"rule.lightningKillsDropsFix.desc": "閃電會破壞自閃電擊殺實體的掉落物",
|
||||
"rule.lightningKillsDropsFix.extra.0": "設置成true來防止掉落物被閃電破壞",
|
||||
"rule.lightningKillsDropsFix.extra.1": "修正[MC-206922](https://bugs.mojang.com/browse/MC-206922)",
|
||||
|
||||
"rule.pingPlayerListLimit.name": "玩家ping列表數量限制",
|
||||
"rule.pingPlayerListLimit.desc": "自訂義伺服器ping列表(多人遊戲菜單)玩家列表樣本限制",
|
||||
|
||||
"rule.structureBlockIgnored.name": "結構方塊變更無視方塊",
|
||||
"rule.structureBlockIgnored.desc": "變更被結構方塊無視的方塊",
|
||||
|
||||
"rule.structureBlockLimit.name": "結構方塊上限更改",
|
||||
"rule.structureBlockLimit.desc": "自訂義結構方塊每個方位距離上限",
|
||||
"rule.structureBlockLimit.extra.0": "警告 : 必須是永久的才能正確加載",
|
||||
"rule.structureBlockLimit.extra.1": "建議設置'結構方塊變更無視方塊'成空氣(air)",
|
||||
"rule.structureBlockLimit.extra.2": "當儲存大量結構時",
|
||||
"rule.structureBlockLimit.extra.3": "需要客戶端玩家編輯結構方塊",
|
||||
"rule.structureBlockLimit.extra.4": "當渲染很長的結構時,\n'結構方塊外框顯示距離'將會是必要開啟的",
|
||||
|
||||
|
||||
|
||||
"category.bugfix": "BUG修復",
|
||||
"category.survival": "生存用",
|
||||
"category.creative": "創造用",
|
||||
"category.experimental": "實驗用",
|
||||
"category.optimization": "優化",
|
||||
"category.feature": "特性",
|
||||
"category.command": "指令",
|
||||
"category.tnt": "TNT",
|
||||
"category.dispenser": "發射器",
|
||||
"category.scarpet": "scarpet腳本程式語言",
|
||||
"category.client": "玩家端",
|
||||
|
||||
"ui.browse_categories": "瀏覽分類",
|
||||
"ui.list_all_%(cat)s_settings": "列出所有 %s 選項",
|
||||
"ui.version": "版本",
|
||||
"ui.current_%(mod)s_settings": "目前啟用的 %s 選項",
|
||||
"ui.switch_to": "切換至",
|
||||
"ui.settings_are_locked": "選項被鎖定",
|
||||
"ui.is_set_to": "被設置為",
|
||||
"ui.unknown_rule": "未知的規則選項",
|
||||
"ui.current_%(mod)s_startup_settings_from_%(conf)s": "目前啟用的%s選項,從%s中讀取得",
|
||||
"ui.%(mod)s_settings_matching_'%(query)s'": "%s中符合\"%s\"的選項:",
|
||||
"ui.all_%(mod)s_settings": "所有的%s選項",
|
||||
"ui.tags": "分類",
|
||||
"ui.change_permanently": "設為預設",
|
||||
"ui.click_to_keep_the_settings_in_%(conf)s_to_save_across_restarts": "點擊此按鈕以使此選項保存於%s,重啟後依舊生效",
|
||||
"ui.rule_%(rule)s_will_now_default_to_%(value)s": "規則%s將會被默認設置為%s",
|
||||
"ui.current_value": "當前的值"
|
||||
"carpet.rule.interactionUpdates.name": "放置方塊產生更新",
|
||||
"carpet.rule.interactionUpdates.desc": "放置方塊是否產生更新的開關,設為false以避免產生更新",
|
||||
|
||||
"carpet.rule.language.name": "語言",
|
||||
"carpet.rule.language.desc": "為地毯Mod設置所需語言",
|
||||
|
||||
"carpet.rule.portalSurvivalDelay.name": "生存模式地獄門傳送延遲",
|
||||
"carpet.rule.portalSurvivalDelay.desc": "設置生存模式下地獄門傳送的延遲(單位:tick)",
|
||||
|
||||
"carpet.rule.spawnChunksSize.name": "出生點永加載區塊半徑",
|
||||
"carpet.rule.spawnChunksSize.desc": "更改出生點永加載區塊半徑",
|
||||
"carpet.rule.spawnChunksSize.extra.0": "定義新半徑\n設為0以禁用出生點區塊加載",
|
||||
|
||||
"carpet.rule.chainStone.name": "強化鎖鏈",
|
||||
"carpet.rule.chainStone.desc": "長條鎖鏈在末端間將會相互連接",
|
||||
"carpet.rule.chainStone.extra.0": "並且也會與直接連接的方塊連接\n當啟用 stick_to_all : 將無視視覺上連接狀態,使全部連接",
|
||||
|
||||
"carpet.rule.piglinsSpawningInBastions.name": "豬布林堡壘生成",
|
||||
"carpet.rule.piglinsSpawningInBastions.desc": "豬布林將在堡壘遺跡生成",
|
||||
"carpet.rule.piglinsSpawningInBastions.extra.0": "包括 : 豬布林,蠻兵,及一些豬布獸",
|
||||
|
||||
"carpet.rule.renewableBlackstone.name": "可再生黑石",
|
||||
"carpet.rule.renewableBlackstone.desc": "地獄玄武岩生成機,但在下方沒有靈魂沙",
|
||||
"carpet.rule.renewableBlackstone.extra.0": "..將會轉換成黑石",
|
||||
|
||||
"carpet.rule.lightEngineMaxBatchSize.name": "光照引擎最大批次大小",
|
||||
"carpet.rule.lightEngineMaxBatchSize.desc": "更改光照引擎最大批次大小",
|
||||
"carpet.rule.lightEngineMaxBatchSize.extra.0": "允許更高光照抑制容限",
|
||||
"carpet.rule.lightEngineMaxBatchSize.extra.1": "設置成5-遊戲預設容限",
|
||||
|
||||
"carpet.rule.cleanLogs.name": "清除數據追蹤器",
|
||||
"carpet.rule.cleanLogs.desc": "從數據追蹤器中刪除令人討厭的信息",
|
||||
"carpet.rule.cleanLogs.extra.0": "在'達到最大聲音對象池247'時將不再顯示",
|
||||
"carpet.rule.cleanLogs.extra.1": "在一般農場與裝置是正常顯示",
|
||||
|
||||
"carpet.rule.structureBlockOutlineDistance.name": "結構方塊外框顯示距離",
|
||||
"carpet.rule.structureBlockOutlineDistance.desc": "自訂義結構方塊外框顯示距離",
|
||||
"carpet.rule.structureBlockOutlineDistance.extra.0": "需要客戶端支持才能正常工作",
|
||||
|
||||
"carpet.rule.superSecretSetting.name": "最高機密設置",
|
||||
|
||||
"carpet.rule.lightningKillsDropsFix.name": "閃電破壞掉落物修正",
|
||||
"carpet.rule.lightningKillsDropsFix.desc": "閃電會破壞自閃電擊殺實體的掉落物",
|
||||
"carpet.rule.lightningKillsDropsFix.extra.0": "設置成true來防止掉落物被閃電破壞",
|
||||
"carpet.rule.lightningKillsDropsFix.extra.1": "修正[MC-206922](https://bugs.mojang.com/browse/MC-206922)",
|
||||
|
||||
"carpet.rule.pingPlayerListLimit.name": "玩家ping列表數量限制",
|
||||
"carpet.rule.pingPlayerListLimit.desc": "自訂義伺服器ping列表(多人遊戲菜單)玩家列表樣本限制",
|
||||
|
||||
"carpet.rule.structureBlockIgnored.name": "結構方塊變更無視方塊",
|
||||
"carpet.rule.structureBlockIgnored.desc": "變更被結構方塊無視的方塊",
|
||||
|
||||
"carpet.rule.structureBlockLimit.name": "結構方塊上限更改",
|
||||
"carpet.rule.structureBlockLimit.desc": "自訂義結構方塊每個方位距離上限",
|
||||
"carpet.rule.structureBlockLimit.extra.0": "警告 : 必須是永久的才能正確加載",
|
||||
"carpet.rule.structureBlockLimit.extra.1": "建議設置'結構方塊變更無視方塊'成空氣(air)",
|
||||
"carpet.rule.structureBlockLimit.extra.2": "當儲存大量結構時",
|
||||
"carpet.rule.structureBlockLimit.extra.3": "需要客戶端玩家編輯結構方塊",
|
||||
"carpet.rule.structureBlockLimit.extra.4": "當渲染很長的結構時,\n'結構方塊外框顯示距離'將會是必要開啟的",
|
||||
|
||||
|
||||
|
||||
"carpet.category.bugfix": "BUG修復",
|
||||
"carpet.category.survival": "生存用",
|
||||
"carpet.category.creative": "創造用",
|
||||
"carpet.category.experimental": "實驗用",
|
||||
"carpet.category.optimization": "優化",
|
||||
"carpet.category.feature": "特性",
|
||||
"carpet.category.command": "指令",
|
||||
"carpet.category.tnt": "TNT",
|
||||
"carpet.category.dispenser": "發射器",
|
||||
"carpet.category.scarpet": "scarpet腳本程式語言",
|
||||
"carpet.category.client": "玩家端",
|
||||
|
||||
"carpet.settings.command.browse_categories": "瀏覽分類",
|
||||
"carpet.settings.command.version": "版本",
|
||||
"carpet.settings.command.list_all_category": "列出所有 %s 選項",
|
||||
"carpet.settings.command.current_settings_header": "目前啟用的 %s 選項",
|
||||
"carpet.settings.command.switch_to": "切換至%s",
|
||||
"carpet.settings.command.unknown_rule": "未知的規則選項",
|
||||
"carpet.settings.command.current_from_file_header": "目前啟用的%s選項,從%s中讀取得",
|
||||
"carpet.settings.command.mod_settings_matching": "%s中符合\"%s\"的選項:",
|
||||
"carpet.settings.command.all_mod_settings": "所有的%s選項",
|
||||
"carpet.settings.command.tags": "分類",
|
||||
"carpet.settings.command.change_permanently": "設為預設",
|
||||
"carpet.settings.command.change_permanently_tooltip": "點擊此按鈕以使此選項保存於%s,重啟後依舊生效",
|
||||
"carpet.settings.command.default_set": "規則%s將會被默認設置為%s",
|
||||
"carpet.settings.command.default_removed": "Rule %s will no longer be set on restart",
|
||||
"carpet.settings.command.current_value": "當前的值"
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user