diff --git a/patches/net/minecraft/client/Minecraft.java.patch b/patches/net/minecraft/client/Minecraft.java.patch index 13ff259e92..f20c7d7eb9 100644 --- a/patches/net/minecraft/client/Minecraft.java.patch +++ b/patches/net/minecraft/client/Minecraft.java.patch @@ -9,6 +9,14 @@ static Minecraft instance; private static final Logger LOGGER = LogUtils.getLogger(); public static final boolean ON_OSX = Util.getPlatform() == Util.OS.OSX; +@@ -399,6 +_,7 @@ + private final long clientStartTimeMs; + private long clientTickCount; + private String debugPath = "root"; ++ private final net.neoforged.neoforge.flag.FlagManager moddedFlagManager = new net.neoforged.neoforge.flag.FlagManager(this::syncFlag); + + public Minecraft(GameConfig p_91084_) { + super("Client"); @@ -435,7 +_,6 @@ } }, Util.nonCriticalIoPool()); @@ -378,14 +386,31 @@ 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 +_,16 @@ + @Nullable + public static String getLauncherBrand() { + return System.getProperty("minecraft.launcher.brand"); ++ } ++ ++ @Override ++ public net.neoforged.neoforge.flag.FlagManager getModdedFlagManager() { ++ return moddedFlagManager; ++ } ++ ++ private void syncFlag(net.neoforged.neoforge.flag.Flag flag, boolean enabled) { ++ if(singleplayerServer != null) ++ moddedFlagManager.saveToLevel(singleplayerServer.overworld()); } - public EntityModelSet getEntityModels() { + @OnlyIn(Dist.CLIENT) 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/client/server/IntegratedServer.java.patch b/patches/net/minecraft/client/server/IntegratedServer.java.patch index 2c8e4f62cc..121606ace1 100644 --- a/patches/net/minecraft/client/server/IntegratedServer.java.patch +++ b/patches/net/minecraft/client/server/IntegratedServer.java.patch @@ -1,11 +1,12 @@ --- a/net/minecraft/client/server/IntegratedServer.java +++ b/net/minecraft/client/server/IntegratedServer.java -@@ -73,10 +_,12 @@ +@@ -73,10 +_,13 @@ this.setPvpAllowed(true); this.setFlightAllowed(true); this.initializeKeyPair(); + net.neoforged.neoforge.server.ServerLifecycleHooks.handleServerAboutToStart(this); this.loadLevel(); ++ minecraft.getModdedFlagManager().loadFromLevel(overworld()); // Neo: Load flag data GameProfile gameprofile = this.getSingleplayerProfile(); String s = this.getWorldData().getLevelName(); this.setMotd(gameprofile != null ? gameprofile.getName() + " - " + s : s); @@ -21,3 +22,14 @@ this.executeBlocking(() -> { for (ServerPlayer serverplayer : Lists.newArrayList(this.getPlayerList().getPlayers())) { if (!serverplayer.getUUID().equals(this.uuid)) { +@@ -320,5 +_,10 @@ + super.reportChunkSaveFailure(p_352264_, p_352355_, p_331440_); + this.warnOnLowDiskSpace(); + this.minecraft.execute(() -> SystemToast.onChunkSaveFailure(this.minecraft, p_331440_)); ++ } ++ ++ @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..8cb88e60f2 100644 --- a/patches/net/minecraft/gametest/framework/GameTestServer.java.patch +++ b/patches/net/minecraft/gametest/framework/GameTestServer.java.patch @@ -1,5 +1,13 @@ --- a/net/minecraft/gametest/framework/GameTestServer.java +++ b/net/minecraft/gametest/framework/GameTestServer.java +@@ -70,6 +_,7 @@ + private static final WorldOptions WORLD_OPTIONS = new WorldOptions(0L, false, false); + @Nullable + private MultipleTestTracker testTracker; ++ private final net.neoforged.neoforge.flag.FlagManager moddedFlagManager = new net.neoforged.neoforge.flag.FlagManager((flag, enabled) -> { /* NOOP: No syncing during game tests */ }); + + public static GameTestServer create( + Thread p_206607_, LevelStorageSource.LevelStorageAccess p_206608_, PackRepository p_206609_, Collection p_206610_, BlockPos p_206611_ @@ -151,6 +_,7 @@ public boolean initServer() { this.setPlayerList(new PlayerList(this, this.registries(), this.playerDataStorage, 1) { @@ -16,3 +24,14 @@ return true; } +@@ -301,5 +_,10 @@ + @Override + public boolean isSingleplayerOwner(GameProfile p_177617_) { + return false; ++ } ++ ++ @Override ++ public net.neoforged.neoforge.flag.FlagManager getModdedFlagManager() { ++ return moddedFlagManager; + } + } diff --git a/patches/net/minecraft/server/MinecraftServer.java.patch b/patches/net/minecraft/server/MinecraftServer.java.patch index 5d60f55f4d..e9272042fa 100644 --- a/patches/net/minecraft/server/MinecraftServer.java.patch +++ b/patches/net/minecraft/server/MinecraftServer.java.patch @@ -1,5 +1,14 @@ --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java +@@ -167,7 +_,7 @@ + import net.minecraft.world.phys.Vec3; + import org.slf4j.Logger; + +-public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements ServerInfo, ChunkIOErrorReporter, CommandSource, AutoCloseable { ++public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements ServerInfo, ChunkIOErrorReporter, CommandSource, AutoCloseable, net.neoforged.neoforge.common.extensions.IMinecraftServerExtension { + private static final Logger LOGGER = LogUtils.getLogger(); + public static final String VANILLA_BRAND = "vanilla"; + private static final float AVERAGE_TICK_TIME_SMOOTHING = 0.8F; @@ -264,7 +_,7 @@ public static S spin(Function p_129873_) { @@ -205,6 +214,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 Feature Flages (Modded)", ++ () -> getModdedFlagManager().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_) { diff --git a/patches/net/minecraft/server/dedicated/DedicatedServer.java.patch b/patches/net/minecraft/server/dedicated/DedicatedServer.java.patch index fededefd4f..3d0ea88236 100644 --- a/patches/net/minecraft/server/dedicated/DedicatedServer.java.patch +++ b/patches/net/minecraft/server/dedicated/DedicatedServer.java.patch @@ -1,11 +1,12 @@ --- a/net/minecraft/server/dedicated/DedicatedServer.java +++ b/net/minecraft/server/dedicated/DedicatedServer.java -@@ -80,6 +_,8 @@ +@@ -80,6 +_,9 @@ @Nullable private DebugSampleSubscriptionTracker debugSampleSubscriptionTracker; private final ServerLinks serverLinks; + @Nullable + private net.minecraft.client.server.LanServerPinger dediLanPinger; ++ private final net.neoforged.neoforge.flag.FlagManager moddedFlagManager = new net.neoforged.neoforge.flag.FlagManager(this::syncFlag); public DedicatedServer( Thread p_214789_, @@ -17,13 +18,14 @@ BufferedReader bufferedreader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8)); String s1; -@@ -185,11 +_,13 @@ +@@ -185,11 +_,14 @@ long i = Util.getNanos(); SkullBlockEntity.setup(this.services, this); GameProfileCache.setUsesAuthentication(this.usesAuthentication()); + net.neoforged.neoforge.server.ServerLifecycleHooks.handleServerAboutToStart(this); LOGGER.info("Preparing level \"{}\"", this.getLevelIdName()); this.loadLevel(); ++ moddedFlagManager.loadFromLevel(overworld()); // Neo: Load flag data long j = Util.getNanos() - i; String s = String.format(Locale.ROOT, "%.3fs", (double)j / 1.0E9); LOGGER.info("Done ({})! For help, type \"help\"", s); @@ -68,3 +70,23 @@ Util.shutdownExecutors(); SkullBlockEntity.clear(); } +@@ -618,6 +_,19 @@ + @Override + public ServerLinks serverLinks() { + return this.serverLinks; ++ } ++ ++ @Override ++ public net.neoforged.neoforge.flag.FlagManager getModdedFlagManager() { ++ return moddedFlagManager; ++ } ++ ++ private void syncFlag(net.neoforged.neoforge.flag.Flag flag, boolean enabled) { ++ moddedFlagManager.saveToLevel(overworld()); ++ ++ net.neoforged.neoforge.network.PacketDistributor.sendToAllPlayers( ++ new net.neoforged.neoforge.flag.ClientboundSyncFlag(flag, enabled) ++ ); + } + + private static ServerLinks createServerLinks(DedicatedServerSettings p_352317_) { 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..8c9a167c2b 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 java.util.Set requiredFlags = new java.util.HashSet<>(); + + protected MobEffect(MobEffectCategory p_19451_, int p_19452_) { + this.category = p_19451_; @@ -130,6 +_,18 @@ return this; } @@ -28,11 +36,32 @@ 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 +_,34 @@ return this.requiredFeatures; } - static record AttributeTemplate(ResourceLocation id, double amount, AttributeModifier.Operation operation) { ++ public MobEffect requiredFlags(net.neoforged.neoforge.flag.Flag... requiredFlags) { ++ java.util.Collections.addAll(this.requiredFlags, requiredFlags); ++ return this; ++ } ++ ++ @Override ++ public java.util.Set requiredFlags() { ++ return 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..17aa98b092 100644 --- a/patches/net/minecraft/world/entity/EntityType.java.patch +++ b/patches/net/minecraft/world/entity/EntityType.java.patch @@ -1,21 +1,22 @@ --- 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 java.util.Set 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 +_,27 @@ 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, java.util.Collections.emptySet()); + } + + public EntityType( @@ -33,18 +34,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 java.util.Set 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 = java.util.Collections.unmodifiableSet(requiredFlags); } @Nullable @@ -88,30 +91,50 @@ 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 java.util.Set 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 final java.util.Set requiredFlags = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); + private Builder(EntityType.EntityFactory p_20696_, MobCategory p_20697_) { this.factory = p_20696_; this.category = p_20697_; -@@ -1314,6 +_,21 @@ +@@ -1309,11 +_,35 @@ 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) { ++ java.util.Collections.addAll(this.requiredFlags, requiredFlags); ++ return this; ++ } ++ + public EntityType.Builder setUpdateInterval(int interval) { + this.updateIntervalSupplier = t->interval; + return this; @@ -130,7 +153,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 +161,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..683a479f59 100644 --- a/patches/net/minecraft/world/inventory/MenuType.java.patch +++ b/patches/net/minecraft/world/inventory/MenuType.java.patch @@ -9,18 +9,49 @@ 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,18 +_,20 @@ + public static final MenuType STONECUTTER = register("stonecutter", StonecutterMenu::new); + private final FeatureFlagSet requiredFeatures; + private final MenuType.MenuSupplier constructor; ++ private final java.util.Set requiredFlags; + + 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)); ++ return Registry.register(BuiltInRegistries.MENU, p_39989_, new MenuType<>(p_39990_, FeatureFlags.VANILLA_SET, java.util.Collections.emptySet())); + } + + private static MenuType register(String p_267295_, MenuType.MenuSupplier p_266945_, FeatureFlag... p_267055_) { +- return Registry.register(BuiltInRegistries.MENU, p_267295_, new MenuType<>(p_266945_, FeatureFlags.REGISTRY.subset(p_267055_))); ++ return Registry.register(BuiltInRegistries.MENU, p_267295_, new MenuType<>(p_266945_, FeatureFlags.REGISTRY.subset(p_267055_), java.util.Collections.emptySet())); + } + +- public MenuType(MenuType.MenuSupplier p_267054_, FeatureFlagSet p_266909_) { ++ public MenuType(MenuType.MenuSupplier p_267054_, FeatureFlagSet p_266909_, java.util.Set requiredFlags) { + this.constructor = p_267054_; + this.requiredFeatures = p_266909_; ++ this.requiredFlags = java.util.Collections.unmodifiableSet(requiredFlags); + } public T create(int p_39986_, Inventory p_39987_) { - return this.constructor.create(p_39986_, p_39987_); -+ } -+ -+ @Override +@@ -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 java.util.Set 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..0cef5336a5 100644 --- a/patches/net/minecraft/world/item/BlockItem.java.patch +++ b/patches/net/minecraft/world/item/BlockItem.java.patch @@ -31,16 +31,25 @@ @Nullable public BlockPlaceContext updatePlacementContext(BlockPlaceContext p_40609_) { return p_40609_; -@@ -193,6 +_,12 @@ - - public void registerBlocks(Map p_40607_, Item p_40608_) { +@@ -195,6 +_,10 @@ 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()); } ++ 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..e83df034fc 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 java.util.Set 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 = java.util.Collections.unmodifiableSet(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 java.util.Set 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; ++ java.util.Set requiredFlags = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); public Item.Properties food(FoodProperties p_41490_) { return this.component(DataComponents.FOOD, p_41490_); -@@ -381,12 +_,18 @@ +@@ -381,12 +_,27 @@ return this.component(DataComponents.JUKEBOX_PLAYABLE, new JukeboxPlayable(new EitherHolder<>(p_350862_), true)); } @@ -175,11 +190,20 @@ + 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) { ++ 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..a7d29e5dd6 --- /dev/null +++ b/patches/net/minecraft/world/item/alchemy/Potion.java.patch @@ -0,0 +1,38 @@ +--- 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 final java.util.Set requiredFlags = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); + + 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 +_,16 @@ + @Override + public FeatureFlagSet requiredFeatures() { + return this.requiredFeatures; ++ } ++ ++ public Potion requiredFlags(net.neoforged.neoforge.flag.Flag... requiredFlags) { ++ java.util.Collections.addAll(this.requiredFlags, requiredFlags); ++ return this; ++ } ++ ++ @Override ++ public java.util.Set requiredFlags() { ++ return requiredFlags; + } + + public static String getName(Optional> p_330503_, String p_43493_) { 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..7a96813b0f 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,18 @@ --- a/net/minecraft/world/level/block/state/BlockBehaviour.java +++ b/net/minecraft/world/level/block/state/BlockBehaviour.java -@@ -111,6 +_,17 @@ +@@ -98,6 +_,7 @@ + protected final BlockBehaviour.Properties properties; + @Nullable + protected ResourceKey drops; ++ protected final java.util.Set requiredFlags; + + public BlockBehaviour(BlockBehaviour.Properties p_60452_) { + this.hasCollision = p_60452_.hasCollision; +@@ -110,7 +_,19 @@ + this.jumpFactor = p_60452_.jumpFactor; this.dynamicShape = p_60452_.dynamicShape; this.requiredFeatures = p_60452_.requiredFeatures; ++ this.requiredFlags = java.util.Collections.unmodifiableSet(p_60452_.requiredFlags); this.properties = p_60452_; + final ResourceKey lootTableCache = p_60452_.drops; + if (lootTableCache != null) { @@ -66,7 +76,7 @@ protected SoundType getSoundType(BlockState p_320941_) { return this.soundType; } -@@ -393,6 +_,13 @@ +@@ -393,6 +_,18 @@ return this.properties.destroyTime; } @@ -74,6 +84,11 @@ + return ((BlockStateBase)state).isAir; + } + ++ @Override ++ public final java.util.Set requiredFlags() { ++ return requiredFlags; ++ } ++ + // Neo: Holds the loot table for this block's drops. Used for getLootTable method. + private final java.util.function.Supplier> lootTableSupplier; + @@ -150,6 +165,22 @@ 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 final java.util.Set requiredFlags = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); + + private Properties() { + } +@@ -1020,6 +_,7 @@ + blockbehaviour$properties.offsetFunction = blockbehaviour$properties1.offsetFunction; + blockbehaviour$properties.spawnTerrainParticles = blockbehaviour$properties1.spawnTerrainParticles; + blockbehaviour$properties.requiredFeatures = blockbehaviour$properties1.requiredFeatures; ++ 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 +198,21 @@ } public BlockBehaviour.Properties ignitedByLava() { +@@ -1215,8 +_,17 @@ + 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) { ++ java.util.Collections.addAll(this.requiredFlags, requiredFlags); + return this; + } + diff --git a/src/main/java/net/neoforged/neoforge/client/ClientNeoForgeMod.java b/src/main/java/net/neoforged/neoforge/client/ClientNeoForgeMod.java index b683378daa..eb35c0cd12 100644 --- a/src/main/java/net/neoforged/neoforge/client/ClientNeoForgeMod.java +++ b/src/main/java/net/neoforged/neoforge/client/ClientNeoForgeMod.java @@ -59,6 +59,9 @@ public ClientNeoForgeMod(IEventBus modEventBus, ModContainer container) { spec.resetCaches(ModConfigSpec.RestartType.WORLD); } }); + + // reset flags when disconnecting from server + Minecraft.getInstance().getModdedFlagManager().reset(); }); } diff --git a/src/main/java/net/neoforged/neoforge/client/extensions/IMinecraftExtension.java b/src/main/java/net/neoforged/neoforge/client/extensions/IMinecraftExtension.java index 59f41a06c5..8ceb0896ed 100644 --- a/src/main/java/net/neoforged/neoforge/client/extensions/IMinecraftExtension.java +++ b/src/main/java/net/neoforged/neoforge/client/extensions/IMinecraftExtension.java @@ -9,6 +9,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.Screen; import net.neoforged.neoforge.client.ClientHooks; +import net.neoforged.neoforge.flag.FlagManager; /** * Extension interface for {@link Minecraft}. @@ -41,4 +42,6 @@ default void popGuiLayer() { default Locale getLocale() { return self().getLanguageManager().getJavaLocale(); } + + FlagManager getModdedFlagManager(); } diff --git a/src/main/java/net/neoforged/neoforge/common/NeoForgeEventHandler.java b/src/main/java/net/neoforged/neoforge/common/NeoForgeEventHandler.java index 2e2e14ab5f..8bda53e169 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,9 @@ 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) + PacketDistributor.sendToPlayer(sPlayer, new ClientboundSyncFlags(sPlayer.server.getModdedFlagManager())); } @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..2374199370 100644 --- a/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java +++ b/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java @@ -5,6 +5,7 @@ package net.neoforged.neoforge.common; +import com.google.common.collect.Sets; import com.mojang.datafixers.util.Either; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; @@ -82,6 +83,7 @@ import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; import net.neoforged.fml.event.lifecycle.FMLLoadCompleteEvent; import net.neoforged.fml.loading.progress.StartupNotificationManager; +import net.neoforged.neoforge.attachment.AttachmentType; import net.neoforged.neoforge.capabilities.CapabilityHooks; import net.neoforged.neoforge.common.advancements.critereon.ItemAbilityPredicate; import net.neoforged.neoforge.common.advancements.critereon.PiglinCurrencyItemPredicate; @@ -124,6 +126,7 @@ import net.neoforged.neoforge.common.loot.CanItemPerformAbility; import net.neoforged.neoforge.common.loot.IGlobalLootModifier; import net.neoforged.neoforge.common.loot.LootTableIdCondition; +import net.neoforged.neoforge.common.util.NeoForgeExtraCodecs; import net.neoforged.neoforge.common.world.BiomeModifier; import net.neoforged.neoforge.common.world.BiomeModifiers; import net.neoforged.neoforge.common.world.BiomeModifiers.AddFeaturesBiomeModifier; @@ -136,6 +139,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 +387,7 @@ 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); + 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); @@ -477,6 +483,11 @@ public void setItemMovement(ItemEntity entity) { public static final DeferredHolder MILK = DeferredHolder.create(Registries.FLUID, ResourceLocation.withDefaultNamespace("milk")); public static final DeferredHolder FLOWING_MILK = DeferredHolder.create(Registries.FLUID, ResourceLocation.withDefaultNamespace("flowing_milk")); + public static final DeferredRegister> ATTACHMENT_TYPES = DeferredRegister.create(NeoForgeRegistries.Keys.ATTACHMENT_TYPES, NeoForgeVersion.MOD_ID); + public static final DeferredHolder, AttachmentType>> LEVEL_FLAGS = ATTACHMENT_TYPES.register("modded_feature_flags", () -> AttachmentType.>builder(() -> Sets.newHashSet()) + .serialize(NeoForgeExtraCodecs.setOf(Flag.CODEC)) + .build()); + /** * Used in place of {@link DamageSources#magic()} for damage dealt by {@link MobEffects#POISON}. *

@@ -539,6 +550,7 @@ public NeoForgeMod(IEventBus modEventBus, Dist dist, ModContainer container) { INGREDIENT_TYPES.register(modEventBus); CONDITION_CODECS.register(modEventBus); GLOBAL_LOOT_MODIFIER_SERIALIZERS.register(modEventBus); + ATTACHMENT_TYPES.register(modEventBus); NeoForge.EVENT_BUS.addListener(this::serverStopping); container.registerConfig(ModConfig.Type.CLIENT, NeoForgeConfig.clientSpec); container.registerConfig(ModConfig.Type.SERVER, NeoForgeConfig.serverSpec); 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..dde9fc6a89 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,7 @@ import net.minecraft.core.Holder; import net.minecraft.resources.ResourceKey; import net.minecraft.world.level.LevelReader; +import net.neoforged.neoforge.flag.FlagManager; public interface ILevelReaderExtension { private LevelReader self() { @@ -37,4 +38,6 @@ default Holder holderOrThrow(ResourceKey key) { default Optional> holder(ResourceKey key) { return this.self().registryAccess().holder(key); } + + FlagManager getModdedFlagManager(); } 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..0c7ecf4082 100644 --- a/src/main/java/net/neoforged/neoforge/common/extensions/IMenuTypeExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IMenuTypeExtension.java @@ -5,16 +5,18 @@ package net.neoforged.neoforge.common.extensions; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.world.entity.player.Inventory; 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) { - return new MenuType<>(factory, FeatureFlags.DEFAULT_FLAGS); + static MenuType create(IContainerFactory factory, Flag... requiredFlags) { + return new MenuType<>(factory, FeatureFlags.DEFAULT_FLAGS, ReferenceOpenHashSet.of(requiredFlags)); } T create(int windowId, Inventory playerInv, RegistryFriendlyByteBuf extraData); diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IMinecraftServerExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IMinecraftServerExtension.java new file mode 100644 index 0000000000..3e9908eb4c --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IMinecraftServerExtension.java @@ -0,0 +1,12 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.common.extensions; + +import net.neoforged.neoforge.flag.FlagManager; + +public interface IMinecraftServerExtension { + FlagManager getModdedFlagManager(); +} diff --git a/src/main/java/net/neoforged/neoforge/flag/ClientboundSyncFlag.java b/src/main/java/net/neoforged/neoforge/flag/ClientboundSyncFlag.java new file mode 100644 index 0000000000..7928284065 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/flag/ClientboundSyncFlag.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.flag; + +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 ClientboundSyncFlag implements CustomPacketPayload { + public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(NeoForgeVersion.MOD_ID, "sync_flag")); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + Flag.STREAM_CODEC, payload -> payload.flag, + ByteBufCodecs.BOOL, payload -> payload.enabled, + ClientboundSyncFlag::new); + + final Flag flag; + final boolean enabled; + + public ClientboundSyncFlag(Flag flag, boolean enabled) { + this.flag = flag; + this.enabled = enabled; + } + + public void handle(IPayloadContext context) { + context.enqueueWork(() -> { + if (FMLEnvironment.dist.isClient()) + Minecraft.getInstance().getModdedFlagManager().syncFromRemote(this); + }); + } + + @Override + public Type type() { + return TYPE; + } +} 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..b08b346a40 --- /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 com.google.common.collect.Sets; +import java.util.Collection; +import java.util.Set; +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(Sets::newHashSetWithExpectedSize, Flag.STREAM_CODEC), payload -> payload.enabledFlags, + ClientboundSyncFlags::new); + + final Set enabledFlags; + + private ClientboundSyncFlags(Collection enabledFlags) { + this.enabledFlags = Set.copyOf(enabledFlags); + } + + public ClientboundSyncFlags(FlagManager flagManager) { + this(flagManager.getEnabledFlags()); + } + + public void handle(IPayloadContext context) { + context.enqueueWork(() -> { + if (FMLEnvironment.dist.isClient()) + Minecraft.getInstance().getModdedFlagManager().syncFromRemote(this); + }); + } + + @Override + public Type type() { + return TYPE; + } +} 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..b6d8644503 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/flag/Flag.java @@ -0,0 +1,77 @@ +/* + * 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 java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Stream; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; + +public final class Flag { + public static final Codec CODEC = ResourceLocation.CODEC.xmap(Flag::of, flag -> flag.identifier); + 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; + } + + public String namespace() { + return identifier.getNamespace(); + } + + 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 instanceof Flag other) + return identifier.equals(other.identifier); + return false; + } + + @Override + public int hashCode() { + return identifier.hashCode(); + } + + @Override + public String toString() { + return "Flag{" + identifier + '}'; + } + + public static Flag of(String namespace, String identifier) { + return of(ResourceLocation.fromNamespaceAndPath(namespace, identifier)); + } + + public static Flag of(ResourceLocation identifier) { + return FLAGS.computeIfAbsent(identifier, Flag::new); + } + + public static Collection getFlags() { + return FLAGS_VIEW; + } + + public static Stream flags() { + return FLAGS_VIEW.stream(); + } +} diff --git a/src/main/java/net/neoforged/neoforge/flag/FlagCommand.java b/src/main/java/net/neoforged/neoforge/flag/FlagCommand.java new file mode 100644 index 0000000000..06d887be15 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/flag/FlagCommand.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.flag; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +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.Component; + +public interface FlagCommand { + static ArgumentBuilder register() { + return Commands.literal("flag") + .requires(src -> src.hasPermission(Commands.LEVEL_ADMINS)) + .then(Commands.literal("enable") + .then(Commands.argument("flag", ResourceLocationArgument.id()) + .suggests((ctx, builder) -> { + var flags = Flag.flags().map(Flag::toStringShort); + return SharedSuggestionProvider.suggest(flags, builder); + }) + .executes(ctx -> setFlag(ctx, true)))) + .then(Commands.literal("disable") + .then(Commands.argument("flag", ResourceLocationArgument.id()) + .suggests((ctx, builder) -> { + var flags = ctx.getSource().getServer().getModdedFlagManager().enabledFlags().map(Flag::toStringShort); + return SharedSuggestionProvider.suggest(flags, builder); + }) + .executes(ctx -> setFlag(ctx, false)))); + } + + private static int setFlag(CommandContext context, boolean enable) throws CommandSyntaxException { + var flag = Flag.of(ResourceLocationArgument.getId(context, "flag")); + var src = context.getSource(); + var flags = src.getServer().getModdedFlagManager(); + var state = state(enable); + + if (flags.set(flag, enable)) + src.sendSuccess(() -> Component.literal(state).append(" Flag: ").append(flag.toStringShort()), true); + else + src.sendSuccess(() -> Component.literal("Flag ").append(flag.toStringShort()).append(" is already ").append(state), false); + + return Command.SINGLE_SUCCESS; + } + + private static String state(boolean enable) { + return enable ? "Enabled" : "Disabled"; + } +} 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..fd49defeec --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/flag/FlagElement.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.flag; + +import java.util.Set; + +public interface FlagElement { + Set requiredFlags(); + + default boolean isEnabled() { + return FlagManager.lookup().map(mgr -> mgr.isEnabled(this)).orElseGet(FlagManager::shouldBeEnabledDefault); + } +} 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..54c0f00d0b --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/flag/FlagManager.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.flag; + +import io.netty.util.internal.UnstableApi; +import it.unimi.dsi.fastutil.objects.Object2BooleanMap; +import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap; +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import net.minecraft.client.Minecraft; +import net.minecraft.world.level.Level; +import net.neoforged.fml.loading.FMLEnvironment; +import net.neoforged.neoforge.common.NeoForgeMod; +import net.neoforged.neoforge.common.extensions.ILevelReaderExtension; +import net.neoforged.neoforge.data.loading.DatagenModLoader; +import net.neoforged.neoforge.server.ServerLifecycleHooks; +import org.jetbrains.annotations.ApiStatus; + +public final class FlagManager { + private final Object2BooleanMap flags = new Object2BooleanOpenHashMap<>(); + private final Collection flagsView = Collections.unmodifiableCollection(flags.keySet()); + + private final ChangeListener listener; + + @ApiStatus.Internal + public FlagManager(ChangeListener listener) { + this.listener = listener; + + flags.defaultReturnValue(false); + } + + public void loadFromLevel(Level level) { + level.getExistingData(NeoForgeMod.LEVEL_FLAGS).ifPresent(enabledFlags -> { + reset(); + enabledFlags.forEach(flag -> flags.put(flag, true)); + }); + } + + public void saveToLevel(Level level) { + level.setData(NeoForgeMod.LEVEL_FLAGS, enabledFlags().collect(Collectors.toSet())); + } + + void syncFromRemote(ClientboundSyncFlag payload) { + flags.put(payload.flag, payload.enabled); + } + + void syncFromRemote(ClientboundSyncFlags payload) { + reset(); + payload.enabledFlags.forEach(flag -> flags.put(flag, true)); + } + + public void reset() { + flags.clear(); + } + + public boolean isEnabled(Flag flag) { + return flags.getBoolean(flag); + } + + public boolean isEnabled(Flag flag, Flag... flags) { + if (!isEnabled(flag)) + return false; + + for (var other : flags) { + if (!isEnabled(other)) + return false; + } + + return true; + } + + public boolean isEnabled(Iterable flags) { + for (var flag : flags) { + if (!isEnabled(flag)) + return false; + } + + return true; + } + + public boolean isEnabled(FlagElement element) { + return isEnabled(element.requiredFlags()); + } + + public boolean set(Flag flag, boolean enabled) { + if (flags.getBoolean(flag) != enabled) { + flags.put(flag, enabled); + listener.accept(flag, enabled); + return true; + } + + return false; + } + + public boolean toggle(Flag flag) { + return set(flag, !flags.getBoolean(flag)); + } + + public Collection getEnabledFlags() { + return enabledFlags().toList(); + } + + public Stream enabledFlags() { + return flagsView.stream().filter(this::isEnabled); + } + + /** + * Prefer to lookup instance via {@linkplain ILevelReaderExtension#getModdedFlagManager()} + */ + @UnstableApi + public static Optional lookup() { + return lookupClient().or(() -> { + var server = ServerLifecycleHooks.getCurrentServer(); + return server == null ? Optional.empty() : Optional.of(server.getModdedFlagManager()); + }); + } + + private static Optional lookupClient() { + if (DatagenModLoader.isRunningDataGen()) + return Optional.empty(); + if (FMLEnvironment.dist.isClient()) + return Optional.ofNullable(Minecraft.getInstance()).map(Minecraft::getModdedFlagManager); + return Optional.empty(); + } + + public static boolean shouldBeEnabledDefault() { + return DatagenModLoader.isRunningDataGen(); + } + + @FunctionalInterface + public interface ChangeListener { + void accept(Flag flag, boolean enabled); + } +} 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..394cd77169 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/flag/RequiredFlagsCondition.java @@ -0,0 +1,45 @@ +/* + * 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; + +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 FlagManager.lookup().map(manager -> manager.isEnabled(requiredFlags)).orElseGet(FlagManager::shouldBeEnabledDefault); + } + + @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/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..5e094599a9 100644 --- a/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java +++ b/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java @@ -7,6 +7,8 @@ import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.flag.ClientboundSyncFlag; +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 +96,14 @@ private static void register(final RegisterPayloadHandlersEvent event) { .playToClient( ClientboundCustomSetTimePayload.TYPE, ClientboundCustomSetTimePayload.STREAM_CODEC, - ClientPayloadHandler::handle); + ClientPayloadHandler::handle) + .playToClient( + ClientboundSyncFlag.TYPE, + ClientboundSyncFlag.STREAM_CODEC, + ClientboundSyncFlag::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..b8d3b0a074 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.FlagCommand; 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(FlagCommand.register())); } } 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..680e269312 100644 --- a/testframework/src/main/java/net/neoforged/testframework/junit/EphemeralTestServerProvider.java +++ b/testframework/src/main/java/net/neoforged/testframework/junit/EphemeralTestServerProvider.java @@ -50,6 +50,7 @@ import net.minecraft.world.level.levelgen.presets.WorldPresets; import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.PrimaryLevelData; +import net.neoforged.neoforge.flag.FlagManager; import net.neoforged.neoforge.server.ServerLifecycleHooks; import org.apache.logging.log4j.LogManager; import org.junit.jupiter.api.extension.ExtendWith; @@ -143,6 +144,7 @@ public static class JUnitServer extends MinecraftServer { rules.getRule(GameRules.RULE_WEATHER_CYCLE).set(false, null); }); private static final WorldOptions WORLD_OPTIONS = new WorldOptions(0L, false, false); + private final FlagManager moddedFlagManager = new FlagManager((flag, enabled) -> { /* NOOP: No syncing during testing */ }); public static JUnitServer create( Thread thread, Path tempDir, LevelStorageSource.LevelStorageAccess access, PackRepository resources) { @@ -314,5 +316,10 @@ protected SampleLogger getTickTimeLogger() { public boolean isTickTimeLoggingEnabled() { return false; } + + @Override + public final FlagManager getModdedFlagManager() { + return moddedFlagManager; + } } } 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