diff --git a/patches/net/minecraft/client/Minecraft.java.patch b/patches/net/minecraft/client/Minecraft.java.patch index 13ff259e92..5e00660eca 100644 --- a/patches/net/minecraft/client/Minecraft.java.patch +++ b/patches/net/minecraft/client/Minecraft.java.patch @@ -9,6 +9,16 @@ static Minecraft instance; private static final Logger LOGGER = LogUtils.getLogger(); public static final boolean ON_OSX = Util.getPlatform() == Util.OS.OSX; +@@ -399,6 +_,9 @@ + private final long clientStartTimeMs; + private long clientTickCount; + private String debugPath = "root"; ++ @org.jetbrains.annotations.Nullable ++ @org.jetbrains.annotations.ApiStatus.Internal ++ public net.neoforged.neoforge.flag.FlagManager clientModdedFlagManager; + + public Minecraft(GameConfig p_91084_) { + super("Client"); @@ -435,7 +_,6 @@ } }, Util.nonCriticalIoPool()); @@ -336,10 +346,11 @@ this.level = p_91157_; this.updateLevelInEngines(p_91157_); if (!this.isLocalServer) { -@@ -2093,6 +_,7 @@ +@@ -2093,6 +_,8 @@ IntegratedServer integratedserver = this.singleplayerServer; this.singleplayerServer = null; this.gameRenderer.resetData(); ++ this.clientModdedFlagManager = null; + net.neoforged.neoforge.client.ClientHooks.firePlayerLogout(this.gameMode, this.player); this.gameMode = null; this.narrator.clear(); @@ -378,14 +389,29 @@ if (itemstack == null) { return; } -@@ -2794,6 +_,10 @@ - - public void updateMaxMipLevel(int p_91313_) { +@@ -2796,6 +_,10 @@ this.modelManager.updateMaxMipLevel(p_91313_); -+ } -+ + } + + public ItemColors getItemColors() { + return this.itemColors; ++ } ++ + public EntityModelSet getEntityModels() { + return this.entityModels; + } +@@ -2868,6 +_,14 @@ + @Nullable + public static String getLauncherBrand() { + return System.getProperty("minecraft.launcher.brand"); ++ } ++ ++ public net.neoforged.neoforge.flag.FlagManager getModdedFlagManager() { ++ if(clientModdedFlagManager != null) ++ return clientModdedFlagManager; ++ if(singleplayerServer != null) ++ return singleplayerServer.getModdedFlagManager(); ++ return net.neoforged.neoforge.flag.FlagManager.EMPTY; } - public EntityModelSet getEntityModels() { + @OnlyIn(Dist.CLIENT) diff --git a/patches/net/minecraft/client/gui/screens/worldselection/CreateWorldScreen.java.patch b/patches/net/minecraft/client/gui/screens/worldselection/CreateWorldScreen.java.patch index 1c369de99a..f95f8e4311 100644 --- a/patches/net/minecraft/client/gui/screens/worldselection/CreateWorldScreen.java.patch +++ b/patches/net/minecraft/client/gui/screens/worldselection/CreateWorldScreen.java.patch @@ -8,6 +8,17 @@ WorldLoader.InitConfig worldloader$initconfig = createDefaultLoadConfig(packrepository, WorldDataConfiguration.DEFAULT); CompletableFuture completablefuture = WorldLoader.load( worldloader$initconfig, +@@ -219,7 +_,9 @@ + WorldDimensions.Complete worlddimensions$complete = worldcreationcontext.selectedDimensions().bake(worldcreationcontext.datapackDimensions()); + LayeredRegistryAccess layeredregistryaccess = worldcreationcontext.worldgenRegistries() + .replaceFrom(RegistryLayer.DIMENSIONS, worlddimensions$complete.dimensionsRegistryAccess()); +- Lifecycle lifecycle = FeatureFlags.isExperimental(worldcreationcontext.dataConfiguration().enabledFeatures()) ++ // Neo: vanilla experimental flag present or modded flag present ++ var isExperimental = FeatureFlags.isExperimental(worldcreationcontext.dataConfiguration().enabledFeatures()) || !worldcreationcontext.dataConfiguration().enabledModdedFeatures().isEmpty(); ++ Lifecycle lifecycle = isExperimental + ? Lifecycle.experimental() + : Lifecycle.stable(); + Lifecycle lifecycle1 = layeredregistryaccess.compositeAccess().allRegistriesLifecycle(); @@ -243,6 +_,10 @@ WorldCreationContext worldcreationcontext = this.uiState.getSettings(); LevelSettings levelsettings = this.createLevelSettings(flag); @@ -19,12 +30,30 @@ this.minecraft .createWorldOpenFlows() .createLevelFromExistingSettings(optional.get(), worldcreationcontext.dataPackResources(), p_249152_, worlddata); +@@ -358,13 +_,15 @@ + List list = ImmutableList.copyOf(p_270299_.getSelectedIds()); + List list1 = p_270299_.getAvailableIds().stream().filter(p_232927_ -> !list.contains(p_232927_)).collect(ImmutableList.toImmutableList()); + WorldDataConfiguration worlddataconfiguration = new WorldDataConfiguration( +- new DataPackConfig(list, list1), this.uiState.getSettings().dataConfiguration().enabledFeatures() ++ new DataPackConfig(list, list1), this.uiState.getSettings().dataConfiguration().enabledFeatures(), this.uiState.getSettings().dataConfiguration().enabledModdedFeatures() + ); + if (this.uiState.tryUpdateDataConfiguration(worlddataconfiguration)) { + this.minecraft.setScreen(this); + } else { + FeatureFlagSet featureflagset = p_270299_.getRequestedFeatureFlags(); +- if (FeatureFlags.isExperimental(featureflagset) && p_270896_) { ++ // Neo: vanilla experimental flag present or modded flag present ++ var isExperimental = FeatureFlags.isExperimental(featureflagset) || !worlddataconfiguration.enabledModdedFeatures().isEmpty(); ++ if (isExperimental && p_270896_) { + this.minecraft.setScreen(new ConfirmExperimentalFeaturesScreen(p_270299_.getSelectedPacks(), p_269635_ -> { + if (p_269635_) { + this.applyNewPackConfig(p_270299_, worlddataconfiguration, p_270760_); @@ -428,7 +_,7 @@ if (p_269627_) { p_270552_.accept(this.uiState.getSettings().dataConfiguration()); } else { - p_270552_.accept(WorldDataConfiguration.DEFAULT); -+ p_270552_.accept(new WorldDataConfiguration(new DataPackConfig(ImmutableList.of("vanilla"), ImmutableList.of()), FeatureFlags.VANILLA_SET)); // FORGE: Revert to *actual* vanilla data ++ p_270552_.accept(new WorldDataConfiguration(new DataPackConfig(ImmutableList.of("vanilla"), ImmutableList.of()), FeatureFlags.VANILLA_SET, it.unimi.dsi.fastutil.objects.ReferenceSets.emptySet())); // FORGE: Revert to *actual* vanilla data } }, Component.translatable("dataPack.validation.failed"), diff --git a/patches/net/minecraft/client/multiplayer/ClientLevel.java.patch b/patches/net/minecraft/client/multiplayer/ClientLevel.java.patch index e3eabb22b7..0539cfce5f 100644 --- a/patches/net/minecraft/client/multiplayer/ClientLevel.java.patch +++ b/patches/net/minecraft/client/multiplayer/ClientLevel.java.patch @@ -109,7 +109,7 @@ this.difficulty = p_104852_; } -@@ -1069,14 +_,75 @@ +@@ -1069,14 +_,80 @@ if (p_171712_ instanceof AbstractClientPlayer) { ClientLevel.this.players.add((AbstractClientPlayer)p_171712_); } @@ -183,5 +183,10 @@ + @org.jetbrains.annotations.ApiStatus.Internal + public void setDayTimePerTick(float dayTimePerTick) { + this.dayTimePerTick = dayTimePerTick; ++ } ++ ++ @Override ++ public net.neoforged.neoforge.flag.FlagManager getModdedFlagManager() { ++ return minecraft.getModdedFlagManager(); } } diff --git a/patches/net/minecraft/gametest/framework/GameTestServer.java.patch b/patches/net/minecraft/gametest/framework/GameTestServer.java.patch index 162c353781..78e9a72394 100644 --- a/patches/net/minecraft/gametest/framework/GameTestServer.java.patch +++ b/patches/net/minecraft/gametest/framework/GameTestServer.java.patch @@ -1,5 +1,14 @@ --- a/net/minecraft/gametest/framework/GameTestServer.java +++ b/net/minecraft/gametest/framework/GameTestServer.java +@@ -79,7 +_,7 @@ + } else { + p_206609_.reload(); + WorldDataConfiguration worlddataconfiguration = new WorldDataConfiguration( +- new DataPackConfig(new ArrayList<>(p_206609_.getAvailableIds()), List.of()), FeatureFlags.REGISTRY.allFlags() ++ new DataPackConfig(new ArrayList<>(p_206609_.getAvailableIds()), List.of()), FeatureFlags.REGISTRY.allFlags(), net.neoforged.neoforge.flag.Flag.getFlags() + ); + LevelSettings levelsettings = new LevelSettings( + "Test Level", GameType.CREATIVE, false, Difficulty.NORMAL, true, TEST_GAME_RULES, worlddataconfiguration @@ -151,6 +_,7 @@ public boolean initServer() { this.setPlayerList(new PlayerList(this, this.registries(), this.playerDataStorage, 1) { diff --git a/patches/net/minecraft/server/Main.java.patch b/patches/net/minecraft/server/Main.java.patch index 786a375e82..342f75544f 100644 --- a/patches/net/minecraft/server/Main.java.patch +++ b/patches/net/minecraft/server/Main.java.patch @@ -142,3 +142,12 @@ } }; thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER)); +@@ -302,7 +_,7 @@ + worlddataconfiguration = worlddataconfiguration1; + } else { + flag = true; +- worlddataconfiguration = new WorldDataConfiguration(p_248563_.initialDataPackConfiguration, FeatureFlags.DEFAULT_FLAGS); ++ worlddataconfiguration = new WorldDataConfiguration(p_248563_.initialDataPackConfiguration, FeatureFlags.DEFAULT_FLAGS, it.unimi.dsi.fastutil.objects.ReferenceSets.emptySet()); + } + + WorldLoader.PackConfig worldloader$packconfig = new WorldLoader.PackConfig(p_251069_, worlddataconfiguration, p_249093_, flag); diff --git a/patches/net/minecraft/server/MinecraftServer.java.patch b/patches/net/minecraft/server/MinecraftServer.java.patch index 5d60f55f4d..9d6a253a89 100644 --- a/patches/net/minecraft/server/MinecraftServer.java.patch +++ b/patches/net/minecraft/server/MinecraftServer.java.patch @@ -1,6 +1,10 @@ --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java -@@ -264,7 +_,7 @@ +@@ -261,10 +_,11 @@ + private final PotionBrewing potionBrewing; + private volatile boolean isSaving; + private static final AtomicReference fatalException = new AtomicReference<>(); ++ private final net.neoforged.neoforge.flag.FlagManager moddedFlagManager; public static S spin(Function p_129873_) { AtomicReference atomicreference = new AtomicReference<>(); @@ -9,6 +13,14 @@ thread.setUncaughtExceptionHandler((p_177909_, p_177910_) -> LOGGER.error("Uncaught exception in server thread", p_177910_)); if (Runtime.getRuntime().availableProcessors() > 4) { thread.setPriority(8); +@@ -316,6 +_,7 @@ + this.serverThread = p_236723_; + this.executor = Util.backgroundExecutor(); + this.potionBrewing = PotionBrewing.bootstrap(this.worldData.enabledFeatures()); ++ this.moddedFlagManager = new net.neoforged.neoforge.flag.ServerFlagManager(this, this.worldData.getDataConfiguration().enabledModdedFeatures()); + } + } + @@ -372,6 +_,7 @@ this.readScoreboard(dimensiondatastorage); this.commandStorage = new CommandStorage(dimensiondatastorage); @@ -205,6 +217,18 @@ } public SystemReport fillSystemReport(SystemReport p_177936_) { +@@ -1135,6 +_,11 @@ + "Enabled Feature Flags", + () -> FeatureFlags.REGISTRY.toNames(this.worldData.enabledFeatures()).stream().map(ResourceLocation::toString).collect(Collectors.joining(", ")) + ); ++ // Neo: Append modded feature flags to crash reports ++ p_177936_.setDetail( ++ "Enabled Mod Features", ++ () -> moddedFlagManager.enabledFlags().map(net.neoforged.neoforge.flag.Flag::toStringShort).collect(Collectors.joining(", ")) ++ ); + p_177936_.setDetail("World Generation", () -> this.worldData.worldGenSettingsLifecycle().toString()); + p_177936_.setDetail("World Seed", () -> String.valueOf(this.worldData.worldGenOptions().seed())); + if (this.serverId != null) { @@ -1441,7 +_,7 @@ public CompletableFuture reloadResources(Collection p_129862_) { @@ -214,6 +238,23 @@ this ) .thenCompose( +@@ -1451,6 +_,7 @@ + closeableresourcemanager, + this.registries, + this.worldData.enabledFeatures(), ++ this.moddedFlagManager, + this.isDedicatedServer() ? Commands.CommandSelection.DEDICATED : Commands.CommandSelection.INTEGRATED, + this.getFunctionCompilationLevel(), + this.executor, +@@ -1470,7 +_,7 @@ + this.resources = p_335203_; + this.packRepository.setSelected(p_129862_); + WorldDataConfiguration worlddataconfiguration = new WorldDataConfiguration( +- getSelectedPacks(this.packRepository, true), this.worldData.enabledFeatures() ++ getSelectedPacks(this.packRepository, true), this.worldData.enabledFeatures(), this.worldData.getDataConfiguration().enabledModdedFeatures() + ); + this.worldData.setDataConfiguration(worlddataconfiguration); + this.resources.managers.updateRegistryTags(); @@ -1478,6 +_,7 @@ this.getPlayerList().reloadResources(); this.functionManager.replaceLibrary(this.resources.managers.getFunctionLibrary()); @@ -230,19 +271,44 @@ + datapackconfig.addModPacks(net.neoforged.neoforge.common.CommonHooks.getModDataPacks()); if (p_341620_) { - return configureRepositoryWithSelection(p_248681_, List.of("vanilla"), featureflagset, false); -+ return configureRepositoryWithSelection(p_248681_, net.neoforged.neoforge.common.CommonHooks.getModDataPacksWithVanilla(), featureflagset, false); ++ return configureRepositoryWithSelection(p_248681_, net.neoforged.neoforge.common.CommonHooks.getModDataPacksWithVanilla(), featureflagset, p_341632_.enabledModdedFeatures(), false); } else { Set set = Sets.newLinkedHashSet(); -@@ -1542,6 +_,8 @@ +@@ -1542,18 +_,30 @@ set.add("vanilla"); } +- return configureRepositoryWithSelection(p_248681_, set, featureflagset, true); + net.neoforged.neoforge.resource.ResourcePackLoader.reorderNewlyDiscoveredPacks(set, datapackconfig.getEnabled(), p_248681_); + - return configureRepositoryWithSelection(p_248681_, set, featureflagset, true); ++ return configureRepositoryWithSelection(p_248681_, set, featureflagset, p_341632_.enabledModdedFeatures(), true); } } + + private static WorldDataConfiguration configureRepositoryWithSelection( +- PackRepository p_341680_, Collection p_341677_, FeatureFlagSet p_341602_, boolean p_341662_ ++ PackRepository p_341680_, Collection p_341677_, FeatureFlagSet p_341602_, it.unimi.dsi.fastutil.objects.ReferenceSet enabledModdedFeatures, boolean p_341662_ + ) { + p_341680_.setSelected(p_341677_); + enableForcedFeaturePacks(p_341680_, p_341602_); + DataPackConfig datapackconfig = getSelectedPacks(p_341680_, p_341662_); + FeatureFlagSet featureflagset = p_341680_.getRequestedFeatureFlags().join(p_341602_); +- return new WorldDataConfiguration(datapackconfig, featureflagset); ++ return new WorldDataConfiguration(datapackconfig, featureflagset, enabledModdedFeatures); ++ } ++ ++ /** ++ * @deprecated Prefer {@link #configureRepositoryWithSelection(PackRepository, Collection, FeatureFlagSet, it.unimi.dsi.fastutil.objects.ReferenceSet, boolean)} ++ */ ++ @Deprecated ++ private static WorldDataConfiguration configureRepositoryWithSelection( ++ PackRepository p_341680_, Collection p_341677_, FeatureFlagSet p_341602_, boolean p_341662_ ++ ) { ++ return configureRepositoryWithSelection(p_341680_, p_341677_, p_341602_, it.unimi.dsi.fastutil.objects.ReferenceSets.emptySet(), p_341662_); + } + + private static void enableForcedFeaturePacks(PackRepository p_341674_, FeatureFlagSet p_341598_) { @@ -1712,6 +_,31 @@ public abstract boolean isSingleplayerOwner(GameProfile p_129840_); @@ -275,14 +341,25 @@ public void dumpServerProperties(Path p_177911_) throws IOException { } -@@ -1873,6 +_,10 @@ - - public WorldData getWorldData() { +@@ -1875,6 +_,10 @@ return this.worldData; -+ } -+ + } + + public MinecraftServer.ReloadableResources getServerResources() { + return resources; ++ } ++ + public RegistryAccess.Frozen registryAccess() { + return this.registries.compositeAccess(); } +@@ -1999,6 +_,10 @@ - public RegistryAccess.Frozen registryAccess() { + public ServerLinks serverLinks() { + return ServerLinks.EMPTY; ++ } ++ ++ public net.neoforged.neoforge.flag.FlagManager getModdedFlagManager() { ++ return moddedFlagManager; + } + + public static record ReloadableResources(CloseableResourceManager resourceManager, ReloadableServerResources managers) implements AutoCloseable { diff --git a/patches/net/minecraft/server/ReloadableServerResources.java.patch b/patches/net/minecraft/server/ReloadableServerResources.java.patch index cf23f14fbc..91beb89215 100644 --- a/patches/net/minecraft/server/ReloadableServerResources.java.patch +++ b/patches/net/minecraft/server/ReloadableServerResources.java.patch @@ -1,15 +1,32 @@ --- a/net/minecraft/server/ReloadableServerResources.java +++ b/net/minecraft/server/ReloadableServerResources.java -@@ -49,6 +_,8 @@ +@@ -40,7 +_,7 @@ + private final ServerAdvancementManager advancements; + private final ServerFunctionLibrary functionLibrary; + +- private ReloadableServerResources(RegistryAccess.Frozen p_206857_, FeatureFlagSet p_250695_, Commands.CommandSelection p_206858_, int p_206859_) { ++ private ReloadableServerResources(RegistryAccess.Frozen p_206857_, FeatureFlagSet p_250695_, net.neoforged.neoforge.flag.FlagManager moddedFlagManager, Commands.CommandSelection p_206858_, int p_206859_) { + this.fullRegistryHolder = new ReloadableServerRegistries.Holder(p_206857_); + this.registryLookup = new ReloadableServerResources.ConfigurableRegistryLookup(p_206857_); + this.registryLookup.missingTagAccessPolicy(ReloadableServerResources.MissingTagAccessPolicy.CREATE_NEW); +@@ -49,6 +_,16 @@ this.commands = new Commands(p_206858_, CommandBuildContext.simple(this.registryLookup, p_250695_)); this.advancements = new ServerAdvancementManager(this.registryLookup); this.functionLibrary = new ServerFunctionLibrary(p_206859_, this.commands.getDispatcher()); + // Neo: Create context object -+ this.context = new net.neoforged.neoforge.common.conditions.ConditionContext(this.tagManager); ++ this.context = new net.neoforged.neoforge.common.conditions.ConditionContext(this.tagManager, moddedFlagManager); ++ } ++ ++ /** ++ * Prefer {@link #ReloadableServerResources(RegistryAccess.Frozen, FeatureFlagSet, net.neoforged.neoforge.flag.FlagManager, Commands.CommandSelection, int)} ++ */ ++ @Deprecated ++ private ReloadableServerResources(RegistryAccess.Frozen p_206857_, FeatureFlagSet p_250695_, Commands.CommandSelection p_206858_, int p_206859_) { ++ this(p_206857_, p_250695_, net.neoforged.neoforge.flag.FlagManager.EMPTY, p_206858_, p_206859_); } public ServerFunctionLibrary getFunctionLibrary() { -@@ -75,6 +_,24 @@ +@@ -75,10 +_,29 @@ return List.of(this.tagManager, this.recipes, this.functionLibrary, this.advancements); } @@ -34,9 +51,17 @@ public static CompletableFuture loadResources( ResourceManager p_248588_, LayeredRegistryAccess p_335667_, -@@ -90,14 +_,27 @@ + FeatureFlagSet p_250212_, ++ net.neoforged.neoforge.flag.FlagManager moddedFlagManager, + Commands.CommandSelection p_249301_, + int p_251126_, + Executor p_249136_, +@@ -88,25 +_,55 @@ + .thenCompose( + p_335211_ -> { ReloadableServerResources reloadableserverresources = new ReloadableServerResources( - p_335211_.compositeAccess(), p_250212_, p_249301_, p_251126_ +- p_335211_.compositeAccess(), p_250212_, p_249301_, p_251126_ ++ p_335211_.compositeAccess(), p_250212_, moddedFlagManager, p_249301_, p_251126_ ); + List listeners = new java.util.ArrayList<>(reloadableserverresources.listeners()); + listeners.addAll(net.neoforged.neoforge.event.EventHooks.onResourceReload(reloadableserverresources, p_335211_.compositeAccess())); @@ -63,7 +88,25 @@ .thenApply(p_214306_ -> reloadableserverresources); } ); -@@ -107,6 +_,7 @@ + } + ++ /** ++ * @deprecated Prefer to use {@link #loadResources(ResourceManager, LayeredRegistryAccess, FeatureFlagSet, net.neoforged.neoforge.flag.FlagManager, Commands.CommandSelection, int, Executor, Executor)}. ++ */ ++ @Deprecated ++ public static CompletableFuture loadResources( ++ ResourceManager p_248588_, ++ LayeredRegistryAccess p_335667_, ++ FeatureFlagSet p_250212_, ++ Commands.CommandSelection p_249301_, ++ int p_251126_, ++ Executor p_249136_, ++ Executor p_249601_ ++ ) { ++ return loadResources(p_248588_, p_335667_, p_250212_, net.neoforged.neoforge.flag.FlagManager.EMPTY, p_249301_, p_251126_, p_249136_, p_249601_); ++ } ++ + public void updateRegistryTags() { this.tagManager.getResult().forEach(p_335204_ -> updateRegistryTags(this.fullRegistryHolder.get(), (TagManager.LoadResult)p_335204_)); AbstractFurnaceBlockEntity.invalidateCache(); Blocks.rebuildCache(); diff --git a/patches/net/minecraft/server/WorldLoader.java.patch b/patches/net/minecraft/server/WorldLoader.java.patch index 67180986ab..8b24d4ce17 100644 --- a/patches/net/minecraft/server/WorldLoader.java.patch +++ b/patches/net/minecraft/server/WorldLoader.java.patch @@ -9,3 +9,11 @@ ); RegistryAccess.Frozen registryaccess$frozen = layeredregistryaccess1.getAccessForLoading(RegistryLayer.DIMENSIONS); RegistryAccess.Frozen registryaccess$frozen1 = RegistryDataLoader.load( +@@ -50,6 +_,7 @@ + closeableresourcemanager, + layeredregistryaccess2, + worlddataconfiguration.enabledFeatures(), ++ net.neoforged.neoforge.flag.FlagManager.createImmutable(worlddataconfiguration.enabledModdedFeatures()), + p_214363_.commandSelection(), + p_214363_.functionCompilationLevel(), + p_214366_, diff --git a/patches/net/minecraft/server/level/ServerLevel.java.patch b/patches/net/minecraft/server/level/ServerLevel.java.patch index 411052eadc..ccbb006740 100644 --- a/patches/net/minecraft/server/level/ServerLevel.java.patch +++ b/patches/net/minecraft/server/level/ServerLevel.java.patch @@ -214,7 +214,7 @@ ServerLevel.this.dragonParts.put(enderdragonpart.getId(), enderdragonpart); } } -@@ -1733,24 +_,106 @@ +@@ -1733,24 +_,110 @@ if (ServerLevel.this.isUpdatingNavigations) { String s = "onTrackingStart called during navigation iteration"; Util.logAndPauseIfInIde( @@ -243,7 +243,7 @@ public void onSectionChange(Entity p_215086_) { p_215086_.updateDynamicGameEventListener(DynamicGameEventListener::move); } - } ++ } + + @Override + public java.util.Collection> getPartEntities() { @@ -323,4 +323,8 @@ + } + } + ++ @Override ++ public net.neoforged.neoforge.flag.FlagManager getModdedFlagManager() { ++ return server.getModdedFlagManager(); + } } diff --git a/patches/net/minecraft/server/level/WorldGenRegion.java.patch b/patches/net/minecraft/server/level/WorldGenRegion.java.patch index 77dfc22633..707fb891f5 100644 --- a/patches/net/minecraft/server/level/WorldGenRegion.java.patch +++ b/patches/net/minecraft/server/level/WorldGenRegion.java.patch @@ -8,3 +8,14 @@ int i = SectionPos.blockToSectionCoord(p_9580_.getBlockX()); int j = SectionPos.blockToSectionCoord(p_9580_.getBlockZ()); this.getChunk(i, j).addEntity(p_9580_); +@@ -469,5 +_,10 @@ + @Override + public long nextSubTickCount() { + return this.subTickCount.getAndIncrement(); ++ } ++ ++ @Override ++ public net.neoforged.neoforge.flag.FlagManager getModdedFlagManager() { ++ return level.getModdedFlagManager(); + } + } diff --git a/patches/net/minecraft/world/effect/MobEffect.java.patch b/patches/net/minecraft/world/effect/MobEffect.java.patch index c8eef70796..aa7adc5505 100644 --- a/patches/net/minecraft/world/effect/MobEffect.java.patch +++ b/patches/net/minecraft/world/effect/MobEffect.java.patch @@ -9,6 +9,14 @@ public static final Codec> CODEC = BuiltInRegistries.MOB_EFFECT.holderByNameCodec(); public static final StreamCodec> STREAM_CODEC = ByteBufCodecs.holderRegistry(Registries.MOB_EFFECT); private static final int AMBIENT_ALPHA = Mth.floor(38.25F); +@@ -48,6 +_,7 @@ + private int blendDurationTicks; + private Optional soundOnAdded = Optional.empty(); + private FeatureFlagSet requiredFeatures = FeatureFlags.VANILLA_SET; ++ private it.unimi.dsi.fastutil.objects.ReferenceSet requiredFlags = it.unimi.dsi.fastutil.objects.ReferenceSets.emptySet(); + + protected MobEffect(MobEffectCategory p_19451_, int p_19452_) { + this.category = p_19451_; @@ -130,6 +_,18 @@ return this; } @@ -28,11 +36,35 @@ public MobEffect setBlendDuration(int p_316265_) { this.blendDurationTicks = p_316265_; return this; -@@ -181,8 +_,24 @@ +@@ -171,6 +_,10 @@ + return this; + } + ++ /** ++ * @deprecated Prefer {@linkplain #requiredFlags(net.neoforged.neoforge.flag.Flag...)} ++ */ ++ @Deprecated + public MobEffect requiredFeatures(FeatureFlag... p_338702_) { + this.requiredFeatures = FeatureFlags.REGISTRY.subset(p_338702_); + return this; +@@ -181,8 +_,37 @@ return this.requiredFeatures; } - static record AttributeTemplate(ResourceLocation id, double amount, AttributeModifier.Operation operation) { ++ public MobEffect requiredFlags(net.neoforged.neoforge.flag.Flag... requiredFlags) { ++ if(this.requiredFlags.isEmpty()) ++ this.requiredFlags = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); ++ ++ java.util.Collections.addAll(this.requiredFlags, requiredFlags); ++ return this; ++ } ++ ++ @Override ++ public it.unimi.dsi.fastutil.objects.ReferenceSet requiredFlags() { ++ return requiredFlags.isEmpty() ? it.unimi.dsi.fastutil.objects.ReferenceSets.emptySet() : it.unimi.dsi.fastutil.objects.ReferenceSets.unmodifiable(requiredFlags); ++ } ++ + /** + * Neo: Allowing mods to define client behavior for their MobEffects + * @deprecated Use {@link net.neoforged.neoforge.client.extensions.common.RegisterClientExtensionsEvent} instead diff --git a/patches/net/minecraft/world/entity/EntityType.java.patch b/patches/net/minecraft/world/entity/EntityType.java.patch index 1f3f299099..d53d6e7565 100644 --- a/patches/net/minecraft/world/entity/EntityType.java.patch +++ b/patches/net/minecraft/world/entity/EntityType.java.patch @@ -1,21 +1,44 @@ --- a/net/minecraft/world/entity/EntityType.java +++ b/net/minecraft/world/entity/EntityType.java -@@ -834,6 +_,10 @@ +@@ -834,6 +_,11 @@ private final float spawnDimensionsScale; private final FeatureFlagSet requiredFeatures; + private final java.util.function.Predicate> trackDeltasSupplier; + private final java.util.function.ToIntFunction> trackingRangeSupplier; + private final java.util.function.ToIntFunction> updateIntervalSupplier; ++ private final it.unimi.dsi.fastutil.objects.ReferenceSet requiredFlags; + private static EntityType register(String p_20635_, EntityType.Builder p_20636_) { return Registry.register(BuiltInRegistries.ENTITY_TYPE, p_20635_, p_20636_.build(p_20635_)); } -@@ -860,6 +_,26 @@ +@@ -860,6 +_,49 @@ int p_273451_, FeatureFlagSet p_273518_ ) { -+ this(p_273268_, p_272918_, p_273417_, p_273389_, p_273556_, p_272654_, p_273631_, p_272946_, p_338404_, p_272895_, p_273451_, p_273518_, EntityType::defaultTrackDeltasSupplier, EntityType::defaultTrackingRangeSupplier, EntityType::defaultUpdateIntervalSupplier); ++ this(p_273268_, p_272918_, p_273417_, p_273389_, p_273556_, p_272654_, p_273631_, p_272946_, p_338404_, p_272895_, p_273451_, p_273518_, EntityType::defaultTrackDeltasSupplier, EntityType::defaultTrackingRangeSupplier, EntityType::defaultUpdateIntervalSupplier, it.unimi.dsi.fastutil.objects.ReferenceSets.emptySet()); ++ } ++ ++ // TODO: Remove in 21.2 ++ @Deprecated(forRemoval = true, since = "21.1") ++ public EntityType( ++ EntityType.EntityFactory p_273268_, ++ MobCategory p_272918_, ++ boolean p_273417_, ++ boolean p_273389_, ++ boolean p_273556_, ++ boolean p_272654_, ++ ImmutableSet p_273631_, ++ EntityDimensions p_272946_, ++ float p_338404_, ++ int p_272895_, ++ int p_273451_, ++ FeatureFlagSet p_273518_, ++ final java.util.function.Predicate> trackDeltasSupplier, ++ final java.util.function.ToIntFunction> trackingRangeSupplier, ++ final java.util.function.ToIntFunction> updateIntervalSupplier ++ ) { ++ this(p_273268_, p_272918_, p_273417_, p_273389_, p_273556_, p_272654_, p_273631_, p_272946_, p_338404_, p_272895_, p_273451_, p_273518_, trackDeltasSupplier, trackingRangeSupplier, updateIntervalSupplier, it.unimi.dsi.fastutil.objects.ReferenceSets.emptySet()); + } + + public EntityType( @@ -33,18 +56,20 @@ + FeatureFlagSet p_273518_, + final java.util.function.Predicate> trackDeltasSupplier, + final java.util.function.ToIntFunction> trackingRangeSupplier, -+ final java.util.function.ToIntFunction> updateIntervalSupplier ++ final java.util.function.ToIntFunction> updateIntervalSupplier, ++ final it.unimi.dsi.fastutil.objects.ReferenceSet requiredFlags + ) { this.factory = p_273268_; this.category = p_272918_; this.canSpawnFarFromPlayer = p_272654_; -@@ -872,6 +_,9 @@ +@@ -872,6 +_,10 @@ this.clientTrackingRange = p_272895_; this.updateInterval = p_273451_; this.requiredFeatures = p_273518_; + this.trackDeltasSupplier = trackDeltasSupplier; + this.trackingRangeSupplier = trackingRangeSupplier; + this.updateIntervalSupplier = updateIntervalSupplier; ++ this.requiredFlags = requiredFlags.isEmpty() ? it.unimi.dsi.fastutil.objects.ReferenceSets.emptySet() : it.unimi.dsi.fastutil.objects.ReferenceSets.unmodifiable(requiredFlags); } @Nullable @@ -88,30 +113,53 @@ return this != PLAYER && this != LLAMA_SPIT && this != WITHER -@@ -1192,6 +_,8 @@ +@@ -1192,6 +_,13 @@ return this.builtInRegistryHolder; } + public Stream>> getTags() {return this.builtInRegistryHolder().tags();} ++ ++ @Override ++ public final it.unimi.dsi.fastutil.objects.ReferenceSet requiredFlags() { ++ return requiredFlags; ++ } + public static class Builder { private final EntityType.EntityFactory factory; private final MobCategory category; -@@ -1207,6 +_,10 @@ +@@ -1207,6 +_,11 @@ private EntityAttachments.Builder attachments = EntityAttachments.builder(); private FeatureFlagSet requiredFeatures = FeatureFlags.VANILLA_SET; + private java.util.function.Predicate> velocityUpdateSupplier = EntityType::defaultTrackDeltasSupplier; + private java.util.function.ToIntFunction> trackingRangeSupplier = EntityType::defaultTrackingRangeSupplier; + private java.util.function.ToIntFunction> updateIntervalSupplier = EntityType::defaultUpdateIntervalSupplier; ++ private it.unimi.dsi.fastutil.objects.ReferenceSet requiredFlags = it.unimi.dsi.fastutil.objects.ReferenceSets.emptySet(); + private Builder(EntityType.EntityFactory p_20696_, MobCategory p_20697_) { this.factory = p_20696_; this.category = p_20697_; -@@ -1314,6 +_,21 @@ +@@ -1309,11 +_,38 @@ return this; } ++ /** ++ * @deprecated Prefer {@linkplain #requiredFlags(net.neoforged.neoforge.flag.Flag...)} ++ */ ++ @Deprecated + public EntityType.Builder requiredFeatures(FeatureFlag... p_251646_) { + this.requiredFeatures = FeatureFlags.REGISTRY.subset(p_251646_); + return this; + } + ++ public EntityType.Builder requiredFlags(net.neoforged.neoforge.flag.Flag... requiredFlags) { ++ if(this.requiredFlags.isEmpty()) ++ this.requiredFlags = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); ++ ++ java.util.Collections.addAll(this.requiredFlags, requiredFlags); ++ return this; ++ } ++ + public EntityType.Builder setUpdateInterval(int interval) { + this.updateIntervalSupplier = t->interval; + return this; @@ -130,7 +178,7 @@ public EntityType build(String p_20713_) { if (this.serialize) { Util.fetchChoiceType(References.ENTITY_TREE, p_20713_); -@@ -1331,7 +_,10 @@ +@@ -1331,7 +_,11 @@ this.spawnDimensionsScale, this.clientTrackingRange, this.updateInterval, @@ -138,7 +186,8 @@ + this.requiredFeatures, + velocityUpdateSupplier, + trackingRangeSupplier, -+ updateIntervalSupplier ++ updateIntervalSupplier, ++ this.requiredFlags ); } } diff --git a/patches/net/minecraft/world/flag/FeatureElement.java.patch b/patches/net/minecraft/world/flag/FeatureElement.java.patch new file mode 100644 index 0000000000..cd78cc83d8 --- /dev/null +++ b/patches/net/minecraft/world/flag/FeatureElement.java.patch @@ -0,0 +1,19 @@ +--- a/net/minecraft/world/flag/FeatureElement.java ++++ b/net/minecraft/world/flag/FeatureElement.java +@@ -5,7 +_,7 @@ + import net.minecraft.core.registries.Registries; + import net.minecraft.resources.ResourceKey; + +-public interface FeatureElement { ++public interface FeatureElement extends net.neoforged.neoforge.flag.FlagElement { + Set>> FILTERED_REGISTRIES = Set.of( + Registries.ITEM, Registries.BLOCK, Registries.ENTITY_TYPE, Registries.MENU, Registries.POTION, Registries.MOB_EFFECT + ); +@@ -13,6 +_,6 @@ + FeatureFlagSet requiredFeatures(); + + default boolean isEnabled(FeatureFlagSet p_249172_) { +- return this.requiredFeatures().isSubsetOf(p_249172_); ++ return this.requiredFeatures().isSubsetOf(p_249172_) && isEnabled(); + } + } diff --git a/patches/net/minecraft/world/inventory/MenuType.java.patch b/patches/net/minecraft/world/inventory/MenuType.java.patch index 7d0eaf4e2d..651bd7bcdb 100644 --- a/patches/net/minecraft/world/inventory/MenuType.java.patch +++ b/patches/net/minecraft/world/inventory/MenuType.java.patch @@ -9,18 +9,47 @@ public static final MenuType GENERIC_9x1 = register("generic_9x1", ChestMenu::oneRow); public static final MenuType GENERIC_9x2 = register("generic_9x2", ChestMenu::twoRows); public static final MenuType GENERIC_9x3 = register("generic_9x3", ChestMenu::threeRows); -@@ -52,6 +_,14 @@ +@@ -36,6 +_,7 @@ + public static final MenuType STONECUTTER = register("stonecutter", StonecutterMenu::new); + private final FeatureFlagSet requiredFeatures; + private final MenuType.MenuSupplier constructor; ++ private final it.unimi.dsi.fastutil.objects.ReferenceSet requiredFlags; - public T create(int p_39986_, Inventory p_39987_) { - return this.constructor.create(p_39986_, p_39987_); + private static MenuType register(String p_39989_, MenuType.MenuSupplier p_39990_) { + return Registry.register(BuiltInRegistries.MENU, p_39989_, new MenuType<>(p_39990_, FeatureFlags.VANILLA_SET)); +@@ -48,6 +_,13 @@ + public MenuType(MenuType.MenuSupplier p_267054_, FeatureFlagSet p_266909_) { + this.constructor = p_267054_; + this.requiredFeatures = p_266909_; ++ this.requiredFlags = it.unimi.dsi.fastutil.objects.ReferenceSets.emptySet(); + } + -+ @Override ++ public MenuType(MenuType.MenuSupplier p_267054_, FeatureFlagSet p_266909_, net.neoforged.neoforge.flag.Flag[] requiredFlags) { ++ this.constructor = p_267054_; ++ this.requiredFeatures = p_266909_; ++ this.requiredFlags = it.unimi.dsi.fastutil.objects.ReferenceSet.of(requiredFlags); + } + + public T create(int p_39986_, Inventory p_39987_) { +@@ -55,8 +_,21 @@ + } + + @Override + public T create(int windowId, Inventory playerInv, net.minecraft.network.RegistryFriendlyByteBuf extraData) { + if (this.constructor instanceof net.neoforged.neoforge.network.IContainerFactory) { + return ((net.neoforged.neoforge.network.IContainerFactory) this.constructor).create(windowId, playerInv, extraData); + } + return create(windowId, playerInv); ++ } ++ ++ @Override + public FeatureFlagSet requiredFeatures() { + return this.requiredFeatures; ++ } ++ ++ @Override ++ public final it.unimi.dsi.fastutil.objects.ReferenceSet requiredFlags() { ++ return requiredFlags; } - @Override + public interface MenuSupplier { diff --git a/patches/net/minecraft/world/item/BlockItem.java.patch b/patches/net/minecraft/world/item/BlockItem.java.patch index eed7cf8eb1..81fe9a22fe 100644 --- a/patches/net/minecraft/world/item/BlockItem.java.patch +++ b/patches/net/minecraft/world/item/BlockItem.java.patch @@ -31,16 +31,27 @@ @Nullable public BlockPlaceContext updatePlacementContext(BlockPlaceContext p_40609_) { return p_40609_; -@@ -193,6 +_,12 @@ - - public void registerBlocks(Map p_40607_, Item p_40608_) { +@@ -195,6 +_,12 @@ p_40607_.put(this.getBlock(), p_40608_); -+ } -+ + } + + /** @deprecated Neo: To be removed without replacement since registry replacement is not a feature anymore. */ + @Deprecated(forRemoval = true, since = "1.21.1") + public void removeFromBlockToItemMap(Map blockToItemMap, Item itemIn) { + blockToItemMap.remove(this.getBlock()); - } - ++ } ++ + @Override + public boolean canFitInsideContainerItems() { + return !(this.getBlock() instanceof ShulkerBoxBlock); +@@ -221,5 +_,10 @@ @Override + public FeatureFlagSet requiredFeatures() { + return this.getBlock().requiredFeatures(); ++ } ++ ++ @Override ++ public boolean isEnabled() { ++ return getBlock().isEnabled() && super.isEnabled(); + } + } diff --git a/patches/net/minecraft/world/item/Item.java.patch b/patches/net/minecraft/world/item/Item.java.patch index 4054c635cd..37ed92658e 100644 --- a/patches/net/minecraft/world/item/Item.java.patch +++ b/patches/net/minecraft/world/item/Item.java.patch @@ -12,11 +12,20 @@ public static final ResourceLocation BASE_ATTACK_DAMAGE_ID = ResourceLocation.withDefaultNamespace("base_attack_damage"); public static final ResourceLocation BASE_ATTACK_SPEED_ID = ResourceLocation.withDefaultNamespace("base_attack_speed"); public static final int DEFAULT_MAX_STACK_SIZE = 64; -@@ -89,12 +_,13 @@ +@@ -71,6 +_,7 @@ + @Nullable + private String descriptionId; + private final FeatureFlagSet requiredFeatures; ++ private final it.unimi.dsi.fastutil.objects.ReferenceSet requiredFlags; + + public static int getId(Item p_41394_) { + return p_41394_ == null ? 0 : BuiltInRegistries.ITEM.getId(p_41394_); +@@ -89,12 +_,14 @@ this.components = p_41383_.buildAndValidateComponents(); this.craftingRemainingItem = p_41383_.craftingRemainingItem; this.requiredFeatures = p_41383_.requiredFeatures; - if (SharedConstants.IS_RUNNING_IN_IDE) { ++ this.requiredFlags = p_41383_.requiredFlags.isEmpty() ? it.unimi.dsi.fastutil.objects.ReferenceSets.emptySet() : it.unimi.dsi.fastutil.objects.ReferenceSets.unmodifiable(p_41383_.requiredFlags); + if (SharedConstants.IS_RUNNING_IN_IDE && false) { String s = this.getClass().getSimpleName(); if (!s.endsWith("Item")) { @@ -142,11 +151,16 @@ } public ItemStack getDefaultInstance() { -@@ -341,13 +_,22 @@ +@@ -341,13 +_,28 @@ return this.requiredFeatures; } - public static class Properties { ++ @Override ++ public final it.unimi.dsi.fastutil.objects.ReferenceSet requiredFlags() { ++ return requiredFlags; ++ } ++ + /** + * Neo: Allowing mods to define client behavior for their Items + * @deprecated Use {@link net.neoforged.neoforge.client.extensions.common.RegisterClientExtensionsEvent} instead @@ -163,10 +177,11 @@ Item craftingRemainingItem; FeatureFlagSet requiredFeatures = FeatureFlags.VANILLA_SET; + private boolean canRepair = true; ++ private it.unimi.dsi.fastutil.objects.ReferenceSet requiredFlags = it.unimi.dsi.fastutil.objects.ReferenceSets.emptySet(); public Item.Properties food(FoodProperties p_41490_) { return this.component(DataComponents.FOOD, p_41490_); -@@ -381,12 +_,18 @@ +@@ -381,12 +_,30 @@ return this.component(DataComponents.JUKEBOX_PLAYABLE, new JukeboxPlayable(new EitherHolder<>(p_350862_), true)); } @@ -175,11 +190,23 @@ + return this; + } + ++ /** ++ * @deprecated Prefer {@linkplain #requiredFlags(net.neoforged.neoforge.flag.Flag...)} ++ */ ++ @Deprecated public Item.Properties requiredFeatures(FeatureFlag... p_250948_) { this.requiredFeatures = FeatureFlags.REGISTRY.subset(p_250948_); return this; } ++ public Item.Properties requiredFlags(net.neoforged.neoforge.flag.Flag... requiredFlags) { ++ if(this.requiredFlags.isEmpty()) ++ this.requiredFlags = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); ++ ++ java.util.Collections.addAll(this.requiredFlags, requiredFlags); ++ return this; ++ } ++ public Item.Properties component(DataComponentType p_330871_, T p_330323_) { + net.neoforged.neoforge.common.CommonHooks.validateComponent(p_330323_); if (this.components == null) { diff --git a/patches/net/minecraft/world/item/SpawnEggItem.java.patch b/patches/net/minecraft/world/item/SpawnEggItem.java.patch index 039545cc2e..c5df7f031a 100644 --- a/patches/net/minecraft/world/item/SpawnEggItem.java.patch +++ b/patches/net/minecraft/world/item/SpawnEggItem.java.patch @@ -39,7 +39,7 @@ } public Optional spawnOffspringFromSpawnEgg( -@@ -179,5 +_,9 @@ +@@ -179,5 +_,14 @@ } } } @@ -47,5 +47,10 @@ + + protected EntityType getDefaultType() { + return defaultType; ++ } ++ ++ @Override ++ public boolean isEnabled() { ++ return getDefaultType().isEnabled() && super.isEnabled(); } } diff --git a/patches/net/minecraft/world/item/alchemy/Potion.java.patch b/patches/net/minecraft/world/item/alchemy/Potion.java.patch new file mode 100644 index 0000000000..5e9e47be2b --- /dev/null +++ b/patches/net/minecraft/world/item/alchemy/Potion.java.patch @@ -0,0 +1,41 @@ +--- a/net/minecraft/world/item/alchemy/Potion.java ++++ b/net/minecraft/world/item/alchemy/Potion.java +@@ -24,6 +_,7 @@ + private final String name; + private final List effects; + private FeatureFlagSet requiredFeatures = FeatureFlags.VANILLA_SET; ++ private it.unimi.dsi.fastutil.objects.ReferenceSet requiredFlags = it.unimi.dsi.fastutil.objects.ReferenceSets.emptySet(); + + public Potion(MobEffectInstance... p_43487_) { + this(null, p_43487_); +@@ -34,6 +_,10 @@ + this.effects = List.of(p_43485_); + } + ++ /** ++ * @deprecated Prefer {@linkplain #requiredFlags(net.neoforged.neoforge.flag.Flag...)} ++ */ ++ @Deprecated + public Potion requiredFeatures(FeatureFlag... p_338520_) { + this.requiredFeatures = FeatureFlags.REGISTRY.subset(p_338520_); + return this; +@@ -42,6 +_,19 @@ + @Override + public FeatureFlagSet requiredFeatures() { + return this.requiredFeatures; ++ } ++ ++ public Potion requiredFlags(net.neoforged.neoforge.flag.Flag... requiredFlags) { ++ if(this.requiredFlags.isEmpty()) ++ this.requiredFlags = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); ++ ++ java.util.Collections.addAll(this.requiredFlags, requiredFlags); ++ return this; ++ } ++ ++ @Override ++ public it.unimi.dsi.fastutil.objects.ReferenceSet requiredFlags() { ++ return requiredFlags.isEmpty() ? it.unimi.dsi.fastutil.objects.ReferenceSets.emptySet() : it.unimi.dsi.fastutil.objects.ReferenceSets.unmodifiable(requiredFlags); + } + + public static String getName(Optional> p_330503_, String p_43493_) { diff --git a/patches/net/minecraft/world/level/WorldDataConfiguration.java.patch b/patches/net/minecraft/world/level/WorldDataConfiguration.java.patch new file mode 100644 index 0000000000..06a2b328e2 --- /dev/null +++ b/patches/net/minecraft/world/level/WorldDataConfiguration.java.patch @@ -0,0 +1,37 @@ +--- a/net/minecraft/world/level/WorldDataConfiguration.java ++++ b/net/minecraft/world/level/WorldDataConfiguration.java +@@ -6,20 +_,31 @@ + import net.minecraft.world.flag.FeatureFlagSet; + import net.minecraft.world.flag.FeatureFlags; + +-public record WorldDataConfiguration(DataPackConfig dataPacks, FeatureFlagSet enabledFeatures) { ++public record WorldDataConfiguration(DataPackConfig dataPacks, FeatureFlagSet enabledFeatures, it.unimi.dsi.fastutil.objects.ReferenceSet enabledModdedFeatures) { + public static final String ENABLED_FEATURES_ID = "enabled_features"; + public static final Codec CODEC = RecordCodecBuilder.create( + p_337972_ -> p_337972_.group( + DataPackConfig.CODEC.lenientOptionalFieldOf("DataPacks", DataPackConfig.DEFAULT).forGetter(WorldDataConfiguration::dataPacks), + FeatureFlags.CODEC + .lenientOptionalFieldOf("enabled_features", FeatureFlags.DEFAULT_FLAGS) +- .forGetter(WorldDataConfiguration::enabledFeatures) ++ .forGetter(WorldDataConfiguration::enabledFeatures), ++ net.neoforged.neoforge.flag.Flag.SET_CODEC ++ .lenientOptionalFieldOf("enabled_modded_features", it.unimi.dsi.fastutil.objects.ReferenceSets.emptySet()) ++ .forGetter(WorldDataConfiguration::enabledModdedFeatures) + ) + .apply(p_337972_, WorldDataConfiguration::new) + ); + public static final WorldDataConfiguration DEFAULT = new WorldDataConfiguration(DataPackConfig.DEFAULT, FeatureFlags.DEFAULT_FLAGS); + + public WorldDataConfiguration expandFeatures(FeatureFlagSet p_249090_) { +- return new WorldDataConfiguration(this.dataPacks, this.enabledFeatures.join(p_249090_)); ++ return new WorldDataConfiguration(this.dataPacks, this.enabledFeatures.join(p_249090_), this.enabledModdedFeatures); ++ } ++ ++ /** ++ * @deprecated Prefer to use {@link #WorldDataConfiguration(DataPackConfig, FeatureFlagSet, it.unimi.dsi.fastutil.objects.ReferenceSet)} instead. ++ */ ++ @Deprecated ++ public WorldDataConfiguration(DataPackConfig dataPacks, FeatureFlagSet enabledFeatures) { ++ this(dataPacks, enabledFeatures, it.unimi.dsi.fastutil.objects.ReferenceSets.emptySet()); + } + } diff --git a/patches/net/minecraft/world/level/block/state/BlockBehaviour.java.patch b/patches/net/minecraft/world/level/block/state/BlockBehaviour.java.patch index 851387e29d..594c5e57af 100644 --- a/patches/net/minecraft/world/level/block/state/BlockBehaviour.java.patch +++ b/patches/net/minecraft/world/level/block/state/BlockBehaviour.java.patch @@ -1,8 +1,10 @@ --- a/net/minecraft/world/level/block/state/BlockBehaviour.java +++ b/net/minecraft/world/level/block/state/BlockBehaviour.java -@@ -111,6 +_,17 @@ +@@ -110,7 +_,19 @@ + this.jumpFactor = p_60452_.jumpFactor; this.dynamicShape = p_60452_.dynamicShape; this.requiredFeatures = p_60452_.requiredFeatures; ++ p_60452_.requiredFlags = p_60452_.requiredFlags.isEmpty() ? it.unimi.dsi.fastutil.objects.ReferenceSets.emptySet() : it.unimi.dsi.fastutil.objects.ReferenceSets.unmodifiable(p_60452_.requiredFlags); this.properties = p_60452_; + final ResourceKey lootTableCache = p_60452_.drops; + if (lootTableCache != null) { @@ -66,7 +68,7 @@ protected SoundType getSoundType(BlockState p_320941_) { return this.soundType; } -@@ -393,6 +_,13 @@ +@@ -393,6 +_,18 @@ return this.properties.destroyTime; } @@ -74,6 +76,11 @@ + return ((BlockStateBase)state).isAir; + } + ++ @Override ++ public final it.unimi.dsi.fastutil.objects.ReferenceSet requiredFlags() { ++ return properties.requiredFlags; ++ } ++ + // Neo: Holds the loot table for this block's drops. Used for getLootTable method. + private final java.util.function.Supplier> lootTableSupplier; + @@ -150,6 +157,27 @@ BlockBehaviour.StatePredicate isRedstoneConductor = (p_284888_, p_284889_, p_284890_) -> p_284888_.isCollisionShapeFullBlock(p_284889_, p_284890_); BlockBehaviour.StatePredicate isSuffocating = (p_284885_, p_284886_, p_284887_) -> p_284885_.blocksMotion() && p_284885_.isCollisionShapeFullBlock(p_284886_, p_284887_); +@@ -974,6 +_,7 @@ + FeatureFlagSet requiredFeatures = FeatureFlags.VANILLA_SET; + @Nullable + BlockBehaviour.OffsetFunction offsetFunction; ++ private it.unimi.dsi.fastutil.objects.ReferenceSet requiredFlags = it.unimi.dsi.fastutil.objects.ReferenceSets.emptySet(); + + private Properties() { + } +@@ -1020,6 +_,12 @@ + blockbehaviour$properties.offsetFunction = blockbehaviour$properties1.offsetFunction; + blockbehaviour$properties.spawnTerrainParticles = blockbehaviour$properties1.spawnTerrainParticles; + blockbehaviour$properties.requiredFeatures = blockbehaviour$properties1.requiredFeatures; ++ ++ if(!blockbehaviour$properties1.requiredFlags.isEmpty()) { ++ blockbehaviour$properties.requiredFlags = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); ++ blockbehaviour$properties.requiredFlags.addAll(blockbehaviour$properties1.requiredFlags); ++ } ++ + blockbehaviour$properties.emissiveRendering = blockbehaviour$properties1.emissiveRendering; + blockbehaviour$properties.instrument = blockbehaviour$properties1.instrument; + blockbehaviour$properties.replaceable = blockbehaviour$properties1.replaceable; @@ -1105,9 +_,15 @@ return this; } @@ -167,3 +195,24 @@ } public BlockBehaviour.Properties ignitedByLava() { +@@ -1215,8 +_,20 @@ + return this; + } + ++ /** ++ * @deprecated Prefer {@linkplain #requiredFlags(net.neoforged.neoforge.flag.Flag...)}. ++ */ ++ @Deprecated + public BlockBehaviour.Properties requiredFeatures(FeatureFlag... p_248792_) { + this.requiredFeatures = FeatureFlags.REGISTRY.subset(p_248792_); ++ return this; ++ } ++ ++ public BlockBehaviour.Properties requiredFlags(net.neoforged.neoforge.flag.Flag... requiredFlags) { ++ if(this.requiredFlags.isEmpty()) ++ this.requiredFlags = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); ++ ++ java.util.Collections.addAll(this.requiredFlags, requiredFlags); + return this; + } + diff --git a/patches/net/minecraft/world/level/storage/LevelStorageSource.java.patch b/patches/net/minecraft/world/level/storage/LevelStorageSource.java.patch index f119c517b6..183fdb11e5 100644 --- a/patches/net/minecraft/world/level/storage/LevelStorageSource.java.patch +++ b/patches/net/minecraft/world/level/storage/LevelStorageSource.java.patch @@ -1,5 +1,15 @@ --- a/net/minecraft/world/level/storage/LevelStorageSource.java +++ b/net/minecraft/world/level/storage/LevelStorageSource.java +@@ -287,7 +_,8 @@ + WorldDataConfiguration worlddataconfiguration = readDataConfig(p_307300_); + LevelSettings levelsettings = LevelSettings.parse(p_307300_, worlddataconfiguration); + FeatureFlagSet featureflagset = parseFeatureFlagsFromSummary(p_307300_); +- boolean flag1 = FeatureFlags.isExperimental(featureflagset); ++ // Neo: vanilla experimental flag present or modded flag present ++ boolean flag1 = FeatureFlags.isExperimental(featureflagset) || !worlddataconfiguration.enabledModdedFeatures().isEmpty(); + return new LevelSummary(levelsettings, levelversion, p_307426_.directoryName(), flag, p_307364_, flag1, path); + } + } @@ -463,6 +_,18 @@ } } diff --git a/src/main/java/net/neoforged/neoforge/common/NeoForgeEventHandler.java b/src/main/java/net/neoforged/neoforge/common/NeoForgeEventHandler.java index 2e2e14ab5f..db66a52a09 100644 --- a/src/main/java/net/neoforged/neoforge/common/NeoForgeEventHandler.java +++ b/src/main/java/net/neoforged/neoforge/common/NeoForgeEventHandler.java @@ -35,6 +35,7 @@ import net.neoforged.neoforge.event.level.ChunkEvent; import net.neoforged.neoforge.event.level.LevelEvent; import net.neoforged.neoforge.event.tick.ServerTickEvent; +import net.neoforged.neoforge.flag.ClientboundSyncFlags; import net.neoforged.neoforge.network.PacketDistributor; import net.neoforged.neoforge.network.payload.RegistryDataMapSyncPayload; import net.neoforged.neoforge.registries.DataMapLoader; @@ -97,6 +98,11 @@ public void playerChangeDimension(PlayerEvent.PlayerChangedDimensionEvent event) @SubscribeEvent public void playerLogin(PlayerEvent.PlayerLoggedInEvent event) { UsernameCache.setUsername(event.getEntity().getUUID(), event.getEntity().getGameProfile().getName()); + + if (event.getEntity() instanceof ServerPlayer sPlayer) { + var flagManager = sPlayer.server.getModdedFlagManager(); + PacketDistributor.sendToPlayer(sPlayer, new ClientboundSyncFlags(flagManager.getEnabledFlags())); + } } @SubscribeEvent diff --git a/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java b/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java index 3c0ca35bb5..63e56456dc 100644 --- a/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java +++ b/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java @@ -136,6 +136,8 @@ import net.neoforged.neoforge.common.world.StructureModifiers; import net.neoforged.neoforge.data.event.GatherDataEvent; import net.neoforged.neoforge.event.server.ServerStoppingEvent; +import net.neoforged.neoforge.flag.Flag; +import net.neoforged.neoforge.flag.RequiredFlagsCondition; import net.neoforged.neoforge.fluids.BaseFlowingFluid; import net.neoforged.neoforge.fluids.CauldronFluidContent; import net.neoforged.neoforge.fluids.FluidType; @@ -382,6 +384,10 @@ public class NeoForgeMod { public static final DeferredHolder, MapCodec> OR_CONDITION = CONDITION_CODECS.register("or", () -> OrCondition.CODEC); public static final DeferredHolder, MapCodec> TAG_EMPTY_CONDITION = CONDITION_CODECS.register("tag_empty", () -> TagEmptyCondition.CODEC); public static final DeferredHolder, MapCodec> TRUE_CONDITION = CONDITION_CODECS.register("true", () -> TrueCondition.CODEC); + /** + * {@link ICondition Condition} used when testing data files against {@link Flag flags}. + */ + public static final DeferredHolder, MapCodec> REQUIRED_FLAGS_CONDITION = CONDITION_CODECS.register("required_flags", () -> RequiredFlagsCondition.CODEC); private static final DeferredRegister> ENTITY_PREDICATE_CODECS = DeferredRegister.create(Registries.ENTITY_SUB_PREDICATE_TYPE, NeoForgeVersion.MOD_ID); public static final DeferredHolder, MapCodec> PIGLIN_NEUTRAL_ARMOR_PREDICATE = ENTITY_PREDICATE_CODECS.register("piglin_neutral_armor", () -> PiglinNeutralArmorEntityPredicate.CODEC); diff --git a/src/main/java/net/neoforged/neoforge/common/conditions/ConditionContext.java b/src/main/java/net/neoforged/neoforge/common/conditions/ConditionContext.java index 186cbb6347..f8d544ff86 100644 --- a/src/main/java/net/neoforged/neoforge/common/conditions/ConditionContext.java +++ b/src/main/java/net/neoforged/neoforge/common/conditions/ConditionContext.java @@ -14,6 +14,7 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.TagManager; +import net.neoforged.neoforge.flag.FlagManager; import org.jetbrains.annotations.Nullable; public class ConditionContext implements ICondition.IContext { @@ -21,9 +22,17 @@ public class ConditionContext implements ICondition.IContext { @Nullable // TODO 1.20.5: Clear loaded tags after reloads complete. The context object may leak, but we still want to invalidate it. private Map, Map>>> loadedTags = null; + private final FlagManager flagManager; - public ConditionContext(TagManager tagManager) { + public ConditionContext(TagManager tagManager, FlagManager flagManager) { this.tagManager = tagManager; + this.flagManager = flagManager; + } + + // TODO: Remove in 21.2 + @Deprecated(forRemoval = true, since = "21.1") + public ConditionContext(TagManager tagManager) { + this(tagManager, FlagManager.EMPTY); } @SuppressWarnings({ "unchecked", "rawtypes" }) @@ -41,4 +50,9 @@ public Map>> getAllTags(ResourceKey Collection> getTag(TagKey key) { * Note that the map and the tags are unmodifiable. */ Map>> getAllTags(ResourceKey> registry); + + /** + * @return {@link FlagManager} instance used to lookup {@link Flag} states. + */ + default FlagManager getModdedFlagManager() { + return FlagManager.EMPTY; + } } } diff --git a/src/main/java/net/neoforged/neoforge/common/conditions/IConditionBuilder.java b/src/main/java/net/neoforged/neoforge/common/conditions/IConditionBuilder.java index 06836647a8..08bfe72e1c 100644 --- a/src/main/java/net/neoforged/neoforge/common/conditions/IConditionBuilder.java +++ b/src/main/java/net/neoforged/neoforge/common/conditions/IConditionBuilder.java @@ -8,6 +8,8 @@ import java.util.List; import net.minecraft.tags.TagKey; import net.minecraft.world.item.Item; +import net.neoforged.neoforge.flag.Flag; +import net.neoforged.neoforge.flag.RequiredFlagsCondition; public interface IConditionBuilder { default ICondition and(ICondition... values) { @@ -41,4 +43,8 @@ default ICondition modLoaded(String modid) { default ICondition tagEmpty(TagKey tag) { return new TagEmptyCondition(tag.location()); } + + default ICondition requiredFlags(Flag... requiredFlags) { + return new RequiredFlagsCondition(requiredFlags); + } } diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/ILevelReaderExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/ILevelReaderExtension.java index bd97ecced5..34e2356dff 100644 --- a/src/main/java/net/neoforged/neoforge/common/extensions/ILevelReaderExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/extensions/ILevelReaderExtension.java @@ -10,6 +10,8 @@ import net.minecraft.core.Holder; import net.minecraft.resources.ResourceKey; import net.minecraft.world.level.LevelReader; +import net.neoforged.neoforge.flag.Flag; +import net.neoforged.neoforge.flag.FlagManager; public interface ILevelReaderExtension { private LevelReader self() { @@ -37,4 +39,15 @@ default Holder holderOrThrow(ResourceKey key) { default Optional> holder(ResourceKey key) { return this.self().registryAccess().holder(key); } + + /** + * Returns a valid and synced {@link FlagManager} or empty of none can be obtained. + *

+ * Modders may use this for all their {@link Flag flag} state testing needs. + * + * @return Valid and synced {@link FlagManager} or empty. + */ + default FlagManager getModdedFlagManager() { + return FlagManager.EMPTY; + } } diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IMenuTypeExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IMenuTypeExtension.java index 38404b95cc..7ee7c19bc5 100644 --- a/src/main/java/net/neoforged/neoforge/common/extensions/IMenuTypeExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IMenuTypeExtension.java @@ -10,9 +10,14 @@ import net.minecraft.world.flag.FeatureFlags; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.MenuType; +import net.neoforged.neoforge.flag.Flag; import net.neoforged.neoforge.network.IContainerFactory; public interface IMenuTypeExtension { + static MenuType create(IContainerFactory factory, Flag... requiredFlags) { + return new MenuType<>(factory, FeatureFlags.DEFAULT_FLAGS, requiredFlags); + } + static MenuType create(IContainerFactory factory) { return new MenuType<>(factory, FeatureFlags.DEFAULT_FLAGS); } diff --git a/src/main/java/net/neoforged/neoforge/flag/ClientboundSyncFlags.java b/src/main/java/net/neoforged/neoforge/flag/ClientboundSyncFlags.java new file mode 100644 index 0000000000..71d409f283 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/flag/ClientboundSyncFlags.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.flag; + +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import it.unimi.dsi.fastutil.objects.ReferenceSet; +import net.minecraft.client.Minecraft; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.fml.loading.FMLEnvironment; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import net.neoforged.neoforge.network.handling.IPayloadContext; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public final class ClientboundSyncFlags implements CustomPacketPayload { + public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(NeoForgeVersion.MOD_ID, "sync_flags")); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.collection(ReferenceOpenHashSet::new, Flag.STREAM_CODEC), payload -> payload.enabledFlags, + ClientboundSyncFlags::new); + + private final ReferenceSet enabledFlags; + + public ClientboundSyncFlags(ReferenceSet enabledFlags) { + this.enabledFlags = enabledFlags; + } + + public void handle(IPayloadContext context) { + context.enqueueWork(() -> { + if (!FMLEnvironment.dist.isClient()) + return; + + var client = Minecraft.getInstance(); + + if (client.getSingleplayerServer() == null) + client.clientModdedFlagManager = FlagManager.createImmutable(enabledFlags); + }); + } + + @Override + public Type type() { + return TYPE; + } +} diff --git a/src/main/java/net/neoforged/neoforge/flag/EmptyFlagManager.java b/src/main/java/net/neoforged/neoforge/flag/EmptyFlagManager.java new file mode 100644 index 0000000000..30000bdc6c --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/flag/EmptyFlagManager.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.flag; + +import it.unimi.dsi.fastutil.objects.ReferenceSet; +import it.unimi.dsi.fastutil.objects.ReferenceSets; + +final class EmptyFlagManager implements FlagManager { + @Override + public boolean set(Flag flag, boolean state) { + return false; + } + + @Override + public ReferenceSet getEnabledFlags() { + return ReferenceSets.emptySet(); + } +} diff --git a/src/main/java/net/neoforged/neoforge/flag/Flag.java b/src/main/java/net/neoforged/neoforge/flag/Flag.java new file mode 100644 index 0000000000..04868723e6 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/flag/Flag.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.flag; + +import com.google.common.collect.Maps; +import com.mojang.serialization.Codec; +import io.netty.buffer.ByteBuf; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import it.unimi.dsi.fastutil.objects.ReferenceSet; +import it.unimi.dsi.fastutil.objects.ReferenceSets; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; + +/** + * Class describing a specific flag. + */ +public final class Flag { + public static final Codec CODEC = ResourceLocation.CODEC.xmap(Flag::of, flag -> flag.identifier); + public static final Codec> SET_CODEC = CODEC.listOf().xmap( + flags -> ReferenceSets.unmodifiable(new ReferenceOpenHashSet<>(flags)), // there is no refset.copyOf must goto mutable then to immutable + List::copyOf); + public static final StreamCodec STREAM_CODEC = ResourceLocation.STREAM_CODEC.map(Flag::of, flag -> flag.identifier); + + private static final Map FLAGS = Maps.newConcurrentMap(); + private static final Collection FLAGS_VIEW = Collections.unmodifiableCollection(FLAGS.values()); + + private final ResourceLocation identifier; + + private Flag(ResourceLocation identifier) { + this.identifier = identifier; + } + + /** + * @return The owning mod id/namespace for this {@link Flag flag}. + */ + public String namespace() { + return identifier.getNamespace(); + } + + /** + * @return The unique identifier for this {@link Flag flag}. + * @apiNote This identifier does not need to be globally unique, only unique to the owning mod. + */ + public String identifier() { + return identifier.getPath(); + } + + public String toStringShort() { + return identifier.toString(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj.getClass() == Flag.class) + return identifier.equals(((Flag) obj).identifier); + return false; + } + + @Override + public int hashCode() { + return identifier.hashCode(); + } + + @Override + public String toString() { + return "Flag{" + identifier + '}'; + } + + /** + * Constructs a new {@link Flag} from the given {@code namespace} and {@code identifier}. + * + * @param namespace The owning mods id/namespace. + * @param identifier A mod specific unique identifier. + * @return The newly constructed flag. + */ + public static Flag of(String namespace, String identifier) { + return of(ResourceLocation.fromNamespaceAndPath(namespace, identifier)); + } + + /** + * Constructs a new {@link Flag} from the given {@code identifier}. + * + * @param identifier A mod specific unique identifier. + * @return The newly constructed flag. + */ + public static Flag of(ResourceLocation identifier) { + return FLAGS.computeIfAbsent(identifier, Flag::new); + } + + /** + * Returns immutable collection of all known {@link Flag flags}. + * + * @return immutable collection of all known {@link Flag flags}. + */ + public static ReferenceSet getFlags() { + return new ReferenceOpenHashSet<>(FLAGS_VIEW); + } + + /** + * Returns {@link Stream} representing all known {@link Flag flags}. + * + * @return {@link Stream} representing all known {@link Flag flags}. + */ + public static Stream flags() { + return FLAGS_VIEW.stream(); + } +} diff --git a/src/main/java/net/neoforged/neoforge/flag/FlagElement.java b/src/main/java/net/neoforged/neoforge/flag/FlagElement.java new file mode 100644 index 0000000000..7dd7ac7387 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/flag/FlagElement.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.flag; + +import com.google.errorprone.annotations.ForOverride; +import it.unimi.dsi.fastutil.objects.ReferenceSet; +import java.util.Set; +import net.minecraft.client.Minecraft; +import net.minecraft.world.flag.FeatureElement; +import net.neoforged.fml.loading.FMLEnvironment; +import net.neoforged.neoforge.data.loading.DatagenModLoader; +import net.neoforged.neoforge.server.ServerLifecycleHooks; +import org.jetbrains.annotations.ApiStatus; + +/** + * Interface describing an element whose state can be toggled with {@link Flag flags}. + * + * @apiNote All vanilla {@link FeatureElement FeatureElements} are pre patched to directly support the {@link Flag} system. + */ +public interface FlagElement { + /** + * @return Immutable {@link Set} containing all required {@link Flag Flags} for this element to be enabled. + */ + @ForOverride + ReferenceSet requiredFlags(); + + /** + * @return {@code true} if this {@link FlagElement element} should be enabled, {@code false} otherwise. + */ + @ApiStatus.NonExtendable + default boolean isEnabled() { + // all flags are enabled during data gen + if (DatagenModLoader.isRunningDataGen()) + return true; + + return activeFlagManager().isEnabled(requiredFlags()); + } + + private FlagManager activeFlagManager() { + // this exists purely so that we can call #isEnabled + // from within FeatureElement.isFeatureEnabled + // which is required to correctly hook into the vanilla system + // without requiring a ton of patches to invoke our isEnabled check along side vanillas + // and mods also needing to invoke ours alongside vanillas + var server = ServerLifecycleHooks.getCurrentServer(); + + if (server != null) + return server.getModdedFlagManager(); + if (FMLEnvironment.dist.isClient()) + return Minecraft.getInstance().getModdedFlagManager(); + + return FlagManager.EMPTY; + } +} diff --git a/src/main/java/net/neoforged/neoforge/flag/FlagManager.java b/src/main/java/net/neoforged/neoforge/flag/FlagManager.java new file mode 100644 index 0000000000..916732d6cf --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/flag/FlagManager.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.flag; + +import it.unimi.dsi.fastutil.objects.ReferenceSet; +import java.util.Collection; +import java.util.Set; +import java.util.stream.Stream; +import net.neoforged.neoforge.common.extensions.ILevelReaderExtension; +import org.jetbrains.annotations.ApiStatus; + +/** + * The main manager for all {@link Flag} related tasks. + *

+ * An instance of this can be looked up via {@link ILevelReaderExtension#getModdedFlagManager()}. + */ +public sealed interface FlagManager permits ImmutableFlagManager, EmptyFlagManager, ServerFlagManager { + /** + * Empty instance to be used when no other can be found. + */ + FlagManager EMPTY = new EmptyFlagManager(); + + /** + * Returns {@code true} if the given {@link Flag} is enabled, {@code false} otherwise. + * + * @param flag The {@link Flag} to be tested. + * @return {@code true} if the given {@link Flag} is enabled, {@code false} otherwise. + */ + default boolean isEnabled(Flag flag) { + return getEnabledFlags().contains(flag); + } + + /** + * Returns {@code true} if all given {@link Flag Flags} are enabled, {@code false} otherwise. + * + * @param flag A {@link Flag} to be tested. + * @param flags Additional {@link Flag Flags} to also be tested. + * @return {@code true} if all given {@link Flag Flags} are enabled, {@code false} otherwise. + */ + default boolean isEnabled(Flag flag, Flag... flags) { + if (!isEnabled(flag)) + return false; + + for (var other : flags) { + if (!isEnabled(other)) + return false; + } + + return true; + } + + /** + * Returns {@code true} if all given {@link Flag Flags} are enabled, {@code false} otherwise. + * + * @param flags {@link Flag Flags} to be tested. + * @return {@code true} if all given {@link Flag Flags} are enabled, {@code false} otherwise. + */ + default boolean isEnabled(Collection flags) { + for (var flag : flags) { + if (!isEnabled(flag)) + return false; + } + + return true; + } + + /** + * Sets the state of the given {@link Flag}. + * + * @param flag The {@link Flag} which to have its state changed. + * @param state The new state for the given {@link Flag}. + * @return {@code true} if the state of the {@link Flag} changed, {@code false} otherwise. + */ + boolean set(Flag flag, boolean state); + + /** + * Toggles the state of the given {@link Flag}. + * + * @param flag {@link Flag} which to have its state toggled. + */ + default void toggle(Flag flag) { + set(flag, !isEnabled(flag)); + } + + /** + * Returns an immutable collection containing all currently enabled {@link Flag Flags}. + * + * @return Immutable collection containing all currently enabled {@link Flag Flags}. + */ + ReferenceSet getEnabledFlags(); + + /** + * Returns {@link Stream} representing all currently enabled {@link Flag Flags}. + * + * @return {@link Stream} representing all currently enabled {@link Flag Flags}. + */ + default Stream enabledFlags() { + return getEnabledFlags().stream(); + } + + /** + * Creates a dummy {@link FlagManager} for the provided {@link Set}. + * + * @param enabledFlags {@link Set} of enabled {@link Flag flags} to be wrapped. + * @return Dummy {@link FlagManager} wrapping the provided {@link Set}. + */ + @ApiStatus.Internal + static FlagManager createImmutable(ReferenceSet enabledFlags) { + return new ImmutableFlagManager(enabledFlags); + } +} diff --git a/src/main/java/net/neoforged/neoforge/flag/FlagsCommand.java b/src/main/java/net/neoforged/neoforge/flag/FlagsCommand.java new file mode 100644 index 0000000000..2b3eb89ca3 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/flag/FlagsCommand.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.flag; + +import static com.mojang.brigadier.Command.SINGLE_SUCCESS; +import static net.minecraft.commands.Commands.argument; +import static net.minecraft.commands.Commands.literal; + +import com.google.common.collect.Sets; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import java.util.Set; +import java.util.stream.Collectors; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.commands.arguments.ResourceLocationArgument; +import net.minecraft.network.chat.ClickEvent; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.HoverEvent; +import org.jetbrains.annotations.ApiStatus; + +/** + * Command used for easy {@link Flag} manipulation. + *

+ * This command requires permission level: {@link Commands#LEVEL_ADMINS}. + * + *

    + *
  • {@code /neoforge flags list} - Lists out all Enabled and Disabled {@link Flag Flags}
  • + *
  • {@code /neoforge flags enabled} - Lists out all Enabled {@link Flag Flags}
  • + *
  • {@code /neoforge flags disabled} - Lists out all Disabled {@link Flag Flags}
  • + *
  • {@code /neoforge flags set enabled} - Attempts to Enable the given {@link Flag}
  • + *
  • {@code /neoforge flags set disabled} - Attempts to Disable the given {@link Flag}
  • + *
+ * Note: Replace {@code } with any valid {@link Flag} identifier + */ +@ApiStatus.Internal +public interface FlagsCommand { + static ArgumentBuilder register() { + return literal("flags") + .requires(src -> src.hasPermission(Commands.LEVEL_ADMINS)) + .then(literal("list").executes(context -> { + listFlagsFor(context, true); + listFlagsFor(context, false); + return SINGLE_SUCCESS; + })) + .then(literal("enabled").executes(context -> listFlagsFor(context, true))) + .then(literal("disabled").executes(context -> listFlagsFor(context, false))) + .then(literal("set") + .then(argument("flag", ResourceLocationArgument.id()) + .suggests((context, builder) -> { + var flags = Flag.flags().map(Flag::toStringShort); + return SharedSuggestionProvider.suggest(flags, builder); + }) + .then(literal("enabled").executes(context -> setFlagState(context, true))) + .then(literal("disabled").executes(context -> setFlagState(context, false))))); + } + + private static int setFlagState(CommandContext context, boolean state) throws CommandSyntaxException { + var flag = Flag.of(ResourceLocationArgument.getId(context, "flag")); + var src = context.getSource(); + var flagManager = src.getServer().getModdedFlagManager(); + var stateKey = state ? "enabled" : "disabled"; + var changed = flagManager.set(flag, state); + + src.sendSuccess(() -> { + if (changed) { + return Component.translatable("commands.neoforge.flags." + stateKey, flag.toStringShort()).withStyle(style -> { + var notice = Component.translatable("commands.neoforge.flags.notice"); + var hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, notice); + var clickEvent = new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/reload"); + return style.withHoverEvent(hoverEvent).withClickEvent(clickEvent); + }); + } + + return Component.translatable("commands.neoforge.flags.already_" + stateKey, flag.toStringShort()); + }, false); + + return SINGLE_SUCCESS; + } + + private static int listFlagsFor(CommandContext context, boolean state) { + var src = context.getSource(); + var stateStr = state ? "enabled" : "disabled"; + var flagManager = src.getServer().getModdedFlagManager(); + Set flags = flagManager.getEnabledFlags(); + + if (!state) { + var knownFlags = Flag.getFlags(); + flags = Sets.difference(knownFlags, flags); + } + + var flagsStr = flags.stream().map(Flag::toStringShort).collect(Collectors.joining(", ", "[", "]")); + src.sendSuccess(() -> Component.translatable("commands.neoforge.flags.list_" + stateStr, flagsStr), false); + return SINGLE_SUCCESS; + } +} diff --git a/src/main/java/net/neoforged/neoforge/flag/ImmutableFlagManager.java b/src/main/java/net/neoforged/neoforge/flag/ImmutableFlagManager.java new file mode 100644 index 0000000000..3cf1569459 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/flag/ImmutableFlagManager.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.flag; + +import it.unimi.dsi.fastutil.objects.ReferenceSet; +import it.unimi.dsi.fastutil.objects.ReferenceSets; + +final class ImmutableFlagManager implements FlagManager { + private final ReferenceSet enabledFlags; + + public ImmutableFlagManager(ReferenceSet enabledFlags) { + this.enabledFlags = ReferenceSets.unmodifiable(enabledFlags); + } + + @Override + public boolean set(Flag flag, boolean state) { + return false; + } + + @Override + public ReferenceSet getEnabledFlags() { + return enabledFlags; + } +} diff --git a/src/main/java/net/neoforged/neoforge/flag/RequiredFlagsCondition.java b/src/main/java/net/neoforged/neoforge/flag/RequiredFlagsCondition.java new file mode 100644 index 0000000000..6b7ec5d721 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/flag/RequiredFlagsCondition.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.flag; + +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import java.util.Collection; +import java.util.Set; +import java.util.stream.Collectors; +import net.neoforged.neoforge.common.conditions.ICondition; +import net.neoforged.neoforge.common.util.NeoForgeExtraCodecs; + +/** + * {@link ICondition Condition} used to conditionally load data files based on state of given set of {@link Flag Flags}. + *

+ * All provided {@link #requiredFlags flags} must be enabled in order for data to pass this {@link ICondition condition}. + * + * @apiNote A {@code /reload} is be required to correctly reload data after toggling the state of a given {@link Flag}. + */ +public final class RequiredFlagsCondition implements ICondition { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(builder -> builder.group( + NeoForgeExtraCodecs.setOf(Flag.CODEC).fieldOf("flags").forGetter(condition -> condition.requiredFlags)).apply(builder, RequiredFlagsCondition::new)); + + private final Set requiredFlags; + + public RequiredFlagsCondition(Flag... requiredFlags) { + this.requiredFlags = new ReferenceOpenHashSet<>(requiredFlags); + } + + private RequiredFlagsCondition(Collection requiredFlags) { + this.requiredFlags = new ReferenceOpenHashSet<>(requiredFlags); + } + + @Override + public boolean test(IContext context) { + return context.getModdedFlagManager().isEnabled(requiredFlags); + } + + @Override + public MapCodec codec() { + return CODEC; + } + + @Override + public String toString() { + return "required_flags(" + requiredFlags.stream().map(Flag::toStringShort).collect(Collectors.joining(", ", "\"", "\"")) + ')'; + } +} diff --git a/src/main/java/net/neoforged/neoforge/flag/ServerFlagManager.java b/src/main/java/net/neoforged/neoforge/flag/ServerFlagManager.java new file mode 100644 index 0000000000..2ceb179578 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/flag/ServerFlagManager.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.flag; + +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import it.unimi.dsi.fastutil.objects.ReferenceSet; +import it.unimi.dsi.fastutil.objects.ReferenceSets; +import java.util.Set; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.level.WorldDataConfiguration; +import net.neoforged.neoforge.network.PacketDistributor; + +public final class ServerFlagManager implements FlagManager { + private final ReferenceSet enabledFlags = new ReferenceOpenHashSet<>(); + + private final MinecraftServer server; + + public ServerFlagManager(MinecraftServer server, Set initialEnabledFlags) { + this.server = server; + enabledFlags.addAll(initialEnabledFlags); + } + + private void flagChanged(Flag flag, boolean state) { + // notify clients of state change + PacketDistributor.sendToAllPlayers(new ClientboundSyncFlags(enabledFlags)); + + var data = server.getWorldData(); + var config = data.getDataConfiguration(); + + // update backing config file + // will be saved to disk later during autosave/server shutdown + data.setDataConfiguration(new WorldDataConfiguration(config.dataPacks(), config.enabledFeatures(), enabledFlags)); + } + + @Override + public boolean set(Flag flag, boolean state) { + var changed = state ? enabledFlags.add(flag) : enabledFlags.remove(flag); + + if (changed) + flagChanged(flag, state); + + return changed; + } + + @Override + public ReferenceSet getEnabledFlags() { + return ReferenceSets.unmodifiable(enabledFlags); + } +} diff --git a/src/main/java/net/neoforged/neoforge/flag/package-info.java b/src/main/java/net/neoforged/neoforge/flag/package-info.java new file mode 100644 index 0000000000..91c6c0d04b --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/flag/package-info.java @@ -0,0 +1,13 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +@FieldsAreNonnullByDefault +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package net.neoforged.neoforge.flag; + +import javax.annotation.ParametersAreNonnullByDefault; +import net.minecraft.FieldsAreNonnullByDefault; +import net.minecraft.MethodsReturnNonnullByDefault; diff --git a/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java b/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java index a3e9c32c35..2e38b27739 100644 --- a/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java +++ b/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java @@ -7,6 +7,7 @@ import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.flag.ClientboundSyncFlags; import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; import net.neoforged.neoforge.network.configuration.CheckExtensibleEnums; import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; @@ -94,6 +95,10 @@ private static void register(final RegisterPayloadHandlersEvent event) { .playToClient( ClientboundCustomSetTimePayload.TYPE, ClientboundCustomSetTimePayload.STREAM_CODEC, - ClientPayloadHandler::handle); + ClientPayloadHandler::handle) + .playToClient( + ClientboundSyncFlags.TYPE, + ClientboundSyncFlags.STREAM_CODEC, + ClientboundSyncFlags::handle); } } diff --git a/src/main/java/net/neoforged/neoforge/server/command/NeoForgeCommand.java b/src/main/java/net/neoforged/neoforge/server/command/NeoForgeCommand.java index 5967982f0c..6e4b112657 100644 --- a/src/main/java/net/neoforged/neoforge/server/command/NeoForgeCommand.java +++ b/src/main/java/net/neoforged/neoforge/server/command/NeoForgeCommand.java @@ -8,6 +8,7 @@ import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import net.minecraft.commands.CommandSourceStack; +import net.neoforged.neoforge.flag.FlagsCommand; import org.jetbrains.annotations.ApiStatus; @ApiStatus.Internal @@ -24,6 +25,7 @@ public static void register(CommandDispatcher dispatcher) { .then(TagsCommand.register()) .then(DumpCommand.register()) .then(TimeSpeedCommand.register()) - .then(DataComponentCommand.register())); + .then(DataComponentCommand.register()) + .then(FlagsCommand.register())); } } diff --git a/src/main/resources/assets/neoforge/lang/en_us.json b/src/main/resources/assets/neoforge/lang/en_us.json index 0d78311974..f2b536146d 100644 --- a/src/main/resources/assets/neoforge/lang/en_us.json +++ b/src/main/resources/assets/neoforge/lang/en_us.json @@ -127,6 +127,13 @@ "commands.neoforge.data_components.list.tooltip.deleted": "Component %s with value %s was deleted", "commands.neoforge.data_components.list.tooltip.modified": "Component %s was modified from %s to %s", "commands.neoforge.data_components.list.tooltip.added": "Component %s was added with value %s", + "commands.neoforge.flags.enabled": "Enabled Flag: %s", + "commands.neoforge.flags.notice": "Note: A '/reload' may also be required for flagged datapack entries\nClick to run command", + "commands.neoforge.flags.already_enabled": "Flag %s is already Enabled", + "commands.neoforge.flags.list_enabled": "Enabled Flags: %s", + "commands.neoforge.flags.disabled": "Disabled Flag: %s", + "commands.neoforge.flags.already_disabled": "Flag %s is already Disabled", + "commands.neoforge.flags.list_disabled": "Disabled Flags: %s", "commands.config.getwithtype": "Config for %s of type %s found at %s", "commands.config.noconfig": "Config for %s of type %s not found", diff --git a/testframework/src/main/java/net/neoforged/testframework/junit/EphemeralTestServerProvider.java b/testframework/src/main/java/net/neoforged/testframework/junit/EphemeralTestServerProvider.java index ca738226a1..cfd046b7f1 100644 --- a/testframework/src/main/java/net/neoforged/testframework/junit/EphemeralTestServerProvider.java +++ b/testframework/src/main/java/net/neoforged/testframework/junit/EphemeralTestServerProvider.java @@ -148,7 +148,7 @@ public static JUnitServer create( Thread thread, Path tempDir, LevelStorageSource.LevelStorageAccess access, PackRepository resources) { resources.reload(); WorldDataConfiguration config = new WorldDataConfiguration( - new DataPackConfig(new ArrayList<>(resources.getAvailableIds()), List.of()), FeatureFlags.REGISTRY.allFlags()); + new DataPackConfig(new ArrayList<>(resources.getAvailableIds()), List.of()), FeatureFlags.REGISTRY.allFlags(), net.neoforged.neoforge.flag.Flag.getFlags()); LevelSettings levelsettings = new LevelSettings( "Test Level", GameType.CREATIVE, false, Difficulty.NORMAL, true, TEST_GAME_RULES, config); WorldLoader.PackConfig worldloader$packconfig = new WorldLoader.PackConfig(resources, config, false, true); diff --git a/tests/src/main/java/net/neoforged/neoforge/debug/FlagTests.java b/tests/src/main/java/net/neoforged/neoforge/debug/FlagTests.java new file mode 100644 index 0000000000..67853b33d9 --- /dev/null +++ b/tests/src/main/java/net/neoforged/neoforge/debug/FlagTests.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.debug; + +import net.minecraft.client.renderer.entity.NoopRenderer; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.MobCategory; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.neoforged.neoforge.common.DeferredSpawnEggItem; +import net.neoforged.neoforge.flag.Flag; +import net.neoforged.testframework.DynamicTest; +import net.neoforged.testframework.annotation.ForEachTest; +import net.neoforged.testframework.annotation.TestHolder; + +@ForEachTest(groups = "modded_feature_flags") +public interface FlagTests { + @TestHolder(description = "Tests modded feature flags") + static void test(DynamicTest test) { + var namespace = test.createModId(); + var registration = test.registrationHelper(); + var items = registration.items(); + + // register various elements which require our flag + var testFlag = Flag.of(namespace, "test_flag"); + + items.registerSimpleItem("flagged_item", new Item.Properties().requiredFlags(testFlag)); + + // block disabled via matching block item + var flaggedBlock = registration.blocks().registerSimpleBlock("flagged_block", BlockBehaviour.Properties.ofFullCopy(Blocks.STONE).requiredFlags(testFlag)); + items.registerSimpleBlockItem(flaggedBlock); + + // spawn egg disabled via matching entity type + var flaggedEntity = registration.entityTypes().registerType("flagged_entity", () -> EntityType.Builder + .of(DummyEntity::new, MobCategory.MISC) + .requiredFlags(testFlag)).withRenderer(() -> NoopRenderer::new).withAttributes(Mob::createMobAttributes); + + items.registerItem("flagged_entity_egg", properties -> new DeferredSpawnEggItem(flaggedEntity, 0, 0, properties)); + + // generate recipe which requires our flag + // TODO: Figure out why this causes duplicate provider exceptions + // Seems to clash with recipe provider in DataGeneratorTest -> gen.addProvider(event.includeServer(), new Recipes(packOutput, lookupProvider)); + // IngredientTests does the same thing using addProvider which works fine, but here causes dupe providers, why?!? + // Things I have tried + // - Renaming method to change mod id + // - Adding @GameTest and @EmptyTemplate to match IngredientTests + // - Making groups in @ForEachTests dotted + // - Passing RegistrationHelper as parameter + // - Creating new RegistrationHelper staticly (and registering with @OnInit) and using that rather than one from DynamicTest + // - Passing new mod id to test.registrationHelper() + // - Changing value in @TestHolder for new mod id + // - Setting idPrefix in @ForEachTest + /*registration.addProvider(event -> event.getGenerator().addProvider(event.includeServer(), new RecipeProvider(event.getGenerator().getPackOutput(), event.getLookupProvider()) { + @Override + protected void buildRecipes(RecipeOutput output, HolderLookup.Provider provider) { + ShapelessRecipeBuilder.shapeless(RecipeCategory.MISC, Items.DIAMOND, 64) + .requires(ItemTags.DIRT) + .unlockedBy("has_dirt", has(ItemTags.DIRT)) + .save(output.withConditions(new RequiredFlagsCondition(testFlag)), ResourceLocation.fromNamespaceAndPath(namespace, "flagged/diamonds_from_dirt")); + } + }));*/ + } + + final class DummyEntity extends Mob { + DummyEntity(EntityType entityType, Level level) { + super(entityType, level); + } + } +} diff --git a/tests/src/main/resources/data/neotests_flags/advancement/recipes/misc/flagged/diamonds_from_dirt.json b/tests/src/main/resources/data/neotests_flags/advancement/recipes/misc/flagged/diamonds_from_dirt.json new file mode 100644 index 0000000000..88c7c37f2c --- /dev/null +++ b/tests/src/main/resources/data/neotests_flags/advancement/recipes/misc/flagged/diamonds_from_dirt.json @@ -0,0 +1,40 @@ +{ + "neoforge:conditions": [ + { + "type": "neoforge:required_flags", + "flags": [ + "neotests_test:test_flag" + ] + } + ], + "parent": "minecraft:recipes/root", + "criteria": { + "has_dirt": { + "conditions": { + "items": [ + { + "items": "#minecraft:dirt" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "neotests_flags:flagged/diamonds_from_dirt" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_dirt" + ] + ], + "rewards": { + "recipes": [ + "neotests_flags:flagged/diamonds_from_dirt" + ] + } +} \ No newline at end of file diff --git a/tests/src/main/resources/data/neotests_flags/recipe/flagged/diamonds_from_dirt.json b/tests/src/main/resources/data/neotests_flags/recipe/flagged/diamonds_from_dirt.json new file mode 100644 index 0000000000..fe7dfca228 --- /dev/null +++ b/tests/src/main/resources/data/neotests_flags/recipe/flagged/diamonds_from_dirt.json @@ -0,0 +1,21 @@ +{ + "neoforge:conditions": [ + { + "type": "neoforge:required_flags", + "flags": [ + "neotests_test:test_flag" + ] + } + ], + "type": "minecraft:crafting_shapeless", + "category": "misc", + "ingredients": [ + { + "tag": "minecraft:dirt" + } + ], + "result": { + "count": 64, + "id": "minecraft:diamond" + } +} \ No newline at end of file