From fd544dd414e56798cd965bd5027b7a037b8cf023 Mon Sep 17 00:00:00 2001 From: gigaherz Date: Tue, 5 Oct 2021 00:10:05 +0200 Subject: [PATCH] Add ability for people to add custom pack finders (RepositorySources) (#8121) Added AddPackFindersEvent which lets you add additional resource/data pack sources to the game. Added PathResourcePack as a utility to allow easy providing of a subfolder in a mod as a resource pack. --- .../net/minecraft/client/Minecraft.java.patch | 4 +- .../ServerSelectionList.java.patch | 2 +- .../CreateWorldScreen.java.patch | 2 +- .../ClientPacketListener.java.patch | 4 +- .../multiplayer/ServerStatusPinger.java.patch | 2 +- .../server/MinecraftServer.java.patch | 2 +- .../packs/AbstractPackResources.java.patch | 13 + .../repository/PackRepository.java.patch | 4 +- .../client/ForgeHooksClient.java | 168 ++++++++++- .../common/data/ExistingFileHelper.java | 4 +- .../event/AddPackFindersEvent.java | 59 ++++ .../minecraftforge/fmlclient/ClientHooks.java | 271 ++---------------- .../fmlclient/ClientModLoader.java | 22 +- .../gui/screen/LoadingErrorScreen.java | 1 - .../fmlclient/gui/screen/ModListScreen.java | 6 +- .../fmllegacy/packs/ModFileResourcePack.java | 124 +------- .../fmllegacy/packs/ResourcePackLoader.java | 47 +-- .../server/ServerLifecycleHooks.java | 21 +- .../resource/PathResourcePack.java | 193 +++++++++++++ .../resources/META-INF/accesstransformer.cfg | 2 +- .../debug/AddPackFinderEventTest.java | 65 +++++ src/test/resources/META-INF/mods.toml | 2 + .../assets/minecraft/blockstates/dirt.json | 5 + .../test_nested_resource_pack/pack.mcmeta | 6 + 24 files changed, 600 insertions(+), 429 deletions(-) create mode 100644 patches/minecraft/net/minecraft/server/packs/AbstractPackResources.java.patch create mode 100644 src/main/java/net/minecraftforge/event/AddPackFindersEvent.java create mode 100644 src/main/java/net/minecraftforge/resource/PathResourcePack.java create mode 100644 src/test/java/net/minecraftforge/debug/AddPackFinderEventTest.java create mode 100644 src/test/resources/test_nested_resource_pack/assets/minecraft/blockstates/dirt.json create mode 100644 src/test/resources/test_nested_resource_pack/pack.mcmeta diff --git a/patches/minecraft/net/minecraft/client/Minecraft.java.patch b/patches/minecraft/net/minecraft/client/Minecraft.java.patch index 08dce6406b..9e206eb9ac 100644 --- a/patches/minecraft/net/minecraft/client/Minecraft.java.patch +++ b/patches/minecraft/net/minecraft/client/Minecraft.java.patch @@ -396,7 +396,7 @@ IntegratedServer integratedserver = this.f_91007_; this.f_91007_ = null; this.f_91063_.m_109150_(); -+ net.minecraftforge.fmlclient.ClientHooks.firePlayerLogout(this.f_91072_, this.f_91074_); ++ net.minecraftforge.client.ForgeHooksClient.firePlayerLogout(this.f_91072_, this.f_91074_); this.f_91072_ = null; NarratorChatListener.f_93311_.m_93328_(); this.m_91362_(p_91321_); @@ -409,7 +409,7 @@ this.f_91065_.m_93089_(); this.f_91008_ = null; this.f_91010_ = false; -+ net.minecraftforge.fmlclient.ClientHooks.handleClientWorldClosing(f_91073_); ++ net.minecraftforge.client.ForgeHooksClient.handleClientWorldClosing(f_91073_); this.f_91004_.m_90740_(); } diff --git a/patches/minecraft/net/minecraft/client/gui/screens/multiplayer/ServerSelectionList.java.patch b/patches/minecraft/net/minecraft/client/gui/screens/multiplayer/ServerSelectionList.java.patch index 088affe534..5c71272ea2 100644 --- a/patches/minecraft/net/minecraft/client/gui/screens/multiplayer/ServerSelectionList.java.patch +++ b/patches/minecraft/net/minecraft/client/gui/screens/multiplayer/ServerSelectionList.java.patch @@ -4,7 +4,7 @@ this.f_99855_.m_99707_(list1); } -+ net.minecraftforge.fmlclient.ClientHooks.drawForgePingInfo(this.f_99855_, f_99857_, p_99879_, p_99882_, p_99881_, p_99883_, i1, j1); ++ net.minecraftforge.client.ForgeHooksClient.drawForgePingInfo(this.f_99855_, f_99857_, p_99879_, p_99882_, p_99881_, p_99883_, i1, j1); + if (this.f_99856_.f_91066_.f_92051_ || p_99887_) { RenderSystem.m_157456_(0, ServerSelectionList.f_99759_); diff --git a/patches/minecraft/net/minecraft/client/gui/screens/worldselection/CreateWorldScreen.java.patch b/patches/minecraft/net/minecraft/client/gui/screens/worldselection/CreateWorldScreen.java.patch index 69d2845f2a..622d4ef496 100644 --- a/patches/minecraft/net/minecraft/client/gui/screens/worldselection/CreateWorldScreen.java.patch +++ b/patches/minecraft/net/minecraft/client/gui/screens/worldselection/CreateWorldScreen.java.patch @@ -13,7 +13,7 @@ File file1 = path.toFile(); if (this.f_100832_ == null) { this.f_100832_ = new PackRepository(PackType.SERVER_DATA, new ServerPacksSource(), new FolderRepositorySource(file1, PackSource.f_10527_)); -+ net.minecraftforge.fmllegacy.packs.ResourcePackLoader.loadResourcePacks(this.f_100832_, net.minecraftforge.fmllegacy.server.ServerLifecycleHooks::buildPackFinder); ++ net.minecraftforge.fmllegacy.packs.ResourcePackLoader.loadResourcePacks(this.f_100832_, net.minecraftforge.fmllegacy.server.ServerLifecycleHooks::buildPackFinderNew); this.f_100832_.m_10506_(); } diff --git a/patches/minecraft/net/minecraft/client/multiplayer/ClientPacketListener.java.patch b/patches/minecraft/net/minecraft/client/multiplayer/ClientPacketListener.java.patch index 3e847199a3..13e289795d 100644 --- a/patches/minecraft/net/minecraft/client/multiplayer/ClientPacketListener.java.patch +++ b/patches/minecraft/net/minecraft/client/multiplayer/ClientPacketListener.java.patch @@ -4,7 +4,7 @@ this.f_104888_.f_91064_.m_113434_(); this.f_104888_.f_91074_.m_172530_(); -+ net.minecraftforge.fmlclient.ClientHooks.firePlayerLogin(this.f_104888_.f_91072_, this.f_104888_.f_91074_, this.f_104888_.m_91403_().m_6198_()); ++ net.minecraftforge.client.ForgeHooksClient.firePlayerLogin(this.f_104888_.f_91072_, this.f_104888_.f_91074_, this.f_104888_.m_91403_().m_6198_()); int i = p_105030_.m_132398_(); this.f_104888_.f_91074_.m_20234_(i); this.f_104889_.m_104630_(i, this.f_104888_.f_91074_); @@ -43,7 +43,7 @@ + localplayer1.updateSyncFields(localplayer); // Forge: fix MC-10657 localplayer1.m_172530_(); localplayer1.m_108748_(s); -+ net.minecraftforge.fmlclient.ClientHooks.firePlayerRespawn(this.f_104888_.f_91072_, localplayer, localplayer1, localplayer1.f_108617_.m_6198_()); ++ net.minecraftforge.client.ForgeHooksClient.firePlayerRespawn(this.f_104888_.f_91072_, localplayer, localplayer1, localplayer1.f_108617_.m_6198_()); this.f_104889_.m_104630_(i, localplayer1); localplayer1.m_146922_(-180.0F); localplayer1.f_108618_ = new KeyboardInput(this.f_104888_.f_91066_); diff --git a/patches/minecraft/net/minecraft/client/multiplayer/ServerStatusPinger.java.patch b/patches/minecraft/net/minecraft/client/multiplayer/ServerStatusPinger.java.patch index 2960aa3981..0678850644 100644 --- a/patches/minecraft/net/minecraft/client/multiplayer/ServerStatusPinger.java.patch +++ b/patches/minecraft/net/minecraft/client/multiplayer/ServerStatusPinger.java.patch @@ -4,7 +4,7 @@ p_105461_.run(); } -+ net.minecraftforge.fmlclient.ClientHooks.processForgeListPingData(serverstatus, p_105460_); ++ net.minecraftforge.client.ForgeHooksClient.processForgeListPingData(serverstatus, p_105460_); this.f_105477_ = Util.m_137550_(); connection.m_129512_(new ServerboundPingRequestPacket(this.f_105477_)); this.f_105475_ = true; diff --git a/patches/minecraft/net/minecraft/server/MinecraftServer.java.patch b/patches/minecraft/net/minecraft/server/MinecraftServer.java.patch index 803b8bb6c9..9bef3147ea 100644 --- a/patches/minecraft/net/minecraft/server/MinecraftServer.java.patch +++ b/patches/minecraft/net/minecraft/server/MinecraftServer.java.patch @@ -167,7 +167,7 @@ } public static DataPackConfig m_129819_(PackRepository p_129820_, DataPackConfig p_129821_, boolean p_129822_) { -+ net.minecraftforge.fmllegacy.packs.ResourcePackLoader.loadResourcePacks(p_129820_, net.minecraftforge.fmllegacy.server.ServerLifecycleHooks::buildPackFinder); ++ net.minecraftforge.fmllegacy.packs.ResourcePackLoader.loadResourcePacks(p_129820_, net.minecraftforge.fmllegacy.server.ServerLifecycleHooks::buildPackFinderNew); p_129820_.m_10506_(); + DataPackConfig.f_45842_.addModPacks(net.minecraftforge.common.ForgeHooks.getModPacks()); + p_129821_.addModPacks(net.minecraftforge.common.ForgeHooks.getModPacks()); diff --git a/patches/minecraft/net/minecraft/server/packs/AbstractPackResources.java.patch b/patches/minecraft/net/minecraft/server/packs/AbstractPackResources.java.patch new file mode 100644 index 0000000000..964e74f517 --- /dev/null +++ b/patches/minecraft/net/minecraft/server/packs/AbstractPackResources.java.patch @@ -0,0 +1,13 @@ +--- a/net/minecraft/server/packs/AbstractPackResources.java ++++ b/net/minecraft/server/packs/AbstractPackResources.java +@@ -120,4 +_,10 @@ + public String m_8017_() { + return this.f_10203_.getName(); + } ++ ++ @Override ++ public String toString() ++ { ++ return String.format("%s: %s", getClass().getName(), f_10203_.getPath()); ++ } + } diff --git a/patches/minecraft/net/minecraft/server/packs/repository/PackRepository.java.patch b/patches/minecraft/net/minecraft/server/packs/repository/PackRepository.java.patch index f9a0cffa9d..b244e129c4 100644 --- a/patches/minecraft/net/minecraft/server/packs/repository/PackRepository.java.patch +++ b/patches/minecraft/net/minecraft/server/packs/repository/PackRepository.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/server/packs/repository/PackRepository.java +++ b/net/minecraft/server/packs/repository/PackRepository.java -@@ -24,12 +_,12 @@ +@@ -24,13 +_,14 @@ public PackRepository(Pack.PackConstructor p_10502_, RepositorySource... p_10503_) { this.f_10500_ = p_10502_; @@ -14,8 +14,10 @@ + this((p_143894_, p_143895_, p_143896_, p_143897_, p_143898_, p_143899_, p_143900_, hidden) -> { + return new Pack(p_143894_, p_143895_, p_143896_, p_143897_, p_143898_, p_143890_, p_143899_, p_143900_, hidden); }, p_143891_); ++ net.minecraftforge.fml.ModLoader.get().postEvent(new net.minecraftforge.event.AddPackFindersEvent(p_143890_, f_10497_::add)); } + public void m_10506_() { @@ -91,6 +_,10 @@ @Nullable public Pack m_10507_(String p_10508_) { diff --git a/src/main/java/net/minecraftforge/client/ForgeHooksClient.java b/src/main/java/net/minecraftforge/client/ForgeHooksClient.java index 184155b4bf..591156fe51 100644 --- a/src/main/java/net/minecraftforge/client/ForgeHooksClient.java +++ b/src/main/java/net/minecraftforge/client/ForgeHooksClient.java @@ -21,10 +21,18 @@ package net.minecraftforge.client; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.*; +import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen; import net.minecraft.client.model.geom.ModelLayerLocation; import net.minecraft.client.model.geom.builders.LayerDefinition; -import net.minecraft.client.renderer.entity.EntityRendererProvider; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.multiplayer.MultiPlayerGameMode; +import net.minecraft.client.multiplayer.ServerData; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.network.Connection; +import net.minecraft.network.chat.TextComponent; +import net.minecraft.network.protocol.status.ServerStatus; import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.level.block.state.BlockState; import com.mojang.blaze3d.platform.Window; @@ -95,17 +103,22 @@ import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.model.TransformationHelper; import net.minecraftforge.eventbus.api.Event; import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.ModLoader; -import net.minecraftforge.fml.VersionChecker; +import net.minecraftforge.fml.*; import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fmlclient.ExtendedServerListData; import net.minecraftforge.fmlclient.registry.ClientRegistry; -import net.minecraftforge.fml.StartupMessageManager; +import net.minecraftforge.fmllegacy.ForgeI18n; +import net.minecraftforge.fmllegacy.network.FMLNetworkConstants; +import net.minecraftforge.fmllegacy.network.NetworkRegistry; import net.minecraftforge.registries.ForgeRegistries; -import net.minecraftforge.resource.ReloadRequirements; +import net.minecraftforge.registries.GameData; import net.minecraftforge.resource.VanillaResourceType; import net.minecraftforge.versions.forge.ForgeVersion; +import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; import org.lwjgl.opengl.GL13; import javax.annotation.Nonnull; @@ -114,13 +127,10 @@ import java.io.File; import java.io.IOException; import java.nio.Buffer; import java.nio.ByteBuffer; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Random; -import java.util.Set; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; +import java.util.stream.Collectors; import java.util.stream.Stream; import static net.minecraftforge.client.event.RenderGameOverlayEvent.ElementType.BOSSINFO; @@ -136,6 +146,7 @@ import net.minecraft.client.renderer.entity.ItemRenderer; public class ForgeHooksClient { private static final Logger LOGGER = LogManager.getLogger(); + private static final Marker CLIENTHOOKS = MarkerManager.getMarker("CLIENTHOOKS"); //private static final ResourceLocation ITEM_GLINT = new ResourceLocation("textures/misc/enchanted_item_glint.png"); @@ -837,6 +848,141 @@ public class ForgeHooksClient layerDefinitions.forEach((k, v) -> builder.put(k, v.get())); } + public static void processForgeListPingData(ServerStatus packet, ServerData target) + { + if (packet.getForgeData() != null) { + final Map mods = packet.getForgeData().getRemoteModData(); + final Map> remoteChannels = packet.getForgeData().getRemoteChannels(); + final int fmlver = packet.getForgeData().getFMLNetworkVersion(); + + boolean fmlNetMatches = fmlver == FMLNetworkConstants.FMLNETVERSION; + boolean channelsMatch = NetworkRegistry.checkListPingCompatibilityForClient(remoteChannels); + AtomicBoolean result = new AtomicBoolean(true); + final List extraClientMods = new ArrayList<>(); + ModList.get().forEachModContainer((modid, mc) -> + mc.getCustomExtension(IExtensionPoint.DisplayTest.class).ifPresent(ext-> { + boolean foundModOnServer = ext.remoteVersionTest().test(mods.get(modid), true); + result.compareAndSet(true, foundModOnServer); + if (!foundModOnServer) { + extraClientMods.add(modid); + } + }) + ); + boolean modsMatch = result.get(); + + final Map extraServerMods = mods.entrySet().stream(). + filter(e -> !Objects.equals(FMLNetworkConstants.IGNORESERVERONLY, e.getValue())). + filter(e -> !ModList.get().isLoaded(e.getKey())). + collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + LOGGER.debug(CLIENTHOOKS, "Received FML ping data from server at {}: FMLNETVER={}, mod list is compatible : {}, channel list is compatible: {}, extra server mods: {}", target.ip, fmlver, modsMatch, channelsMatch, extraServerMods); + + String extraReason = null; + + if (!extraServerMods.isEmpty()) { + extraReason = "fml.menu.multiplayer.extraservermods"; + LOGGER.info(CLIENTHOOKS, ForgeI18n.parseMessage(extraReason) + ": {}", extraServerMods.entrySet().stream() + .map(e -> e.getKey() + "@" + e.getValue()) + .collect(Collectors.joining(", "))); + } + if (!modsMatch) { + extraReason = "fml.menu.multiplayer.modsincompatible"; + LOGGER.info(CLIENTHOOKS, "Client has mods that are missing on server: {}", extraClientMods); + } + if (!channelsMatch) { + extraReason = "fml.menu.multiplayer.networkincompatible"; + } + + if (fmlver < FMLNetworkConstants.FMLNETVERSION) { + extraReason = "fml.menu.multiplayer.serveroutdated"; + } + if (fmlver > FMLNetworkConstants.FMLNETVERSION) { + extraReason = "fml.menu.multiplayer.clientoutdated"; + } + target.forgeData = new ExtendedServerListData("FML", extraServerMods.isEmpty() && fmlNetMatches && channelsMatch && modsMatch, mods.size(), extraReason, packet.getForgeData().isTruncated()); + } else { + target.forgeData = new ExtendedServerListData("VANILLA", NetworkRegistry.canConnectToVanillaServer(),0, null); + } + + } + + private static final ResourceLocation ICON_SHEET = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/icons.png"); + public static void drawForgePingInfo(JoinMultiplayerScreen gui, ServerData target, PoseStack mStack, int x, int y, int width, int relativeMouseX, int relativeMouseY) { + int idx; + String tooltip; + if (target.forgeData == null) + return; + switch (target.forgeData.type) { + case "FML": + if (target.forgeData.isCompatible) { + idx = 0; + tooltip = ForgeI18n.parseMessage("fml.menu.multiplayer.compatible", target.forgeData.numberOfMods); + } else { + idx = 16; + if(target.forgeData.extraReason != null) { + String extraReason = ForgeI18n.parseMessage(target.forgeData.extraReason); + tooltip = ForgeI18n.parseMessage("fml.menu.multiplayer.incompatible.extra", extraReason); + } else { + tooltip = ForgeI18n.parseMessage("fml.menu.multiplayer.incompatible"); + } + } + if (target.forgeData.truncated) + { + tooltip += "\n" + ForgeI18n.parseMessage("fml.menu.multiplayer.truncated"); + } + break; + case "VANILLA": + if (target.forgeData.isCompatible) { + idx = 48; + tooltip = ForgeI18n.parseMessage("fml.menu.multiplayer.vanilla"); + } else { + idx = 80; + tooltip = ForgeI18n.parseMessage("fml.menu.multiplayer.vanilla.incompatible"); + } + break; + default: + idx = 64; + tooltip = ForgeI18n.parseMessage("fml.menu.multiplayer.unknown", target.forgeData.type); + } + + RenderSystem.setShaderTexture(0, ICON_SHEET); + GuiComponent.blit(mStack, x + width - 18, y + 10, 16, 16, 0, idx, 16, 16, 256, 256); + + if(relativeMouseX > width - 15 && relativeMouseX < width && relativeMouseY > 10 && relativeMouseY < 26) { + //this is not the most proper way to do it, + //but works best here and has the least maintenance overhead + gui.setToolTip(Arrays.stream(tooltip.split("\n")).map(TextComponent::new).collect(Collectors.toList())); + } + } + + private static Connection getClientToServerNetworkManager() + { + return Minecraft.getInstance().getConnection()!=null ? Minecraft.getInstance().getConnection().getConnection() : null; + } + + public static void handleClientWorldClosing(ClientLevel world) + { + Connection client = getClientToServerNetworkManager(); + // ONLY revert a non-local connection + if (client != null && !client.isMemoryConnection()) + { + GameData.revertToFrozen(); + } + } + + public static void firePlayerLogin(MultiPlayerGameMode pc, LocalPlayer player, Connection networkManager) { + MinecraftForge.EVENT_BUS.post(new ClientPlayerNetworkEvent.LoggedInEvent(pc, player, networkManager)); + } + + public static void firePlayerLogout(MultiPlayerGameMode pc, LocalPlayer player) { + MinecraftForge.EVENT_BUS.post(new ClientPlayerNetworkEvent.LoggedOutEvent(pc, player, player != null ? player.connection != null ? player.connection.getConnection() : null : null)); + } + + public static void firePlayerRespawn(MultiPlayerGameMode pc, LocalPlayer oldPlayer, LocalPlayer newPlayer, Connection networkManager) { + MinecraftForge.EVENT_BUS.post(new ClientPlayerNetworkEvent.RespawnEvent(pc, oldPlayer, newPlayer, networkManager)); + } + + @Mod.EventBusSubscriber(value = Dist.CLIENT, modid="forge", bus= Mod.EventBusSubscriber.Bus.MOD) public static class ClientEvents { diff --git a/src/main/java/net/minecraftforge/common/data/ExistingFileHelper.java b/src/main/java/net/minecraftforge/common/data/ExistingFileHelper.java index 6456c03a91..c1a0dc4d8d 100644 --- a/src/main/java/net/minecraftforge/common/data/ExistingFileHelper.java +++ b/src/main/java/net/minecraftforge/common/data/ExistingFileHelper.java @@ -47,8 +47,8 @@ import net.minecraft.resources.ResourceLocation; import net.minecraftforge.client.model.generators.ModelBuilder; import net.minecraftforge.fml.ModList; import net.minecraftforge.forge.event.lifecycle.GatherDataEvent; -import net.minecraftforge.fmllegacy.packs.ModFileResourcePack; import net.minecraftforge.forgespi.language.IModFileInfo; +import net.minecraftforge.resource.PathResourcePack; import javax.annotation.Nullable; @@ -127,7 +127,7 @@ public class ExistingFileHelper { for (String existingMod : existingMods) { IModFileInfo modFileInfo = ModList.get().getModFileById(existingMod); if (modFileInfo != null) { - PackResources pack = new ModFileResourcePack(modFileInfo.getFile()); + PackResources pack = new PathResourcePack(modFileInfo.getFile().getFileName(), modFileInfo.getFile().findResource("")); this.clientResources.add(pack); this.serverData.add(pack); } diff --git a/src/main/java/net/minecraftforge/event/AddPackFindersEvent.java b/src/main/java/net/minecraftforge/event/AddPackFindersEvent.java new file mode 100644 index 0000000000..052c55db34 --- /dev/null +++ b/src/main/java/net/minecraftforge/event/AddPackFindersEvent.java @@ -0,0 +1,59 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2021. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.event; + +import net.minecraft.server.packs.PackType; +import net.minecraft.server.packs.repository.RepositorySource; +import net.minecraftforge.eventbus.api.Event; +import net.minecraftforge.fml.event.IModBusEvent; + +import java.util.function.Consumer; + +/** + * Fired on PackRepository creation to allow mods to add new pack finders. + */ +public class AddPackFindersEvent extends Event implements IModBusEvent +{ + private final PackType packType; + private final Consumer sources; + + public AddPackFindersEvent(PackType packType, Consumer sources) + { + this.packType = packType; + this.sources = sources; + } + + /** + * Adds a new source to the list of pack finders. + * @param source the pack finder + */ + public void addRepositorySource(RepositorySource source) + { + sources.accept(source); + } + + /** + * @return the {@link PackType} of the resource manager being constructed. + */ + public PackType getPackType() + { + return packType; + } +} diff --git a/src/main/java/net/minecraftforge/fmlclient/ClientHooks.java b/src/main/java/net/minecraftforge/fmlclient/ClientHooks.java index 4c72f5e7bf..db68da180e 100644 --- a/src/main/java/net/minecraftforge/fmlclient/ClientHooks.java +++ b/src/main/java/net/minecraftforge/fmlclient/ClientHooks.java @@ -19,304 +19,73 @@ package net.minecraftforge.fmlclient; -import java.io.File; -import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; - -import javax.annotation.Nullable; - -import com.google.common.collect.HashBasedTable; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.SetMultimap; -import com.google.common.collect.Sets; -import com.google.common.collect.Table; -import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.client.player.LocalPlayer; -import net.minecraft.client.gui.GuiComponent; import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen; import net.minecraft.client.multiplayer.MultiPlayerGameMode; -import net.minecraft.network.chat.TextComponent; -import net.minecraftforge.client.event.ClientPlayerNetworkEvent; -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.fml.IExtensionPoint; -import net.minecraftforge.fmllegacy.ForgeI18n; -import net.minecraftforge.fml.ModList; -import net.minecraftforge.fmllegacy.network.FMLNetworkConstants; -import net.minecraftforge.fmllegacy.network.NetworkRegistry; -import net.minecraftforge.versions.forge.ForgeVersion; -import org.apache.commons.lang3.tuple.Pair; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.MarkerManager; +import net.minecraftforge.client.ForgeHooksClient; -import com.google.common.base.MoreObjects; -import com.google.common.base.Strings; - -import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ServerData; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.network.Connection; import net.minecraft.network.protocol.status.ServerStatus; -import net.minecraft.server.packs.AbstractPackResources; -import net.minecraft.server.packs.resources.FallbackResourceManager; -import net.minecraft.server.packs.PackResources; -import net.minecraft.server.packs.resources.SimpleReloadableResourceManager; import net.minecraft.resources.ResourceLocation; -import net.minecraftforge.fml.util.ObfuscationReflectionHelper; -import net.minecraftforge.forgespi.language.IModInfo; -import net.minecraftforge.fmllegacy.packs.ModFileResourcePack; -import net.minecraftforge.registries.GameData; +@Deprecated(since="1.18", forRemoval = true) public class ClientHooks { - private static final Logger LOGGER = LogManager.getLogger(); - private static final Marker CLIENTHOOKS = MarkerManager.getMarker("CLIENTHOOKS"); - - private static final ResourceLocation iconSheet = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/icons.png"); - @Nullable - + @Deprecated(since="1.18", forRemoval = true) public static void processForgeListPingData(ServerStatus packet, ServerData target) { - if (packet.getForgeData() != null) { - final Map mods = packet.getForgeData().getRemoteModData(); - final Map> remoteChannels = packet.getForgeData().getRemoteChannels(); - final int fmlver = packet.getForgeData().getFMLNetworkVersion(); - - boolean fmlNetMatches = fmlver == FMLNetworkConstants.FMLNETVERSION; - boolean channelsMatch = NetworkRegistry.checkListPingCompatibilityForClient(remoteChannels); - AtomicBoolean result = new AtomicBoolean(true); - final List extraClientMods = new ArrayList<>(); - ModList.get().forEachModContainer((modid, mc) -> - mc.getCustomExtension(IExtensionPoint.DisplayTest.class).ifPresent(ext-> { - boolean foundModOnServer = ext.remoteVersionTest().test(mods.get(modid), true); - result.compareAndSet(true, foundModOnServer); - if (!foundModOnServer) { - extraClientMods.add(modid); - } - }) - ); - boolean modsMatch = result.get(); - - final Map extraServerMods = mods.entrySet().stream(). - filter(e -> !Objects.equals(FMLNetworkConstants.IGNORESERVERONLY, e.getValue())). - filter(e -> !ModList.get().isLoaded(e.getKey())). - collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - LOGGER.debug(CLIENTHOOKS, "Received FML ping data from server at {}: FMLNETVER={}, mod list is compatible : {}, channel list is compatible: {}, extra server mods: {}", target.ip, fmlver, modsMatch, channelsMatch, extraServerMods); - - String extraReason = null; - - if (!extraServerMods.isEmpty()) { - extraReason = "fml.menu.multiplayer.extraservermods"; - LOGGER.info(CLIENTHOOKS, ForgeI18n.parseMessage(extraReason) + ": {}", extraServerMods.entrySet().stream() - .map(e -> e.getKey() + "@" + e.getValue()) - .collect(Collectors.joining(", "))); - } - if (!modsMatch) { - extraReason = "fml.menu.multiplayer.modsincompatible"; - LOGGER.info(CLIENTHOOKS, "Client has mods that are missing on server: {}", extraClientMods); - } - if (!channelsMatch) { - extraReason = "fml.menu.multiplayer.networkincompatible"; - } - - if (fmlver < FMLNetworkConstants.FMLNETVERSION) { - extraReason = "fml.menu.multiplayer.serveroutdated"; - } - if (fmlver > FMLNetworkConstants.FMLNETVERSION) { - extraReason = "fml.menu.multiplayer.clientoutdated"; - } - target.forgeData = new ExtendedServerListData("FML", extraServerMods.isEmpty() && fmlNetMatches && channelsMatch && modsMatch, mods.size(), extraReason, packet.getForgeData().isTruncated()); - } else { - target.forgeData = new ExtendedServerListData("VANILLA", NetworkRegistry.canConnectToVanillaServer(),0, null); - } - + ForgeHooksClient.processForgeListPingData(packet, target); } - public static void drawForgePingInfo(JoinMultiplayerScreen gui, ServerData target, PoseStack mStack, int x, int y, int width, int relativeMouseX, int relativeMouseY) { - int idx; - String tooltip; - if (target.forgeData == null) - return; - switch (target.forgeData.type) { - case "FML": - if (target.forgeData.isCompatible) { - idx = 0; - tooltip = ForgeI18n.parseMessage("fml.menu.multiplayer.compatible", target.forgeData.numberOfMods); - } else { - idx = 16; - if(target.forgeData.extraReason != null) { - String extraReason = ForgeI18n.parseMessage(target.forgeData.extraReason); - tooltip = ForgeI18n.parseMessage("fml.menu.multiplayer.incompatible.extra", extraReason); - } else { - tooltip = ForgeI18n.parseMessage("fml.menu.multiplayer.incompatible"); - } - } - if (target.forgeData.truncated) - { - tooltip += "\n" + ForgeI18n.parseMessage("fml.menu.multiplayer.truncated"); - } - break; - case "VANILLA": - if (target.forgeData.isCompatible) { - idx = 48; - tooltip = ForgeI18n.parseMessage("fml.menu.multiplayer.vanilla"); - } else { - idx = 80; - tooltip = ForgeI18n.parseMessage("fml.menu.multiplayer.vanilla.incompatible"); - } - break; - default: - idx = 64; - tooltip = ForgeI18n.parseMessage("fml.menu.multiplayer.unknown", target.forgeData.type); - } - - RenderSystem.setShaderTexture(0, iconSheet); - GuiComponent.blit(mStack, x + width - 18, y + 10, 16, 16, 0, idx, 16, 16, 256, 256); - - if(relativeMouseX > width - 15 && relativeMouseX < width && relativeMouseY > 10 && relativeMouseY < 26) { - //this is not the most proper way to do it, - //but works best here and has the least maintenance overhead - gui.setToolTip(Arrays.stream(tooltip.split("\n")).map(TextComponent::new).collect(Collectors.toList())); - } + @Deprecated(since="1.18", forRemoval = true) + public static void drawForgePingInfo(JoinMultiplayerScreen gui, ServerData target, PoseStack mStack, int x, int y, int width, int relativeMouseX, int relativeMouseY) + { + ForgeHooksClient.drawForgePingInfo(gui, target, mStack, x, y, width, relativeMouseX, relativeMouseY); } + @Deprecated(since="1.18", forRemoval = true) public static String fixDescription(String description) { return description.endsWith(":NOFML§r") ? description.substring(0, description.length() - 8)+"§r" : description; } - @SuppressWarnings("resource") - static File getSavesDir() - { - return new File(Minecraft.getInstance().gameDirectory, "saves"); - } - - private static Connection getClientToServerNetworkManager() - { - return Minecraft.getInstance().getConnection()!=null ? Minecraft.getInstance().getConnection().getConnection() : null; - } - + @Deprecated(since="1.18", forRemoval = true) public static void handleClientWorldClosing(ClientLevel world) { - Connection client = getClientToServerNetworkManager(); - // ONLY revert a non-local connection - if (client != null && !client.isMemoryConnection()) - { - GameData.revertToFrozen(); - } + ForgeHooksClient.handleClientWorldClosing(world); } - - private static SetMultimap missingTextures = HashMultimap.create(); - private static Set badTextureDomains = Sets.newHashSet(); - private static Table> brokenTextures = HashBasedTable.create(); - + @Deprecated(since="1.18", forRemoval = true) public static void trackMissingTexture(ResourceLocation resourceLocation) { - badTextureDomains.add(resourceLocation.getNamespace()); - missingTextures.put(resourceLocation.getNamespace(),resourceLocation); } + @Deprecated(since="1.18", forRemoval = true) public static void trackBrokenTexture(ResourceLocation resourceLocation, String error) { - badTextureDomains.add(resourceLocation.getNamespace()); - Set badType = brokenTextures.get(resourceLocation.getNamespace(), error); - if (badType == null) - { - badType = Sets.newHashSet(); - brokenTextures.put(resourceLocation.getNamespace(), MoreObjects.firstNonNull(error, "Unknown error"), badType); - } - badType.add(resourceLocation); } + @Deprecated(since="1.18", forRemoval = true) public static void logMissingTextureErrors() { - if (missingTextures.isEmpty() && brokenTextures.isEmpty()) - { - return; - } - Logger logger = LogManager.getLogger("FML.TEXTURE_ERRORS"); - logger.error(Strings.repeat("+=", 25)); - logger.error("The following texture errors were found."); - Map resManagers = ObfuscationReflectionHelper.getPrivateValue(SimpleReloadableResourceManager.class, (SimpleReloadableResourceManager)Minecraft.getInstance().getResourceManager(), "f_1087"+"0_"); - for (String resourceDomain : badTextureDomains) - { - Set missing = missingTextures.get(resourceDomain); - logger.error(Strings.repeat("=", 50)); - logger.error(" DOMAIN {}", resourceDomain); - logger.error(Strings.repeat("-", 50)); - logger.error(" domain {} is missing {} texture{}",resourceDomain, missing.size(),missing.size()!=1 ? "s" : ""); - FallbackResourceManager fallbackResourceManager = resManagers.get(resourceDomain); - if (fallbackResourceManager == null) - { - logger.error(" domain {} is missing a resource manager - it is probably a side-effect of automatic texture processing", resourceDomain); - } - else - { - List resPacks = fallbackResourceManager.fallbacks; - logger.error(" domain {} has {} location{}:",resourceDomain, resPacks.size(), resPacks.size() != 1 ? "s" :""); - for (PackResources resPack : resPacks) - { - if (resPack instanceof ModFileResourcePack) { - ModFileResourcePack modRP = (ModFileResourcePack) resPack; - List mods = modRP.getModFile().getModInfos(); - logger.error(" mod(s) {} resources at {}", mods.stream().map(IModInfo::getDisplayName).collect(Collectors.toList()), modRP.getModFile().getFilePath()); - } - else if (resPack instanceof AbstractPackResources) - { - logger.error(" resource pack at path {}", ((AbstractPackResources)resPack).file.getPath()); - } - else - { - logger.error(" unknown resourcepack type {} : {}", resPack.getClass().getName(), resPack.getName()); - } - } - } - logger.error(Strings.repeat("-", 25)); - if (missingTextures.containsKey(resourceDomain)) { - logger.error(" The missing resources for domain {} are:", resourceDomain); - for (ResourceLocation rl : missing) { - logger.error(" {}", rl.getPath()); - } - logger.error(Strings.repeat("-", 25)); - } - if (!brokenTextures.containsRow(resourceDomain)) - { - logger.error(" No other errors exist for domain {}", resourceDomain); - } - else - { - logger.error(" The following other errors were reported for domain {}:",resourceDomain); - Map> resourceErrs = brokenTextures.row(resourceDomain); - for (String error: resourceErrs.keySet()) - { - logger.error(Strings.repeat("-", 25)); - logger.error(" Problem: {}", error); - for (ResourceLocation rl : resourceErrs.get(error)) - { - logger.error(" {}",rl.getPath()); - } - } - } - logger.error(Strings.repeat("=", 50)); - } - logger.error(Strings.repeat("+=", 25)); } + @Deprecated(since="1.18", forRemoval = true) public static void firePlayerLogin(MultiPlayerGameMode pc, LocalPlayer player, Connection networkManager) { - MinecraftForge.EVENT_BUS.post(new ClientPlayerNetworkEvent.LoggedInEvent(pc, player, networkManager)); + ForgeHooksClient.firePlayerLogin(pc, player, networkManager); } + @Deprecated(since="1.18", forRemoval = true) public static void firePlayerLogout(MultiPlayerGameMode pc, LocalPlayer player) { - MinecraftForge.EVENT_BUS.post(new ClientPlayerNetworkEvent.LoggedOutEvent(pc, player, player != null ? player.connection != null ? player.connection.getConnection() : null : null)); + ForgeHooksClient.firePlayerLogout(pc, player); } + @Deprecated(since="1.18", forRemoval = true) public static void firePlayerRespawn(MultiPlayerGameMode pc, LocalPlayer oldPlayer, LocalPlayer newPlayer, Connection networkManager) { - MinecraftForge.EVENT_BUS.post(new ClientPlayerNetworkEvent.RespawnEvent(pc, oldPlayer, newPlayer, networkManager)); + ForgeHooksClient.firePlayerRespawn(pc, oldPlayer, newPlayer, networkManager); } diff --git a/src/main/java/net/minecraftforge/fmlclient/ClientModLoader.java b/src/main/java/net/minecraftforge/fmlclient/ClientModLoader.java index 0b0f34a4af..a4a55b4a83 100644 --- a/src/main/java/net/minecraftforge/fmlclient/ClientModLoader.java +++ b/src/main/java/net/minecraftforge/fmlclient/ClientModLoader.java @@ -25,12 +25,11 @@ import static net.minecraftforge.fml.Logging.LOADING; import java.io.File; import java.util.*; import java.util.Map.Entry; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.function.BiConsumer; import java.util.function.Consumer; +import net.minecraft.server.packs.repository.RepositorySource; import net.minecraft.server.packs.resources.PreparableReloadListener; import net.minecraft.server.packs.repository.PackSource; import net.minecraft.server.packs.resources.ReloadableResourceManager; @@ -38,11 +37,14 @@ import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.server.packs.repository.Pack; import net.minecraft.server.packs.repository.PackRepository; import net.minecraft.world.level.DataPackConfig; +import net.minecraftforge.event.AddPackFindersEvent; +import net.minecraftforge.event.ForgeEventFactory; import net.minecraftforge.fml.*; import net.minecraftforge.fmllegacy.BrandingControl; import net.minecraftforge.fmllegacy.CrashReportExtender; import net.minecraftforge.fmllegacy.LogicalSidedProvider; import net.minecraftforge.forgespi.locating.IModFile; +import net.minecraftforge.resource.PathResourcePack; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -62,7 +64,6 @@ import net.minecraftforge.common.ForgeConfig; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.fmlclient.gui.screen.LoadingErrorScreen; import net.minecraftforge.fmllegacy.packs.DelegatingResourcePack; -import net.minecraftforge.fmllegacy.packs.ModFileResourcePack; import net.minecraftforge.fmllegacy.packs.ResourcePackLoader; import net.minecraftforge.fmllegacy.server.LanguageHook; import net.minecraftforge.forgespi.language.IModInfo; @@ -107,6 +108,7 @@ public class ClientModLoader createRunnableWithCatch(()->ModLoader.get().gatherAndInitializeMods(ModWorkManager.syncExecutor(), ModWorkManager.parallelExecutor(), new SpacedRunnable(earlyLoaderGUI::renderTick))).run(); if (error == null) { ResourcePackLoader.loadResourcePacks(defaultResourcePacks, net.minecraftforge.fmlclient.ClientModLoader::buildPackFinder); + ModLoader.get().postEvent(new AddPackFindersEvent(PackType.CLIENT_RESOURCES, defaultResourcePacks::addPackFinder)); DataPackConfig.DEFAULT.addModPacks(ResourcePackLoader.getPackNames()); mcResourceManager.registerReloadListener(net.minecraftforge.fmlclient.ClientModLoader::onResourceReload); mcResourceManager.registerReloadListener(BrandingControl.resourceManagerReloadListener()); @@ -186,7 +188,6 @@ public class ClientModLoader mc.setScreen(new LoadingErrorScreen(error, warnings, dumpedLocation)); return true; } else { - ClientHooks.logMissingTextureErrors(); return false; } } @@ -199,13 +200,13 @@ public class ClientModLoader return loading; } - private static ResourcePackLoader.IPackInfoFinder buildPackFinder(Map modResourcePacks, BiConsumer packSetter) { - return (packList, factory) -> clientPackFinder(modResourcePacks, packSetter, packList, factory); + private static RepositorySource buildPackFinder(Map modResourcePacks) { + return (packList, factory) -> clientPackFinder(modResourcePacks, packList, factory); } - private static void clientPackFinder(Map modResourcePacks, BiConsumer packSetter, Consumer consumer, Pack.PackConstructor factory) { - List hiddenPacks = new ArrayList<>(); - for (Entry e : modResourcePacks.entrySet()) + private static void clientPackFinder(Map modResourcePacks, Consumer consumer, Pack.PackConstructor factory) { + List hiddenPacks = new ArrayList<>(); + for (Entry e : modResourcePacks.entrySet()) { IModInfo mod = e.getKey().getModInfos().get(0); final String name = "mod:" + mod.getModId(); @@ -215,7 +216,6 @@ public class ClientModLoader ModLoader.get().addWarning(new ModLoadingWarning(mod, ModLoadingStage.ERROR, "fml.modloading.brokenresources", e.getKey())); continue; } - packSetter.accept(e.getValue(), packInfo); LOGGER.debug(CORE, "Generating PackInfo named {} for mod file {}", name, e.getKey().getFilePath()); if (mod.getOwningFile().showAsResourcePack()) { consumer.accept(packInfo); diff --git a/src/main/java/net/minecraftforge/fmlclient/gui/screen/LoadingErrorScreen.java b/src/main/java/net/minecraftforge/fmlclient/gui/screen/LoadingErrorScreen.java index 9ce18c316c..167beec07b 100644 --- a/src/main/java/net/minecraftforge/fmlclient/gui/screen/LoadingErrorScreen.java +++ b/src/main/java/net/minecraftforge/fmlclient/gui/screen/LoadingErrorScreen.java @@ -83,7 +83,6 @@ public class LoadingErrorScreen extends ErrorScreen { this.addRenderableWidget(new ExtendedButton(this.width / 2 + 5, this.height - yOffset, this.width / 2 - 55, 20, new TextComponent(ForgeI18n.parseMessage("fml.button.open.file", logFile.getFileName())), b -> Util.getPlatform().openFile(logFile.toFile()))); if (this.modLoadErrors.isEmpty()) { this.addRenderableWidget(new ExtendedButton(this.width / 4, this.height - 24, this.width / 2, 20, new TextComponent(ForgeI18n.parseMessage("fml.button.continue.launch")), b -> { - ClientHooks.logMissingTextureErrors(); this.minecraft.setScreen(null); })); } else { diff --git a/src/main/java/net/minecraftforge/fmlclient/gui/screen/ModListScreen.java b/src/main/java/net/minecraftforge/fmlclient/gui/screen/ModListScreen.java index 64366420d6..36c3735baf 100644 --- a/src/main/java/net/minecraftforge/fmlclient/gui/screen/ModListScreen.java +++ b/src/main/java/net/minecraftforge/fmlclient/gui/screen/ModListScreen.java @@ -35,6 +35,7 @@ import net.minecraft.client.gui.narration.NarrationElementOutput; import net.minecraft.client.renderer.GameRenderer; import net.minecraftforge.fmlclient.gui.GuiUtils; import net.minecraftforge.fmlclient.gui.widget.ModListWidget; +import net.minecraftforge.resource.PathResourcePack; import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -66,7 +67,6 @@ import net.minecraftforge.fml.VersionChecker; import net.minecraftforge.fmlclient.ConfigGuiHandler; import net.minecraftforge.fml.loading.FMLPaths; import net.minecraftforge.fml.loading.StringUtils; -import net.minecraftforge.fmllegacy.packs.ModFileResourcePack; import net.minecraftforge.fmllegacy.packs.ResourcePackLoader; import net.minecraftforge.forgespi.language.IModInfo; @@ -428,8 +428,8 @@ public class ModListScreen extends Screen Pair logoData = selectedMod.getLogoFile().map(logoFile-> { TextureManager tm = this.minecraft.getTextureManager(); - final ModFileResourcePack resourcePack = ResourcePackLoader.getResourcePackFor(selectedMod.getModId()) - .orElse(ResourcePackLoader.getResourcePackFor("forge"). + final PathResourcePack resourcePack = ResourcePackLoader.getPackFor(selectedMod.getModId()) + .orElse(ResourcePackLoader.getPackFor("forge"). orElseThrow(()->new RuntimeException("Can't find forge, WHAT!"))); try { diff --git a/src/main/java/net/minecraftforge/fmllegacy/packs/ModFileResourcePack.java b/src/main/java/net/minecraftforge/fmllegacy/packs/ModFileResourcePack.java index dd79352875..c83da44111 100644 --- a/src/main/java/net/minecraftforge/fmllegacy/packs/ModFileResourcePack.java +++ b/src/main/java/net/minecraftforge/fmllegacy/packs/ModFileResourcePack.java @@ -19,35 +19,22 @@ package net.minecraftforge.fmllegacy.packs; -import net.minecraft.server.packs.AbstractPackResources; import net.minecraft.server.packs.repository.Pack; -import net.minecraft.server.packs.PackType; -import net.minecraft.resources.ResourceLocation; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.Collection; -import java.util.Collections; -import java.util.Set; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -import com.google.common.base.Joiner; import net.minecraftforge.forgespi.locating.IModFile; +import net.minecraftforge.resource.PathResourcePack; -public class ModFileResourcePack extends AbstractPackResources +import javax.annotation.Nonnull; +import java.nio.file.Path; + +@Deprecated(since="1.18", forRemoval = true) // TODO 1.18: Replace usages with PathResourcePack +public class ModFileResourcePack extends PathResourcePack { private final IModFile modFile; private Pack packInfo; public ModFileResourcePack(final IModFile modFile) { - super(new File("dummy")); + super(modFile.getFileName(), modFile.getFilePath()); this.modFile = modFile; } @@ -55,103 +42,16 @@ public class ModFileResourcePack extends AbstractPackResources return this.modFile; } + @Nonnull @Override - public String getName() + protected Path resolve(@Nonnull String... paths) { - return modFile.getFileName(); + return modFile.findResource(paths); } @Override - protected InputStream getResource(String name) throws IOException + public String toString() { - final Path path = modFile.findResource(name); - if(!Files.exists(path)) - throw new FileNotFoundException("Can't find resource " + name + " at " + modFile.getFilePath().toString()); - return Files.newInputStream(path, StandardOpenOption.READ); - } - - @Override - protected boolean hasResource(String name) - { - return Files.exists(modFile.findResource(name)); - } - - - @Override - public Collection getResources(PackType type, String resourceNamespace, String pathIn, int maxDepth, Predicate filter) - { - try - { - Path root = modFile.findResource(type.getDirectory(), resourceNamespace).toAbsolutePath(); - Path inputPath = root.getFileSystem().getPath(pathIn); - - return Files.walk(root) - .map(root::relativize) - .filter(path -> path.getNameCount() <= maxDepth && !path.toString().endsWith(".mcmeta") && path.startsWith(inputPath)) - .filter(path -> filter.test(path.getFileName().toString())) - // It is VERY IMPORTANT that we do not rely on Path.toString as this is inconsistent between operating systems - // Join the path names ourselves to force forward slashes - .map(path -> new ResourceLocation(resourceNamespace, Joiner.on('/').join(path))) - .collect(Collectors.toList()); - } - catch (IOException e) - { - return Collections.emptyList(); - } - } - - @Override - public Set getNamespaces(PackType type) - { - try { - Path root = modFile.findResource(type.getDirectory()); - return Files.walk(root,1) - .map(path -> root.relativize(path)) - .filter(path -> path.getNameCount() > 0) // skip the root entry - .map(p->p.toString().replaceAll("/$","")) // remove the trailing slash, if present - .filter(s -> !s.isEmpty()) //filter empty strings, otherwise empty strings default to minecraft in ResourceLocations - .collect(Collectors.toSet()); - } - catch (IOException e) - { - if (type == PackType.SERVER_DATA) //We still have to add the resource namespace if client resources exist, as we load langs (which are in assets) on server - { - return this.getNamespaces(PackType.CLIENT_RESOURCES); - } - else - { - return Collections.emptySet(); - } - } - } - - public InputStream getResource(PackType type, ResourceLocation location) throws IOException { - if (location.getPath().startsWith("lang/")) { - return super.getResource(PackType.CLIENT_RESOURCES, location); - } else { - return super.getResource(type, location); - } - } - - public boolean hasResource(PackType type, ResourceLocation location) { - if (location.getPath().startsWith("lang/")) { - return super.hasResource(PackType.CLIENT_RESOURCES, location); - } else { - return super.hasResource(type, location); - } - } - - @Override - public void close() - { - - } - - void setPackInfo(final T packInfo) { - this.packInfo = packInfo; - } - - T getPackInfo() { - return (T)this.packInfo; + return String.format("%s: %s", getClass().getName(), getModFile().getFileName()); } } diff --git a/src/main/java/net/minecraftforge/fmllegacy/packs/ResourcePackLoader.java b/src/main/java/net/minecraftforge/fmllegacy/packs/ResourcePackLoader.java index 0f01e62064..642183f0b3 100644 --- a/src/main/java/net/minecraftforge/fmllegacy/packs/ResourcePackLoader.java +++ b/src/main/java/net/minecraftforge/fmllegacy/packs/ResourcePackLoader.java @@ -32,6 +32,7 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; +import com.mojang.datafixers.util.Pair; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import net.minecraft.server.packs.repository.RepositorySource; @@ -41,23 +42,35 @@ import net.minecraft.server.packs.repository.PackRepository; import net.minecraftforge.fml.ModList; import net.minecraftforge.forgespi.language.IModFileInfo; import net.minecraftforge.forgespi.locating.IModFile; +import net.minecraftforge.resource.PathResourcePack; public class ResourcePackLoader { - private static Map modResourcePacks; - private static PackRepository resourcePackList; + private static Map modResourcePacks; + @Deprecated(since="1.18", forRemoval = true) public static Optional getResourcePackFor(String modId) { + return Optional.ofNullable(ModList.get().getModFileById(modId)). + map(IModFileInfo::getFile).map(mf->modResourcePacks.get(mf)) + .filter(ModFileResourcePack.class::isInstance) + .map(ModFileResourcePack.class::cast); + } + + public static Optional getPackFor(String modId) { return Optional.ofNullable(ModList.get().getModFileById(modId)). map(IModFileInfo::getFile).map(mf->modResourcePacks.get(mf)); } - public static void loadResourcePacks(PackRepository resourcePacks, BiFunction, BiConsumer, IPackInfoFinder> packFinder) { - resourcePackList = resourcePacks; + @Deprecated + public static void loadResourcePacks(PackRepository resourcePacks, BiFunction, BiConsumer, ? extends RepositorySource> packFinder) { + loadResourcePacks(resourcePacks, (map) -> packFinder.apply(map, (rp,p) -> {})); + } + + public static void loadResourcePacks(PackRepository resourcePacks, Function, ? extends RepositorySource> packFinder) { modResourcePacks = ModList.get().getModFiles().stream() .filter(mf->mf.requiredLanguageLoaders().stream().noneMatch(ls->ls.languageName().equals("minecraft"))) - .map(mf -> new ModFileResourcePack(mf.getFile())) - .collect(Collectors.toMap(ModFileResourcePack::getModFile, Function.identity(), (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); }, LinkedHashMap::new)); - resourcePacks.addPackFinder(new LambdaFriendlyPackFinder(packFinder.apply(modResourcePacks, ModFileResourcePack::setPackInfo))); + .map(mf -> Pair.of(mf, new ModFileResourcePack(mf.getFile()))) // TODO: change to PathResourcePack in 1.18 + .collect(Collectors.toMap(p -> p.getFirst().getFile(), Pair::getSecond, (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); }, LinkedHashMap::new)); + resourcePacks.addPackFinder(packFinder.apply(modResourcePacks)); } public static List getPackNames() { @@ -93,22 +106,14 @@ public class ResourcePackLoader { }; } - public interface IPackInfoFinder { - void addPackInfos(Consumer consumer, PackConstructor factory); - } - - // SO GROSS - DON'T @ me bro - @SuppressWarnings("unchecked") - private static class LambdaFriendlyPackFinder implements RepositorySource { - private IPackInfoFinder wrapped; - - private LambdaFriendlyPackFinder(final IPackInfoFinder wrapped) { - this.wrapped = wrapped; - } + @Deprecated(since="1.18", forRemoval = true) + public interface IPackInfoFinder extends RepositorySource { @Override - public void loadPacks(Consumer consumer, PackConstructor factory) { - wrapped.addPackInfos(consumer, factory); + default void loadPacks(Consumer consumer, PackConstructor factory) { + addPackInfos(consumer, factory); } + + void addPackInfos(Consumer consumer, PackConstructor factory); } } diff --git a/src/main/java/net/minecraftforge/fmllegacy/server/ServerLifecycleHooks.java b/src/main/java/net/minecraftforge/fmllegacy/server/ServerLifecycleHooks.java index aea2108c7e..a8f84d8694 100644 --- a/src/main/java/net/minecraftforge/fmllegacy/server/ServerLifecycleHooks.java +++ b/src/main/java/net/minecraftforge/fmllegacy/server/ServerLifecycleHooks.java @@ -31,6 +31,7 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import net.minecraft.server.packs.repository.PackSource; +import net.minecraft.server.packs.repository.RepositorySource; import net.minecraft.world.level.storage.LevelResource; import net.minecraftforge.fml.DistExecutor; import net.minecraftforge.fmllegacy.LogicalSidedProvider; @@ -42,9 +43,9 @@ import net.minecraftforge.fmllegacy.network.FMLNetworkConstants; import net.minecraftforge.fmllegacy.network.FMLStatusPing; import net.minecraftforge.fmllegacy.network.NetworkHooks; import net.minecraftforge.fmllegacy.network.NetworkRegistry; -import net.minecraftforge.fmllegacy.packs.ModFileResourcePack; import net.minecraftforge.fmllegacy.packs.ResourcePackLoader; import net.minecraftforge.forgespi.locating.IModFile; +import net.minecraftforge.resource.PathResourcePack; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; @@ -186,13 +187,20 @@ public class ServerLifecycleHooks } //INTERNAL MODDERS DO NOT USE - @Deprecated - public static ResourcePackLoader.IPackInfoFinder buildPackFinder(Map modResourcePacks, BiConsumer packSetter) { - return (packList, factory) -> serverPackFinder(modResourcePacks, packSetter, packList, factory); + @Deprecated(since="1.18", forRemoval = true) + public static RepositorySource buildPackFinder(Map modResourcePacks, BiConsumer packSetter) { + return (packList, factory) -> serverPackFinder(modResourcePacks, packList, factory); } - private static void serverPackFinder(Map modResourcePacks, BiConsumer packSetter, Consumer consumer, Pack.PackConstructor factory) { - for (Entry e : modResourcePacks.entrySet()) + //INTERNAL MODDERS DO NOT USE + // TODO: Rename in 1.18 (if this doesn't get + @Deprecated + public static RepositorySource buildPackFinderNew(Map modResourcePacks) { + return (packList, factory) -> serverPackFinder(modResourcePacks, packList, factory); + } + + private static void serverPackFinder(Map modResourcePacks, Consumer consumer, Pack.PackConstructor factory) { + for (Entry e : modResourcePacks.entrySet()) { IModInfo mod = e.getKey().getModInfos().get(0); if (Objects.equals(mod.getModId(), "minecraft")) continue; // skip the minecraft "mod" @@ -203,7 +211,6 @@ public class ServerLifecycleHooks ModLoader.get().addWarning(new ModLoadingWarning(mod, ModLoadingStage.ERROR, "fml.modloading.brokenresources", e.getKey())); continue; } - packSetter.accept(e.getValue(), packInfo); LOGGER.debug(CORE, "Generating PackInfo named {} for mod file {}", name, e.getKey().getFilePath()); consumer.accept(packInfo); } diff --git a/src/main/java/net/minecraftforge/resource/PathResourcePack.java b/src/main/java/net/minecraftforge/resource/PathResourcePack.java new file mode 100644 index 0000000000..8b5631cf41 --- /dev/null +++ b/src/main/java/net/minecraftforge/resource/PathResourcePack.java @@ -0,0 +1,193 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2021. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.resource; + +import com.google.common.base.Joiner; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.AbstractPackResources; +import net.minecraft.server.packs.PackType; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * Defines a resource pack from an arbitrary Path. + * + * This is primarily intended to support including optional resource packs inside a mod, + * such as to have alternative textures to use along with Programmer Art, or optional + * alternative recipes for compatibility ot to replace vanilla recipes. + */ +public class PathResourcePack extends AbstractPackResources +{ + private final Path source; + private final String packName; + + /** + * Constructs a java.nio.Path-based resource pack. + * + * @param packName the identifying name of the pack. + * This name should be unique within the pack finder, preferably the name of the file or folder containing the resources. + * @param source the root path of the pack. This needs to point to the folder that contains "assets" and/or "data", not the asset folder itself! + */ + public PathResourcePack(String packName, final Path source) + { + super(new File("dummy")); + this.source = source; + this.packName = packName; + } + + /** + * Returns the source path containing the resource pack. + * This is used for error display. + * + * @return the root path of the resources. + */ + public Path getSource() { + return this.source; + } + + /** + * Returns the identifying name for the pack. + * + * @return the identifier of the pack. + */ + @Override + public String getName() + { + return packName; + } + + /** + * Implement to return a file or folder path for the given set of path components. + * @param paths One or more path strings to resolve. Can include slash-separated paths. + * @return the resulting path, which may not exist. + */ + protected Path resolve(String... paths) + { + Path path = getSource(); + for(String name : paths) + path = path.resolve(name); + return path; + } + + @Override + protected InputStream getResource(String name) throws IOException + { + final Path path = resolve(name); + if(!Files.exists(path)) + throw new FileNotFoundException("Can't find resource " + name + " at " + getSource()); + return Files.newInputStream(path, StandardOpenOption.READ); + } + + @Override + protected boolean hasResource(String name) + { + final Path path = resolve(name); + return Files.exists(path); + } + + @Override + public Collection getResources(PackType type, String resourceNamespace, String pathIn, int maxDepth, Predicate filter) + { + try + { + Path root = resolve(type.getDirectory(), resourceNamespace).toAbsolutePath(); + Path inputPath = root.getFileSystem().getPath(pathIn); + + return Files.walk(root) + .map(root::relativize) + .filter(path -> path.getNameCount() <= maxDepth && !path.toString().endsWith(".mcmeta") && path.startsWith(inputPath)) + .filter(path -> filter.test(path.getFileName().toString())) + // It is VERY IMPORTANT that we do not rely on Path.toString as this is inconsistent between operating systems + // Join the path names ourselves to force forward slashes + .map(path -> new ResourceLocation(resourceNamespace, Joiner.on('/').join(path))) + .collect(Collectors.toList()); + } + catch (IOException e) + { + return Collections.emptyList(); + } + } + + @Override + public Set getNamespaces(PackType type) + { + try { + Path root = resolve(type.getDirectory()); + return Files.walk(root,1) + .map(path -> root.relativize(path)) + .filter(path -> path.getNameCount() > 0) // skip the root entry + .map(p->p.toString().replaceAll("/$","")) // remove the trailing slash, if present + .filter(s -> !s.isEmpty()) //filter empty strings, otherwise empty strings default to minecraft in ResourceLocations + .collect(Collectors.toSet()); + } + catch (IOException e) + { + if (type == PackType.SERVER_DATA) //We still have to add the resource namespace if client resources exist, as we load langs (which are in assets) on server + { + return this.getNamespaces(PackType.CLIENT_RESOURCES); + } + else + { + return Collections.emptySet(); + } + } + } + + @Override + public InputStream getResource(PackType type, ResourceLocation location) throws IOException { + if (location.getPath().startsWith("lang/")) { + return super.getResource(PackType.CLIENT_RESOURCES, location); + } else { + return super.getResource(type, location); + } + } + + @Override + public boolean hasResource(PackType type, ResourceLocation location) { + if (location.getPath().startsWith("lang/")) { + return super.hasResource(PackType.CLIENT_RESOURCES, location); + } else { + return super.hasResource(type, location); + } + } + + @Override + public void close() + { + + } + + @Override + public String toString() + { + return String.format("%s: %s", getClass().getName(), getSource()); + } +} diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index a1783cdd54..5f43c7a603 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -201,13 +201,13 @@ public net.minecraft.server.level.ServerPlayer m_143399_(Lnet/minecraft/world/in public net.minecraft.server.level.ServerPlayer m_9217_()V # nextContainerCounter public net.minecraft.server.packs.AbstractPackResources f_10203_ # file public net.minecraft.server.packs.resources.FallbackResourceManager f_10599_ # fallbacks -public net.minecraft.tags.Tag$BuilderEntry (Lnet/minecraft/tags/Tag$Entry;Ljava/lang/String;)V # constructor public net.minecraft.tags.BlockTags m_13116_(Ljava/lang/String;)Lnet/minecraft/tags/Tag$Named; # bind public net.minecraft.tags.EntityTypeTags m_13127_(Ljava/lang/String;)Lnet/minecraft/tags/Tag$Named; # bind public net.minecraft.tags.FluidTags m_13134_(Ljava/lang/String;)Lnet/minecraft/tags/Tag$Named; # bind public net.minecraft.tags.GameEventTags m_144307_(Ljava/lang/String;)Lnet/minecraft/tags/Tag$Named; # bind public net.minecraft.tags.ItemTags m_13194_(Ljava/lang/String;)Lnet/minecraft/tags/Tag$Named; # bind protected net.minecraft.tags.StaticTagHelper$Wrapper f_13251_ # tag +public net.minecraft.tags.Tag$BuilderEntry (Lnet/minecraft/tags/Tag$Entry;Ljava/lang/String;)V # constructor public net.minecraft.tags.TagContainer (Ljava/util/Map;)V # constructor public net.minecraft.tags.TagContainer f_144433_ # collections public net.minecraft.util.WeighedRandom$WeighedRandomItem f_14482_ # weight diff --git a/src/test/java/net/minecraftforge/debug/AddPackFinderEventTest.java b/src/test/java/net/minecraftforge/debug/AddPackFinderEventTest.java new file mode 100644 index 0000000000..59fa7b8734 --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/AddPackFinderEventTest.java @@ -0,0 +1,65 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2021. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.debug; + +import net.minecraft.network.chat.TextComponent; +import net.minecraft.server.packs.PackType; +import net.minecraft.server.packs.metadata.pack.PackMetadataSection; +import net.minecraft.server.packs.repository.Pack; +import net.minecraft.server.packs.repository.PackSource; +import net.minecraftforge.event.AddPackFindersEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.resource.PathResourcePack; + +import java.io.IOException; + +@Mod(AddPackFinderEventTest.MODID) +@Mod.EventBusSubscriber(modid=AddPackFinderEventTest.MODID, bus= Mod.EventBusSubscriber.Bus.MOD) +public class AddPackFinderEventTest +{ + public static final String MODID = "add_pack_finders_test"; + + @SubscribeEvent + public static void addPackFinders(AddPackFindersEvent event) + { + try + { + if (event.getPackType() == PackType.CLIENT_RESOURCES) + { + var resourcePath = ModList.get().getModFileById(MODID).getFile().findResource("test_nested_resource_pack"); + var pack = new PathResourcePack(ModList.get().getModFileById(MODID).getFile().getFileName() + ":" + resourcePath, resourcePath); + var metadataSection = pack.getMetadataSection(PackMetadataSection.SERIALIZER); + if (metadataSection != null) + { + event.addRepositorySource((packConsumer, packConstructor) -> + packConsumer.accept(packConstructor.create( + "builtin/add_pack_finders_test", new TextComponent("display name"), false, + () -> pack, metadataSection, Pack.Position.BOTTOM, PackSource.BUILT_IN, false))); + } + } + } + catch(IOException ex) + { + throw new RuntimeException(ex); + } + } +} diff --git a/src/test/resources/META-INF/mods.toml b/src/test/resources/META-INF/mods.toml index 66202a1bc4..de881e034d 100644 --- a/src/test/resources/META-INF/mods.toml +++ b/src/test/resources/META-INF/mods.toml @@ -156,5 +156,7 @@ license="LGPL v2.1" modId="custom_shield_test" [[mods]] modId="lazy_capabilities_on_items" +[[mods]] + modId="add_pack_finders_test" # ADD ABOVE THIS LINE \ No newline at end of file diff --git a/src/test/resources/test_nested_resource_pack/assets/minecraft/blockstates/dirt.json b/src/test/resources/test_nested_resource_pack/assets/minecraft/blockstates/dirt.json new file mode 100644 index 0000000000..b3dcc8c5a4 --- /dev/null +++ b/src/test/resources/test_nested_resource_pack/assets/minecraft/blockstates/dirt.json @@ -0,0 +1,5 @@ +{ + "variants": { + "": {"model": "block/diamond_block"} + } +} \ No newline at end of file diff --git a/src/test/resources/test_nested_resource_pack/pack.mcmeta b/src/test/resources/test_nested_resource_pack/pack.mcmeta new file mode 100644 index 0000000000..0510da06c2 --- /dev/null +++ b/src/test/resources/test_nested_resource_pack/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "pack_format": 7, + "description": "Nested resource pack" + } +}