package net.minecraft.world.level; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.arguments.BoolArgumentType; import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.logging.LogUtils; import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; import com.mojang.serialization.DynamicLike; import java.util.Comparator; import java.util.Map; import java.util.Map.Entry; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Stream; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.protocol.game.ClientboundEntityEventPacket; import net.minecraft.network.protocol.game.ClientboundGameEventPacket; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.flag.FeatureFlagSet; import net.minecraft.world.flag.FeatureFlags; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; public class GameRules { public static final int DEFAULT_RANDOM_TICK_SPEED = 3; static final Logger LOGGER = LogUtils.getLogger(); private static final Map, GameRules.Type> GAME_RULE_TYPES = Maps.newTreeMap(Comparator.comparing(key -> key.id)); public static final GameRules.Key RULE_DOFIRETICK = register( "doFireTick", GameRules.Category.UPDATES, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_ALLOWFIRETICKAWAYFROMPLAYERS = register( "allowFireTicksAwayFromPlayer", GameRules.Category.UPDATES, GameRules.BooleanValue.create(false) ); public static final GameRules.Key RULE_MOBGRIEFING = register( "mobGriefing", GameRules.Category.MOBS, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_KEEPINVENTORY = register( "keepInventory", GameRules.Category.PLAYER, GameRules.BooleanValue.create(false) ); public static final GameRules.Key RULE_DOMOBSPAWNING = register( "doMobSpawning", GameRules.Category.SPAWNING, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_DOMOBLOOT = register("doMobLoot", GameRules.Category.DROPS, GameRules.BooleanValue.create(true)); public static final GameRules.Key RULE_PROJECTILESCANBREAKBLOCKS = register( "projectilesCanBreakBlocks", GameRules.Category.DROPS, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_DOBLOCKDROPS = register( "doTileDrops", GameRules.Category.DROPS, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_DOENTITYDROPS = register( "doEntityDrops", GameRules.Category.DROPS, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_COMMANDBLOCKOUTPUT = register( "commandBlockOutput", GameRules.Category.CHAT, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_NATURAL_REGENERATION = register( "naturalRegeneration", GameRules.Category.PLAYER, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_DAYLIGHT = register( "doDaylightCycle", GameRules.Category.UPDATES, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_LOGADMINCOMMANDS = register( "logAdminCommands", GameRules.Category.CHAT, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_SHOWDEATHMESSAGES = register( "showDeathMessages", GameRules.Category.CHAT, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_RANDOMTICKING = register( "randomTickSpeed", GameRules.Category.UPDATES, GameRules.IntegerValue.create(3) ); public static final GameRules.Key RULE_SENDCOMMANDFEEDBACK = register( "sendCommandFeedback", GameRules.Category.CHAT, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_REDUCEDDEBUGINFO = register( "reducedDebugInfo", GameRules.Category.MISC, GameRules.BooleanValue.create(false, (minecraftServer, booleanValue) -> { byte b = (byte)(booleanValue.get() ? 22 : 23); for (ServerPlayer serverPlayer : minecraftServer.getPlayerList().getPlayers()) { serverPlayer.connection.send(new ClientboundEntityEventPacket(serverPlayer, b)); } }) ); public static final GameRules.Key RULE_SPECTATORSGENERATECHUNKS = register( "spectatorsGenerateChunks", GameRules.Category.PLAYER, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_SPAWN_RADIUS = register( "spawnRadius", GameRules.Category.PLAYER, GameRules.IntegerValue.create(10) ); public static final GameRules.Key RULE_DISABLE_PLAYER_MOVEMENT_CHECK = register( "disablePlayerMovementCheck", GameRules.Category.PLAYER, GameRules.BooleanValue.create(false) ); public static final GameRules.Key RULE_DISABLE_ELYTRA_MOVEMENT_CHECK = register( "disableElytraMovementCheck", GameRules.Category.PLAYER, GameRules.BooleanValue.create(false) ); public static final GameRules.Key RULE_MAX_ENTITY_CRAMMING = register( "maxEntityCramming", GameRules.Category.MOBS, GameRules.IntegerValue.create(24) ); public static final GameRules.Key RULE_WEATHER_CYCLE = register( "doWeatherCycle", GameRules.Category.UPDATES, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_LIMITED_CRAFTING = register( "doLimitedCrafting", GameRules.Category.PLAYER, GameRules.BooleanValue.create(false, (minecraftServer, booleanValue) -> { for (ServerPlayer serverPlayer : minecraftServer.getPlayerList().getPlayers()) { serverPlayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.LIMITED_CRAFTING, booleanValue.get() ? 1.0F : 0.0F)); } }) ); public static final GameRules.Key RULE_MAX_COMMAND_CHAIN_LENGTH = register( "maxCommandChainLength", GameRules.Category.MISC, GameRules.IntegerValue.create(65536) ); public static final GameRules.Key RULE_MAX_COMMAND_FORK_COUNT = register( "maxCommandForkCount", GameRules.Category.MISC, GameRules.IntegerValue.create(65536) ); public static final GameRules.Key RULE_COMMAND_MODIFICATION_BLOCK_LIMIT = register( "commandModificationBlockLimit", GameRules.Category.MISC, GameRules.IntegerValue.create(32768) ); public static final GameRules.Key RULE_ANNOUNCE_ADVANCEMENTS = register( "announceAdvancements", GameRules.Category.CHAT, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_DISABLE_RAIDS = register( "disableRaids", GameRules.Category.MOBS, GameRules.BooleanValue.create(false) ); public static final GameRules.Key RULE_DOINSOMNIA = register( "doInsomnia", GameRules.Category.SPAWNING, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_DO_IMMEDIATE_RESPAWN = register( "doImmediateRespawn", GameRules.Category.PLAYER, GameRules.BooleanValue.create(false, (minecraftServer, booleanValue) -> { for (ServerPlayer serverPlayer : minecraftServer.getPlayerList().getPlayers()) { serverPlayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.IMMEDIATE_RESPAWN, booleanValue.get() ? 1.0F : 0.0F)); } }) ); public static final GameRules.Key RULE_PLAYERS_NETHER_PORTAL_DEFAULT_DELAY = register( "playersNetherPortalDefaultDelay", GameRules.Category.PLAYER, GameRules.IntegerValue.create(80) ); public static final GameRules.Key RULE_PLAYERS_NETHER_PORTAL_CREATIVE_DELAY = register( "playersNetherPortalCreativeDelay", GameRules.Category.PLAYER, GameRules.IntegerValue.create(0) ); public static final GameRules.Key RULE_DROWNING_DAMAGE = register( "drowningDamage", GameRules.Category.PLAYER, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_FALL_DAMAGE = register( "fallDamage", GameRules.Category.PLAYER, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_FIRE_DAMAGE = register( "fireDamage", GameRules.Category.PLAYER, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_FREEZE_DAMAGE = register( "freezeDamage", GameRules.Category.PLAYER, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_DO_PATROL_SPAWNING = register( "doPatrolSpawning", GameRules.Category.SPAWNING, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_DO_TRADER_SPAWNING = register( "doTraderSpawning", GameRules.Category.SPAWNING, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_DO_WARDEN_SPAWNING = register( "doWardenSpawning", GameRules.Category.SPAWNING, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_FORGIVE_DEAD_PLAYERS = register( "forgiveDeadPlayers", GameRules.Category.MOBS, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_UNIVERSAL_ANGER = register( "universalAnger", GameRules.Category.MOBS, GameRules.BooleanValue.create(false) ); public static final GameRules.Key RULE_PLAYERS_SLEEPING_PERCENTAGE = register( "playersSleepingPercentage", GameRules.Category.PLAYER, GameRules.IntegerValue.create(100) ); public static final GameRules.Key RULE_BLOCK_EXPLOSION_DROP_DECAY = register( "blockExplosionDropDecay", GameRules.Category.DROPS, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_MOB_EXPLOSION_DROP_DECAY = register( "mobExplosionDropDecay", GameRules.Category.DROPS, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_TNT_EXPLOSION_DROP_DECAY = register( "tntExplosionDropDecay", GameRules.Category.DROPS, GameRules.BooleanValue.create(false) ); public static final GameRules.Key RULE_SNOW_ACCUMULATION_HEIGHT = register( "snowAccumulationHeight", GameRules.Category.UPDATES, GameRules.IntegerValue.create(1) ); public static final GameRules.Key RULE_WATER_SOURCE_CONVERSION = register( "waterSourceConversion", GameRules.Category.UPDATES, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_LAVA_SOURCE_CONVERSION = register( "lavaSourceConversion", GameRules.Category.UPDATES, GameRules.BooleanValue.create(false) ); public static final GameRules.Key RULE_GLOBAL_SOUND_EVENTS = register( "globalSoundEvents", GameRules.Category.MISC, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_DO_VINES_SPREAD = register( "doVinesSpread", GameRules.Category.UPDATES, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_ENDER_PEARLS_VANISH_ON_DEATH = register( "enderPearlsVanishOnDeath", GameRules.Category.PLAYER, GameRules.BooleanValue.create(true) ); public static final GameRules.Key RULE_MINECART_MAX_SPEED = register( "minecartMaxSpeed", GameRules.Category.MISC, GameRules.IntegerValue.create(8, 1, 1000, FeatureFlagSet.of(FeatureFlags.MINECART_IMPROVEMENTS), (minecraftServer, integerValue) -> {}) ); public static final GameRules.Key RULE_SPAWN_CHUNK_RADIUS = register( "spawnChunkRadius", GameRules.Category.MISC, GameRules.IntegerValue.create(2, 0, 32, FeatureFlagSet.of(), (minecraftServer, integerValue) -> { ServerLevel serverLevel = minecraftServer.overworld(); serverLevel.setDefaultSpawnPos(serverLevel.getSharedSpawnPos(), serverLevel.getSharedSpawnAngle()); }) ); public static final GameRules.Key RULE_TNT_EXPLODES = register( "tntExplodes", GameRules.Category.MISC, GameRules.BooleanValue.create(true) ); private final Map, GameRules.Value> rules; private final FeatureFlagSet enabledFeatures; public static > GameRules.Type getType(GameRules.Key key) { return (GameRules.Type)GAME_RULE_TYPES.get(key); } public static > Codec> keyCodec(Class clazz) { return Codec.STRING .comapFlatMap( string -> (DataResult)GAME_RULE_TYPES.entrySet() .stream() .filter(entry -> ((GameRules.Type)entry.getValue()).valueClass == clazz) .map(Entry::getKey) .filter(key -> key.getId().equals(string)) .map(key -> key) .findFirst() .map(DataResult::success) .orElseGet(() -> DataResult.error(() -> "Invalid game rule ID for type: " + string)), GameRules.Key::getId ); } private static > GameRules.Key register(String name, GameRules.Category category, GameRules.Type type) { GameRules.Key key = new GameRules.Key<>(name, category); GameRules.Type type2 = (GameRules.Type)GAME_RULE_TYPES.put(key, type); if (type2 != null) { throw new IllegalStateException("Duplicate game rule registration for " + name); } else { return key; } } public GameRules(FeatureFlagSet enabledFeatures, DynamicLike tag) { this(enabledFeatures); this.loadFromTag(tag); } public GameRules(FeatureFlagSet enabledFeatures) { this( (Map, GameRules.Value>)availableRules(enabledFeatures) .collect(ImmutableMap.toImmutableMap(Entry::getKey, entry -> ((GameRules.Type)entry.getValue()).createRule())), enabledFeatures ); } private static Stream, GameRules.Type>> availableRules(FeatureFlagSet enabledFeatures) { return GAME_RULE_TYPES.entrySet().stream().filter(entry -> ((GameRules.Type)entry.getValue()).requiredFeatures.isSubsetOf(enabledFeatures)); } private GameRules(Map, GameRules.Value> rules, FeatureFlagSet enabledFeatures) { this.rules = rules; this.enabledFeatures = enabledFeatures; } public > T getRule(GameRules.Key key) { T value = (T)this.rules.get(key); if (value == null) { throw new IllegalArgumentException("Tried to access invalid game rule"); } else { return value; } } /** * Return the defined game rules as NBT. */ public CompoundTag createTag() { CompoundTag compoundTag = new CompoundTag(); this.rules.forEach((key, value) -> compoundTag.putString(key.id, value.serialize())); return compoundTag; } private void loadFromTag(DynamicLike dynamic) { this.rules.forEach((key, value) -> dynamic.get(key.id).asString().ifSuccess(value::deserialize)); } public GameRules copy(FeatureFlagSet enabledFeatures) { return new GameRules( (Map, GameRules.Value>)availableRules(enabledFeatures) .collect( ImmutableMap.toImmutableMap( Entry::getKey, entry -> this.rules.containsKey(entry.getKey()) ? (GameRules.Value)this.rules.get(entry.getKey()) : ((GameRules.Type)entry.getValue()).createRule() ) ), enabledFeatures ); } public void visitGameRuleTypes(GameRules.GameRuleTypeVisitor visitor) { GAME_RULE_TYPES.forEach((key, type) -> this.callVisitorCap(visitor, key, type)); } private > void callVisitorCap(GameRules.GameRuleTypeVisitor visitor, GameRules.Key key, GameRules.Type type) { if (type.requiredFeatures.isSubsetOf(this.enabledFeatures)) { visitor.visit(key, type); type.callVisitor(visitor, key); } } public void assignFrom(GameRules rules, @Nullable MinecraftServer server) { rules.rules.keySet().forEach(key -> this.assignCap(key, rules, server)); } private > void assignCap(GameRules.Key key, GameRules rules, @Nullable MinecraftServer server) { T value = rules.getRule(key); this.getRule(key).setFrom(value, server); } public boolean getBoolean(GameRules.Key key) { return this.getRule(key).get(); } public int getInt(GameRules.Key key) { return this.getRule(key).get(); } public static class BooleanValue extends GameRules.Value { private boolean value; static GameRules.Type create(boolean defaultValue, BiConsumer changeListener) { return new GameRules.Type<>( BoolArgumentType::bool, type -> new GameRules.BooleanValue(type, defaultValue), changeListener, GameRules.GameRuleTypeVisitor::visitBoolean, GameRules.BooleanValue.class, FeatureFlagSet.of() ); } static GameRules.Type create(boolean defaultValue) { return create(defaultValue, (minecraftServer, booleanValue) -> {}); } public BooleanValue(GameRules.Type type, boolean value) { super(type); this.value = value; } @Override protected void updateFromArgument(CommandContext context, String paramName) { this.value = BoolArgumentType.getBool(context, paramName); } public boolean get() { return this.value; } public void set(boolean value, @Nullable MinecraftServer server) { this.value = value; this.onChanged(server); } @Override public String serialize() { return Boolean.toString(this.value); } @Override protected void deserialize(String value) { this.value = Boolean.parseBoolean(value); } @Override public int getCommandResult() { return this.value ? 1 : 0; } protected GameRules.BooleanValue getSelf() { return this; } protected GameRules.BooleanValue copy() { return new GameRules.BooleanValue(this.type, this.value); } public void setFrom(GameRules.BooleanValue value, @Nullable MinecraftServer server) { this.value = value.value; this.onChanged(server); } } public static enum Category { PLAYER("gamerule.category.player"), MOBS("gamerule.category.mobs"), SPAWNING("gamerule.category.spawning"), DROPS("gamerule.category.drops"), UPDATES("gamerule.category.updates"), CHAT("gamerule.category.chat"), MISC("gamerule.category.misc"); private final String descriptionId; private Category(final String descriptionId) { this.descriptionId = descriptionId; } public String getDescriptionId() { return this.descriptionId; } } public interface GameRuleTypeVisitor { default > void visit(GameRules.Key key, GameRules.Type type) { } default void visitBoolean(GameRules.Key key, GameRules.Type type) { } default void visitInteger(GameRules.Key key, GameRules.Type type) { } } public static class IntegerValue extends GameRules.Value { private int value; private static GameRules.Type create(int defaultValue, BiConsumer changeListener) { return new GameRules.Type<>( IntegerArgumentType::integer, type -> new GameRules.IntegerValue(type, defaultValue), changeListener, GameRules.GameRuleTypeVisitor::visitInteger, GameRules.IntegerValue.class, FeatureFlagSet.of() ); } static GameRules.Type create( int defaultValue, int min, int max, FeatureFlagSet requiredFeatures, BiConsumer changeListener ) { return new GameRules.Type<>( () -> IntegerArgumentType.integer(min, max), type -> new GameRules.IntegerValue(type, defaultValue), changeListener, GameRules.GameRuleTypeVisitor::visitInteger, GameRules.IntegerValue.class, requiredFeatures ); } static GameRules.Type create(int defaultValue) { return create(defaultValue, (minecraftServer, integerValue) -> {}); } public IntegerValue(GameRules.Type type, int value) { super(type); this.value = value; } @Override protected void updateFromArgument(CommandContext context, String paramName) { this.value = IntegerArgumentType.getInteger(context, paramName); } public int get() { return this.value; } public void set(int value, @Nullable MinecraftServer server) { this.value = value; this.onChanged(server); } @Override public String serialize() { return Integer.toString(this.value); } @Override protected void deserialize(String value) { this.value = safeParse(value); } public boolean tryDeserialize(String name) { try { StringReader stringReader = new StringReader(name); this.value = (Integer)((ArgumentType)this.type.argument.get()).parse(stringReader); return !stringReader.canRead(); } catch (CommandSyntaxException var3) { return false; } } private static int safeParse(String strValue) { if (!strValue.isEmpty()) { try { return Integer.parseInt(strValue); } catch (NumberFormatException var2) { GameRules.LOGGER.warn("Failed to parse integer {}", strValue); } } return 0; } @Override public int getCommandResult() { return this.value; } protected GameRules.IntegerValue getSelf() { return this; } protected GameRules.IntegerValue copy() { return new GameRules.IntegerValue(this.type, this.value); } public void setFrom(GameRules.IntegerValue value, @Nullable MinecraftServer server) { this.value = value.value; this.onChanged(server); } } public static final class Key> { final String id; private final GameRules.Category category; public Key(String id, GameRules.Category category) { this.id = id; this.category = category; } public String toString() { return this.id; } public boolean equals(Object object) { return this == object ? true : object instanceof GameRules.Key && ((GameRules.Key)object).id.equals(this.id); } public int hashCode() { return this.id.hashCode(); } public String getId() { return this.id; } public String getDescriptionId() { return "gamerule." + this.id; } public GameRules.Category getCategory() { return this.category; } } public static class Type> { final Supplier> argument; private final Function, T> constructor; final BiConsumer callback; private final GameRules.VisitorCaller visitorCaller; final Class valueClass; final FeatureFlagSet requiredFeatures; Type( Supplier> argument, Function, T> constructor, BiConsumer callback, GameRules.VisitorCaller visitorCaller, Class valueClass, FeatureFlagSet requiredFeatures ) { this.argument = argument; this.constructor = constructor; this.callback = callback; this.visitorCaller = visitorCaller; this.valueClass = valueClass; this.requiredFeatures = requiredFeatures; } public RequiredArgumentBuilder createArgument(String name) { return Commands.argument(name, (ArgumentType)this.argument.get()); } public T createRule() { return (T)this.constructor.apply(this); } public void callVisitor(GameRules.GameRuleTypeVisitor visitor, GameRules.Key key) { this.visitorCaller.call(visitor, key, this); } public FeatureFlagSet requiredFeatures() { return this.requiredFeatures; } } public abstract static class Value> { protected final GameRules.Type type; public Value(GameRules.Type type) { this.type = type; } protected abstract void updateFromArgument(CommandContext context, String paramName); public void setFromArgument(CommandContext context, String paramName) { this.updateFromArgument(context, paramName); this.onChanged(context.getSource().getServer()); } protected void onChanged(@Nullable MinecraftServer server) { if (server != null) { this.type.callback.accept(server, this.getSelf()); } } protected abstract void deserialize(String value); public abstract String serialize(); public String toString() { return this.serialize(); } public abstract int getCommandResult(); protected abstract T getSelf(); protected abstract T copy(); public abstract void setFrom(T value, @Nullable MinecraftServer server); } interface VisitorCaller> { void call(GameRules.GameRuleTypeVisitor gameRuleTypeVisitor, GameRules.Key key, GameRules.Type type); } }