package net.minecraft.gametest.framework; import com.mojang.logging.LogUtils; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import java.util.Arrays; import java.util.List; import java.util.Optional; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.functions.CommandFunction; import net.minecraft.core.Holder; import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; import net.minecraft.resources.RegistryFileCodec; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.server.ServerFunctionManager; import net.minecraft.server.level.ServerLevel; import net.minecraft.util.ExtraCodecs; import net.minecraft.util.StringRepresentable; import net.minecraft.world.level.GameRules; import org.slf4j.Logger; public interface TestEnvironmentDefinition { Codec DIRECT_CODEC = BuiltInRegistries.TEST_ENVIRONMENT_DEFINITION_TYPE .byNameCodec() .dispatch(TestEnvironmentDefinition::codec, mapCodec -> mapCodec); Codec> CODEC = RegistryFileCodec.create(Registries.TEST_ENVIRONMENT, DIRECT_CODEC); static MapCodec bootstrap(Registry> registry) { Registry.register(registry, "all_of", TestEnvironmentDefinition.AllOf.CODEC); Registry.register(registry, "game_rules", TestEnvironmentDefinition.SetGameRules.CODEC); Registry.register(registry, "time_of_day", TestEnvironmentDefinition.TimeOfDay.CODEC); Registry.register(registry, "weather", TestEnvironmentDefinition.Weather.CODEC); return Registry.register(registry, "function", TestEnvironmentDefinition.Functions.CODEC); } void setup(ServerLevel level); default void teardown(ServerLevel level) { } MapCodec codec(); public record AllOf(List> definitions) implements TestEnvironmentDefinition { public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( instance -> instance.group(TestEnvironmentDefinition.CODEC.listOf().fieldOf("definitions").forGetter(TestEnvironmentDefinition.AllOf::definitions)) .apply(instance, TestEnvironmentDefinition.AllOf::new) ); public AllOf(TestEnvironmentDefinition... definitions) { this(Arrays.stream(definitions).map(Holder::direct).toList()); } @Override public void setup(ServerLevel level) { this.definitions.forEach(holder -> ((TestEnvironmentDefinition)holder.value()).setup(level)); } @Override public void teardown(ServerLevel level) { this.definitions.forEach(holder -> ((TestEnvironmentDefinition)holder.value()).teardown(level)); } @Override public MapCodec codec() { return CODEC; } } public record Functions(Optional setupFunction, Optional teardownFunction) implements TestEnvironmentDefinition { private static final Logger LOGGER = LogUtils.getLogger(); public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( instance -> instance.group( ResourceLocation.CODEC.optionalFieldOf("setup").forGetter(TestEnvironmentDefinition.Functions::setupFunction), ResourceLocation.CODEC.optionalFieldOf("teardown").forGetter(TestEnvironmentDefinition.Functions::teardownFunction) ) .apply(instance, TestEnvironmentDefinition.Functions::new) ); @Override public void setup(ServerLevel level) { this.setupFunction.ifPresent(resourceLocation -> run(level, resourceLocation)); } @Override public void teardown(ServerLevel level) { this.teardownFunction.ifPresent(resourceLocation -> run(level, resourceLocation)); } private static void run(ServerLevel level, ResourceLocation function) { MinecraftServer minecraftServer = level.getServer(); ServerFunctionManager serverFunctionManager = minecraftServer.getFunctions(); Optional> optional = serverFunctionManager.get(function); if (optional.isPresent()) { CommandSourceStack commandSourceStack = minecraftServer.createCommandSourceStack().withPermission(2).withSuppressedOutput().withLevel(level); serverFunctionManager.execute((CommandFunction)optional.get(), commandSourceStack); } else { LOGGER.error("Test Batch failed for non-existent function {}", function); } } @Override public MapCodec codec() { return CODEC; } } public record SetGameRules( List> boolRules, List> intRules ) implements TestEnvironmentDefinition { public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( instance -> instance.group( TestEnvironmentDefinition.SetGameRules.Entry.codec(GameRules.BooleanValue.class, Codec.BOOL) .listOf() .fieldOf("bool_rules") .forGetter(TestEnvironmentDefinition.SetGameRules::boolRules), TestEnvironmentDefinition.SetGameRules.Entry.codec(GameRules.IntegerValue.class, Codec.INT) .listOf() .fieldOf("int_rules") .forGetter(TestEnvironmentDefinition.SetGameRules::intRules) ) .apply(instance, TestEnvironmentDefinition.SetGameRules::new) ); @Override public void setup(ServerLevel level) { GameRules gameRules = level.getGameRules(); MinecraftServer minecraftServer = level.getServer(); for (TestEnvironmentDefinition.SetGameRules.Entry entry : this.boolRules) { gameRules.getRule(entry.key()).set(entry.value(), minecraftServer); } for (TestEnvironmentDefinition.SetGameRules.Entry entry : this.intRules) { gameRules.getRule(entry.key()).set(entry.value(), minecraftServer); } } @Override public void teardown(ServerLevel level) { GameRules gameRules = level.getGameRules(); MinecraftServer minecraftServer = level.getServer(); for (TestEnvironmentDefinition.SetGameRules.Entry entry : this.boolRules) { gameRules.getRule(entry.key()).setFrom(GameRules.getType(entry.key()).createRule(), minecraftServer); } for (TestEnvironmentDefinition.SetGameRules.Entry entry : this.intRules) { gameRules.getRule(entry.key()).setFrom(GameRules.getType(entry.key()).createRule(), minecraftServer); } } @Override public MapCodec codec() { return CODEC; } public static > TestEnvironmentDefinition.SetGameRules.Entry entry(GameRules.Key key, S value) { return new TestEnvironmentDefinition.SetGameRules.Entry<>(key, value); } public record Entry>(GameRules.Key key, S value) { public static > Codec> codec(Class valueClass, Codec valueCodec) { return RecordCodecBuilder.create( instance -> instance.group( GameRules.keyCodec(valueClass).fieldOf("rule").forGetter(TestEnvironmentDefinition.SetGameRules.Entry::key), valueCodec.fieldOf("value").forGetter(TestEnvironmentDefinition.SetGameRules.Entry::value) ) .apply(instance, TestEnvironmentDefinition.SetGameRules.Entry::new) ); } } } public record TimeOfDay(int time) implements TestEnvironmentDefinition { public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( instance -> instance.group(ExtraCodecs.NON_NEGATIVE_INT.fieldOf("time").forGetter(TestEnvironmentDefinition.TimeOfDay::time)) .apply(instance, TestEnvironmentDefinition.TimeOfDay::new) ); @Override public void setup(ServerLevel level) { level.setDayTime(this.time); } @Override public MapCodec codec() { return CODEC; } } public record Weather(TestEnvironmentDefinition.Weather.Type weather) implements TestEnvironmentDefinition { public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( instance -> instance.group(TestEnvironmentDefinition.Weather.Type.CODEC.fieldOf("weather").forGetter(TestEnvironmentDefinition.Weather::weather)) .apply(instance, TestEnvironmentDefinition.Weather::new) ); @Override public void setup(ServerLevel level) { this.weather.apply(level); } @Override public void teardown(ServerLevel level) { level.resetWeatherCycle(); } @Override public MapCodec codec() { return CODEC; } public static enum Type implements StringRepresentable { CLEAR("clear", 100000, 0, false, false), RAIN("rain", 0, 100000, true, false), THUNDER("thunder", 0, 100000, true, true); public static final Codec CODEC = StringRepresentable.fromEnum(TestEnvironmentDefinition.Weather.Type::values); private final String id; private final int clearTime; private final int rainTime; private final boolean raining; private final boolean thundering; private Type(final String id, final int clearTime, final int rainTime, final boolean raining, final boolean thundering) { this.id = id; this.clearTime = clearTime; this.rainTime = rainTime; this.raining = raining; this.thundering = thundering; } void apply(ServerLevel level) { level.setWeatherParameters(this.clearTime, this.rainTime, this.raining, this.thundering); } @Override public String getSerializedName() { return this.id; } } } }