diff --git a/patches/net/minecraft/client/gui/screens/inventory/CreativeModeInventoryScreen.java.patch b/patches/net/minecraft/client/gui/screens/inventory/CreativeModeInventoryScreen.java.patch index 37ccae8839..e590cf0739 100644 --- a/patches/net/minecraft/client/gui/screens/inventory/CreativeModeInventoryScreen.java.patch +++ b/patches/net/minecraft/client/gui/screens/inventory/CreativeModeInventoryScreen.java.patch @@ -40,7 +40,7 @@ + this.pages.clear(); + int tabIndex = 0; + List currentPage = new java.util.ArrayList<>(); -+ for (CreativeModeTab sortedCreativeModeTab : net.neoforged.neoforge.common.CreativeModeTabRegistry.getSortedCreativeModeTabs()) { ++ for (CreativeModeTab sortedCreativeModeTab : net.neoforged.neoforge.common.CreativeModeTabRegistry.getSortedCreativeModeTabs().stream().filter(CreativeModeTab::hasAnyItems).toList()) { + currentPage.add(sortedCreativeModeTab); + tabIndex++; + if (tabIndex == 10) { diff --git a/patches/net/minecraft/world/item/crafting/RecipeManager.java.patch b/patches/net/minecraft/world/item/crafting/RecipeManager.java.patch index e1cbdae41d..f33bac52e3 100644 --- a/patches/net/minecraft/world/item/crafting/RecipeManager.java.patch +++ b/patches/net/minecraft/world/item/crafting/RecipeManager.java.patch @@ -1,14 +1,23 @@ --- a/net/minecraft/world/item/crafting/RecipeManager.java +++ b/net/minecraft/world/item/crafting/RecipeManager.java -@@ -260,6 +_,11 @@ +@@ -69,7 +_,7 @@ + protected RecipeMap prepare(ResourceManager p_379845_, ProfilerFiller p_380058_) { + SortedMap> sortedmap = new TreeMap<>(); + SimpleJsonResourceReloadListener.scanDirectory( +- p_379845_, Registries.elementsDirPath(Registries.RECIPE), this.registries.createSerializationContext(JsonOps.INSTANCE), Recipe.CODEC, sortedmap ++ p_379845_, Registries.elementsDirPath(Registries.RECIPE), new net.neoforged.neoforge.common.conditions.ConditionalOps<>(this.registries.createSerializationContext(JsonOps.INSTANCE), getContext()), Recipe.CODEC, sortedmap // Neo: add condition context + ); + List> list = new ArrayList<>(sortedmap.size()); + sortedmap.forEach((p_379232_, p_379233_) -> { +@@ -258,6 +_,11 @@ + return p_380850_ -> p_380850_.getType() == p_381108_ && p_380850_ instanceof SingleItemRecipe singleitemrecipe + ? Optional.of(singleitemrecipe.input()) : Optional.empty(); - } - ++ } ++ + // Neo: expose recipe map + public RecipeMap recipeMap() { + return this.recipes; -+ } -+ - public interface CachedCheck> { - Optional> getRecipeFor(I p_344938_, ServerLevel p_379487_); } + + public interface CachedCheck> { diff --git a/patches/net/minecraft/world/level/storage/PrimaryLevelData.java.patch b/patches/net/minecraft/world/level/storage/PrimaryLevelData.java.patch index b9f9edb375..1ff81563a8 100644 --- a/patches/net/minecraft/world/level/storage/PrimaryLevelData.java.patch +++ b/patches/net/minecraft/world/level/storage/PrimaryLevelData.java.patch @@ -17,6 +17,16 @@ p_78531_.get("Player").flatMap(CompoundTag.CODEC::parse).result().orElse(null), p_78531_.get("WasModded").asBoolean(false), new BlockPos(p_78531_.get("SpawnX").asInt(0), p_78531_.get("SpawnY").asInt(0), p_78531_.get("SpawnZ").asInt(0)), +@@ -192,7 +_,8 @@ + .asStream() + .flatMap(p_338118_ -> p_338118_.asString().result().stream()) + .collect(Collectors.toCollection(Sets::newLinkedHashSet)), +- p_78531_.get("removed_features").asStream().flatMap(p_338117_ -> p_338117_.asString().result().stream()).collect(Collectors.toSet()), ++ // Neo: Append removed modded feature flags ++ updateRemovedFeatureFlags(p_78531_.get("removed_features").asStream().flatMap(p_338117_ -> p_338117_.asString().result().stream()), p_78531_.get("enabled_features").asStream().flatMap(features -> features.asString().result().stream())).collect(Collectors.toSet()), + new TimerQueue<>(TimerCallbacks.SERVER_CALLBACKS, p_78531_.get("ScheduledEvents").asStream()), + (CompoundTag)p_78531_.get("CustomBossEvents").orElseEmptyMap().getValue(), + p_78531_.get("DragonFight").read(EndDragonFight.Data.CODEC).resultOrPartial(LOGGER::error).orElse(EndDragonFight.Data.DEFAULT), @@ -200,7 +_,11 @@ p_251864_, p_250651_, @@ -42,7 +52,7 @@ } private static ListTag stringCollectionToTag(Set p_277880_) { -@@ -572,10 +_,44 @@ +@@ -572,10 +_,58 @@ return this.settings.copy(); } @@ -85,5 +95,19 @@ + @Override + public void setDayTimePerTick(float dayTimePerTick) { + this.dayTimePerTick = dayTimePerTick; ++ } ++ ++ private static java.util.stream.Stream updateRemovedFeatureFlags(java.util.stream.Stream removedFeatures, java.util.stream.Stream enabledFeatures) { ++ var unknownFeatureFlags = new HashSet(); ++ // parses the incoming Stream and spits out unknown flag names (ResourceLocation) ++ // we do not care about the returned FeatureFlagSet, only the flags which do not exist ++ net.minecraft.world.flag.FeatureFlags.REGISTRY.fromNames(enabledFeatures.map(net.minecraft.resources.ResourceLocation::parse).collect(Collectors.toSet()), unknownFeatureFlags::add); ++ // concat the received removed flags with our new additions ++ return java.util.stream.Stream.concat(removedFeatures, unknownFeatureFlags.stream() ++ // we only want modded flags, mojang has datafixers for vanilla flags ++ .filter(java.util.function.Predicate.not(name -> name.getNamespace().equals(net.minecraft.resources.ResourceLocation.DEFAULT_NAMESPACE))) ++ .map(net.minecraft.resources.ResourceLocation::toString)) ++ // no duplicates should exist in this stream ++ .distinct(); } } diff --git a/src/main/java/net/neoforged/neoforge/common/NeoForgeConfig.java b/src/main/java/net/neoforged/neoforge/common/NeoForgeConfig.java index 696fa0728d..e7335e0b74 100644 --- a/src/main/java/net/neoforged/neoforge/common/NeoForgeConfig.java +++ b/src/main/java/net/neoforged/neoforge/common/NeoForgeConfig.java @@ -65,8 +65,11 @@ public static class Server { */ public static class Common { public final ModConfigSpec.EnumValue logUntranslatedItemTagWarnings; + public final ModConfigSpec.EnumValue logLegacyTagWarnings; + public final BooleanValue attributeAdvancedTooltipDebugInfo; + Common(ModConfigSpec.Builder builder) { logUntranslatedItemTagWarnings = builder .comment("A config option mainly for developers. Logs out modded item tags that do not have translations when running on integrated server. Format desired is tag.item.. for the translation key. Defaults to SILENCED.") @@ -77,6 +80,11 @@ public static class Common { .comment("A config option mainly for developers. Logs out modded tags that are using the 'forge' namespace when running on integrated server. Defaults to DEV_SHORT.") .translation("neoforge.configgui.logLegacyTagWarnings") .defineEnum("logLegacyTagWarnings", TagConventionLogWarning.LogWarningMode.DEV_SHORT); + + attributeAdvancedTooltipDebugInfo = builder + .comment("Set this to true to enable showing debug information about attributes on an item when advanced tooltips is on.") + .translation("neoforge.configgui.attributeAdvancedTooltipDebugInfo") + .define("attributeAdvancedTooltipDebugInfo", true); } } diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IAttributeExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IAttributeExtension.java index 6a5c51564d..dd7074d93c 100644 --- a/src/main/java/net/neoforged/neoforge/common/extensions/IAttributeExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IAttributeExtension.java @@ -23,6 +23,7 @@ import net.minecraft.world.entity.ai.attributes.AttributeModifier.Operation; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.item.TooltipFlag; +import net.neoforged.neoforge.common.NeoForgeConfig; import net.neoforged.neoforge.common.NeoForgeMod; import net.neoforged.neoforge.common.util.AttributeUtil; import org.jetbrains.annotations.Nullable; @@ -83,7 +84,7 @@ default MutableComponent toComponent(AttributeModifier modif, TooltipFlag flag) default Component getDebugInfo(AttributeModifier modif, TooltipFlag flag) { Component debugInfo = CommonComponents.EMPTY; - if (flag.isAdvanced()) { + if (flag.isAdvanced() && NeoForgeConfig.COMMON.attributeAdvancedTooltipDebugInfo.get()) { // Advanced Tooltips show the underlying operation and the "true" value. We offset MULTIPLY_TOTAL by 1 due to how the operation is calculated. double advValue = (modif.operation() == Operation.ADD_MULTIPLIED_TOTAL ? 1 : 0) + modif.amount(); String valueStr = FORMAT.format(advValue); diff --git a/src/main/java/net/neoforged/neoforge/network/PacketDistributor.java b/src/main/java/net/neoforged/neoforge/network/PacketDistributor.java index f09dfae0c9..ea23ac4dcc 100644 --- a/src/main/java/net/neoforged/neoforge/network/PacketDistributor.java +++ b/src/main/java/net/neoforged/neoforge/network/PacketDistributor.java @@ -87,22 +87,24 @@ public static void sendToAllPlayers(CustomPacketPayload payload, CustomPacketPay * Send the given payload(s) to all players tracking the given entity */ public static void sendToPlayersTrackingEntity(Entity entity, CustomPacketPayload payload, CustomPacketPayload... payloads) { - if (entity.level().getChunkSource() instanceof ServerChunkCache chunkCache) { - chunkCache.broadcast(entity, makeClientboundPacket(payload, payloads)); - } else { + if (entity.level().isClientSide()) { throw new IllegalStateException("Cannot send clientbound payloads on the client"); + } else if (entity.level().getChunkSource() instanceof ServerChunkCache chunkCache) { + chunkCache.broadcast(entity, makeClientboundPacket(payload, payloads)); } + // Silently ignore custom Level implementations which may not return ServerChunkCache. } /** * Send the given payload(s) to all players tracking the given entity and the entity itself if it is a player */ public static void sendToPlayersTrackingEntityAndSelf(Entity entity, CustomPacketPayload payload, CustomPacketPayload... payloads) { - if (entity.level().getChunkSource() instanceof ServerChunkCache chunkCache) { - chunkCache.broadcastAndSend(entity, makeClientboundPacket(payload, payloads)); - } else { + if (entity.level().isClientSide()) { throw new IllegalStateException("Cannot send clientbound payloads on the client"); + } else if (entity.level().getChunkSource() instanceof ServerChunkCache chunkCache) { + chunkCache.broadcastAndSend(entity, makeClientboundPacket(payload, payloads)); } + // Silently ignore custom Level implementations which may not return ServerChunkCache. } /** diff --git a/src/main/resources/assets/neoforge/lang/en_us.json b/src/main/resources/assets/neoforge/lang/en_us.json index 44647a6d89..7cd26fc40b 100644 --- a/src/main/resources/assets/neoforge/lang/en_us.json +++ b/src/main/resources/assets/neoforge/lang/en_us.json @@ -192,6 +192,8 @@ "neoforge.configuration.section.neoforge.server.toml.title": "Server settings", "neoforge.configgui.advertiseDedicatedServerToLan": "Advertise Dedicated Server To LAN", "neoforge.configgui.advertiseDedicatedServerToLan.tooltip": "Set this to true to enable advertising the dedicated server to local LAN clients so that it shows up in the Multiplayer screen automatically.", + "neoforge.configgui.attributeAdvancedTooltipDebugInfo": "Additional Attribute Advanced Tooltips", + "neoforge.configgui.attributeAdvancedTooltipDebugInfo.tooltip": "Set this to true to enable additional information about attributes on an item when advanced tooltips is on.", "neoforge.configgui.forgeLightPipelineEnabled": "NeoForge Light Pipeline", "neoforge.configgui.forgeLightPipelineEnabled.tooltip": "Enable the NeoForge block rendering pipeline - fixes the lighting of custom models.", "neoforge.configgui.fullBoundingBoxLadders": "Full Bounding Box Ladders", diff --git a/tests/src/generated/resources/data/neotests_test_conditional_recipe/advancement/recipes/misc/always_disabled_recipe.json b/tests/src/generated/resources/data/neotests_test_conditional_recipe/advancement/recipes/misc/always_disabled_recipe.json new file mode 100644 index 0000000000..e13d7504ae --- /dev/null +++ b/tests/src/generated/resources/data/neotests_test_conditional_recipe/advancement/recipes/misc/always_disabled_recipe.json @@ -0,0 +1,37 @@ +{ + "neoforge:conditions": [ + { + "type": "neoforge:false" + } + ], + "parent": "minecraft:recipes/root", + "criteria": { + "has_stone": { + "conditions": { + "items": [ + { + "items": "minecraft:stone" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "neotests_test_conditional_recipe:always_disabled_recipe" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_stone" + ] + ], + "rewards": { + "recipes": [ + "neotests_test_conditional_recipe:always_disabled_recipe" + ] + } +} \ No newline at end of file diff --git a/tests/src/generated/resources/data/neotests_test_conditional_recipe/recipe/always_disabled_recipe.json b/tests/src/generated/resources/data/neotests_test_conditional_recipe/recipe/always_disabled_recipe.json new file mode 100644 index 0000000000..404842c813 --- /dev/null +++ b/tests/src/generated/resources/data/neotests_test_conditional_recipe/recipe/always_disabled_recipe.json @@ -0,0 +1,16 @@ +{ + "neoforge:conditions": [ + { + "type": "neoforge:false" + } + ], + "type": "minecraft:crafting_shapeless", + "category": "misc", + "ingredients": [ + "minecraft:stone" + ], + "result": { + "count": 1, + "id": "minecraft:bedrock" + } +} \ No newline at end of file diff --git a/tests/src/main/java/net/neoforged/neoforge/debug/ConditionalRecipeTest.java b/tests/src/main/java/net/neoforged/neoforge/debug/ConditionalRecipeTest.java new file mode 100644 index 0000000000..e4b71b535f --- /dev/null +++ b/tests/src/main/java/net/neoforged/neoforge/debug/ConditionalRecipeTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.debug; + +import net.minecraft.core.HolderLookup; +import net.minecraft.core.registries.Registries; +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.data.recipes.RecipeOutput; +import net.minecraft.data.recipes.RecipeProvider; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Items; +import net.neoforged.neoforge.common.conditions.FalseCondition; +import net.neoforged.neoforge.event.server.ServerStartedEvent; +import net.neoforged.testframework.DynamicTest; +import net.neoforged.testframework.annotation.ForEachTest; +import net.neoforged.testframework.annotation.TestHolder; +import net.neoforged.testframework.registration.RegistrationHelper; + +@ForEachTest(groups = "conditional_recipes") +public interface ConditionalRecipeTest { + @TestHolder(description = "Validates that recipes support conditionals by generating a new recipe disabled by the FALSE condition", enabledByDefault = true) + static void testConditionalRecipe(DynamicTest test, RegistrationHelper reg) { + // name pointing to recipe which should never be enabled + var recipeName = ResourceKey.create(Registries.RECIPE, ResourceLocation.fromNamespaceAndPath(reg.modId(), "always_disabled_recipe")); + + reg.addProvider(event -> new RecipeProvider.Runner(event.getGenerator().getPackOutput(), event.getLookupProvider()) { + @Override + protected RecipeProvider createRecipeProvider(HolderLookup.Provider registries, RecipeOutput output) { + return new RecipeProvider(registries, output) { + @Override + protected void buildRecipes() { + // generic stone -> bedrock recipe + shapeless(RecipeCategory.MISC, Items.BEDROCK) + .requires(Items.STONE) + .unlockedBy("has_stone", has(Items.STONE)) + // false condition to have this recipe always disabled + .save(output.withConditions(FalseCondition.INSTANCE), recipeName); + } + }; + } + + @Override + public String getName() { + return "always_disabled_recipe_provider"; + } + }); + + test.eventListeners().forge().addListener((ServerStartedEvent event) -> { + var recipe = event.getServer().getRecipeManager().recipeMap().byKey(recipeName); + + if (recipe == null) + test.pass(); + else + test.fail("Found recipe: '" + recipeName.location() + "', This should always be disabled due to 'FALSE' condition!"); + }); + } +}