Support pack overlay system. Closes #9818

This commit is contained in:
LexManos 2023-12-13 14:42:49 -08:00
parent 6d7fda6a9b
commit d44d0b8edc
No known key found for this signature in database
GPG key ID: 6E90061A7AE1F652
11 changed files with 265 additions and 365 deletions

View file

@ -12,8 +12,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class ModLoadingWarning
{
public class ModLoadingWarning {
/**
* Mod Info for mod with warning
*/

View file

@ -21,7 +21,7 @@
if (path != null) {
if (this.tempDataPackRepository == null) {
this.tempDataPackRepository = ServerPacksSource.createPackRepository(path, this.packValidator);
+ net.minecraftforge.resource.ResourcePackLoader.loadResourcePacks(this.tempDataPackRepository, net.minecraftforge.server.ServerLifecycleHooks::buildPackFinder);
+ net.minecraftforge.resource.ResourcePackLoader.loadResourcePacks(this.tempDataPackRepository, false);
this.tempDataPackRepository.reload();
}

View file

@ -1,5 +1,16 @@
--- a/net/minecraft/resources/RegistryDataLoader.java
+++ b/net/minecraft/resources/RegistryDataLoader.java
@@ -75,7 +_,9 @@
});
if (!map.isEmpty()) {
logErrors(map);
- throw new IllegalStateException("Failed to load registries due to above errors");
+ var buf = new StringBuilder("Failed to load registries, see debug.log for more details:");
+ map.forEach((k,v) -> buf.append("\n\t").append(k.toString()).append(": ").append(v.getMessage()));
+ throw new IllegalStateException(buf.toString());
} else {
return (new RegistryAccess.ImmutableRegistryAccess(list.stream().map(Pair::getFirst).toList())).freeze();
}
@@ -124,13 +_,16 @@
}

View file

@ -187,7 +187,7 @@
}
public static WorldDataConfiguration configurePackRepository(PackRepository p_248681_, DataPackConfig p_248920_, boolean p_249869_, FeatureFlagSet p_251243_) {
+ net.minecraftforge.resource.ResourcePackLoader.loadResourcePacks(p_248681_, net.minecraftforge.server.ServerLifecycleHooks::buildPackFinder);
+ net.minecraftforge.resource.ResourcePackLoader.loadResourcePacks(p_248681_, false);
p_248681_.reload();
+ DataPackConfig.DEFAULT.addModPacks(net.minecraftforge.common.ForgeHooks.getModPacks());
+ p_248920_.addModPacks(net.minecraftforge.common.ForgeHooks.getModPacks());

View file

@ -1,21 +1,5 @@
--- a/net/minecraft/world/level/Explosion.java
+++ b/net/minecraft/world/level/Explosion.java
@@ -57,6 +_,7 @@
private final SoundEvent explosionSound;
private final ObjectArrayList<BlockPos> toBlow = new ObjectArrayList<>();
private final Map<Player, Vec3> hitPlayers = Maps.newHashMap();
+ private final Vec3 position;
public static DamageSource getDefaultDamageSource(Level p_309890_, @Nullable Entity p_311046_) {
return p_309890_.damageSources().explosion(p_311046_, getIndirectSourceEntityInternal(p_311046_));
@@ -90,6 +_,7 @@
this.smallExplosionParticles = p_312175_;
this.largeExplosionParticles = p_310459_;
this.explosionSound = p_311795_;
+ this.position = new Vec3(this.x, this.y, this.z);
}
private ExplosionDamageCalculator makeDamageCalculator(@Nullable Entity p_46063_) {
@@ -193,6 +_,7 @@
int j2 = Mth.floor(this.z - (double)f2 - 1.0D);
int j1 = Mth.floor(this.z + (double)f2 + 1.0D);
@ -24,14 +8,15 @@
Vec3 vec3 = new Vec3(this.x, this.y, this.z);
for(Entity entity : list) {
@@ -345,6 +_,15 @@
@@ -345,6 +_,16 @@
public List<BlockPos> getToBlow() {
return this.toBlow;
+ }
+
+ @Deprecated(forRemoval = true, since = "1.20.4")
+ public Vec3 getPosition() {
+ return this.position;
+ return this.center();
+ }
+
+ @Nullable

View file

@ -6,7 +6,7 @@
package net.minecraftforge.client.gui;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@ -17,12 +17,10 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import net.minecraft.client.gui.narration.NarrationElementOutput;
import net.minecraft.server.packs.resources.IoSupplier;
import net.minecraftforge.client.ConfigScreenHandler;
import net.minecraftforge.client.gui.widget.ModListWidget;
import net.minecraftforge.client.gui.widget.ScrollPanel;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.resource.PathPackResources;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -53,19 +51,16 @@ import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.VersionChecker;
import net.minecraftforge.fml.loading.FMLPaths;
import net.minecraftforge.fml.loading.StringUtils;
import net.minecraftforge.resource.ResourcePackLoader;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraft.locale.Language;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.Style;
public class ModListScreen extends Screen
{
public class ModListScreen extends Screen {
private static String stripControlCodes(String value) { return net.minecraft.util.StringUtil.stripColor(value); }
private static final Logger LOGGER = LogManager.getLogger();
private enum SortType implements Comparator<IModInfo>
{
private enum SortType implements Comparator<IModInfo> {
NORMAL,
A_TO_Z{ @Override protected int compare(String name1, String name2){ return name1.compareTo(name2); }},
Z_TO_A{ @Override protected int compare(String name1, String name2){ return name2.compareTo(name1); }};
@ -105,8 +100,7 @@ public class ModListScreen extends Screen
private boolean sorted = false;
private SortType sortType = SortType.NORMAL;
public ModListScreen(Screen parentScreen)
{
public ModListScreen(Screen parentScreen) {
super(Component.translatable("fml.menu.mods.title"));
this.parentScreen = parentScreen;
this.mods = Collections.unmodifiableList(ModList.get().getMods());
@ -118,32 +112,26 @@ public class ModListScreen extends Screen
private Size2i logoDims = new Size2i(0, 0);
private List<FormattedCharSequence> lines = Collections.emptyList();
InfoPanel(Minecraft mcIn, int widthIn, int heightIn, int topIn)
{
InfoPanel(Minecraft mcIn, int widthIn, int heightIn, int topIn) {
super(mcIn, widthIn, heightIn, topIn, modList.getRight() + PADDING);
}
void setInfo(List<String> lines, ResourceLocation logoPath, Size2i logoDims)
{
void setInfo(List<String> lines, ResourceLocation logoPath, Size2i logoDims) {
this.logoPath = logoPath;
this.logoDims = logoDims;
this.lines = resizeContent(lines);
}
void clearInfo()
{
void clearInfo() {
this.logoPath = null;
this.logoDims = new Size2i(0, 0);
this.lines = Collections.emptyList();
}
private List<FormattedCharSequence> resizeContent(List<String> lines)
{
private List<FormattedCharSequence> resizeContent(List<String> lines) {
List<FormattedCharSequence> ret = new ArrayList<>();
for (String line : lines)
{
if (line == null)
{
for (String line : lines) {
if (line == null) {
ret.add(null);
continue;
}
@ -151,16 +139,13 @@ public class ModListScreen extends Screen
Component chat = ForgeHooks.newChatWithLinks(line, false);
int maxTextLength = this.width - 12;
if (maxTextLength >= 0)
{
ret.addAll(Language.getInstance().getVisualOrder(font.getSplitter().splitLines(chat, maxTextLength, Style.EMPTY)));
}
}
return ret;
}
@Override
public int getContentHeight()
{
public int getContentHeight() {
int height = 50;
height += (lines.size() * font.lineHeight);
if (height < this.bottom - this.top - 8)
@ -169,14 +154,12 @@ public class ModListScreen extends Screen
}
@Override
protected int getScrollAmount()
{
protected int getScrollAmount() {
return font.lineHeight * 3;
}
@Override
protected void drawPanel(GuiGraphics guiGraphics, int entryRight, int relativeY, Tesselator tess, int mouseX, int mouseY)
{
protected void drawPanel(GuiGraphics guiGraphics, int entryRight, int relativeY, Tesselator tess, int mouseX, int mouseY) {
if (logoPath != null) {
RenderSystem.enableBlend();
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
@ -186,10 +169,8 @@ public class ModListScreen extends Screen
relativeY += headerHeight + PADDING;
}
for (FormattedCharSequence line : lines)
{
if (line != null)
{
for (FormattedCharSequence line : lines) {
if (line != null) {
RenderSystem.enableBlend();
guiGraphics.drawString(ModListScreen.this.font, line, left + PADDING, relativeY, 0xFFFFFF);
RenderSystem.disableBlend();
@ -198,9 +179,8 @@ public class ModListScreen extends Screen
}
final Style component = findTextLine(mouseX, mouseY);
if (component!=null) {
if (component!=null)
guiGraphics.renderComponentHoverEffect(ModListScreen.this.font, component, mouseX, mouseY);
}
}
private Style findTextLine(final int mouseX, final int mouseY) {
@ -208,9 +188,8 @@ public class ModListScreen extends Screen
return null;
double offset = (mouseY - top) + border + scrollDistance + 1;
if (logoPath != null) {
if (logoPath != null)
offset -= 50;
}
if (offset <= 0)
return null;
@ -220,9 +199,7 @@ public class ModListScreen extends Screen
FormattedCharSequence line = lines.get(lineIdx-1);
if (line != null)
{
return font.getSplitter().componentStyleAtWidth(line, mouseX - left - border);
}
return null;
}
@ -247,10 +224,8 @@ public class ModListScreen extends Screen
}
@Override
public void init()
{
for (IModInfo mod : mods)
{
public void init() {
for (IModInfo mod : mods) {
listWidth = Math.max(listWidth,getFontRenderer().width(mod.getDisplayName()) + 10);
listWidth = Math.max(listWidth,getFontRenderer().width(MavenVersionStringHelper.artifactVersionToString(mod.getVersion())) + 5);
}
@ -295,37 +270,31 @@ public class ModListScreen extends Screen
updateCache();
}
private void displayModConfig()
{
if (selected == null) return;
try
{
private void displayModConfig() {
if (selected == null)
return;
try {
ConfigScreenHandler.getScreenFactoryFor(selected.getInfo()).map(f->f.apply(this.minecraft, this)).ifPresent(newScreen -> this.minecraft.setScreen(newScreen));
}
catch (final Exception e)
{
} catch (final Exception e) {
LOGGER.error("There was a critical issue trying to build the config GUI for {}", selected.getInfo().getModId(), e);
}
}
@Override
public void tick()
{
public void tick() {
modList.setSelected(selected);
if (!search.getValue().equals(lastFilterText))
{
if (!search.getValue().equals(lastFilterText)) {
reloadMods();
sorted = false;
}
if (!sorted)
{
if (!sorted) {
reloadMods();
mods.sort(sortType);
modList.refreshList();
if (selected != null)
{
if (selected != null) {
selected = modList.children().stream().filter(e -> e.getInfo() == selected.getInfo()).findFirst().orElse(null);
updateCache();
}
@ -333,33 +302,33 @@ public class ModListScreen extends Screen
}
}
public <T extends ObjectSelectionList.Entry<T>> void buildModList(Consumer<T> modListViewConsumer, Function<IModInfo, T> newEntry)
{
public <T extends ObjectSelectionList.Entry<T>> void buildModList(Consumer<T> modListViewConsumer, Function<IModInfo, T> newEntry) {
mods.forEach(mod->modListViewConsumer.accept(newEntry.apply(mod)));
}
private void reloadMods()
{
this.mods = this.unsortedMods.stream().
filter(mi->StringUtils.toLowerCase(stripControlCodes(mi.getDisplayName())).contains(StringUtils.toLowerCase(search.getValue()))).collect(Collectors.toList());
private void reloadMods() {
this.mods = this.unsortedMods
.stream()
.filter(mi ->
StringUtils.toLowerCase(stripControlCodes(mi.getDisplayName()))
.contains(StringUtils.toLowerCase(search.getValue()))
).collect(Collectors.toList());
lastFilterText = search.getValue();
}
private void resortMods(SortType newSort)
{
private void resortMods(SortType newSort) {
this.sortType = newSort;
for (SortType sort : SortType.values())
{
for (SortType sort : SortType.values()) {
if (sort.button != null)
sort.button.active = sortType != sort;
}
sorted = false;
}
@Override
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick)
{
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
this.modList.render(guiGraphics, mouseX, mouseY, partialTick);
if (this.modInfo != null)
this.modInfo.render(guiGraphics, mouseX, mouseY, partialTick);
@ -371,52 +340,46 @@ public class ModListScreen extends Screen
guiGraphics.drawString(getFontRenderer(), text.getVisualOrderText(), x, search.getY() - getFontRenderer().lineHeight, 0xFFFFFF, false);
}
public Minecraft getMinecraftInstance()
{
public Minecraft getMinecraftInstance() {
return minecraft;
}
public Font getFontRenderer()
{
public Font getFontRenderer() {
return font;
}
public void setSelected(ModListWidget.ModEntry entry)
{
public void setSelected(ModListWidget.ModEntry entry) {
this.selected = entry == this.selected ? null : entry;
updateCache();
}
private void updateCache()
{
private void updateCache() {
if (selected == null) {
this.configButton.active = false;
this.modInfo.clearInfo();
return;
}
IModInfo selectedMod = selected.getInfo();
this.configButton.active = ConfigScreenHandler.getScreenFactoryFor(selectedMod).isPresent();
List<String> lines = new ArrayList<>();
VersionChecker.CheckResult vercheck = VersionChecker.getResult(selectedMod);
@SuppressWarnings("resource")
Pair<ResourceLocation, Size2i> logoData = selectedMod.getLogoFile().map(logoFile->
{
Pair<ResourceLocation, Size2i> logoData = selectedMod.getLogoFile().map(logoFile -> {
TextureManager tm = this.minecraft.getTextureManager();
final PathPackResources resourcePack = ResourcePackLoader.getPackFor(selectedMod.getModId())
.orElse(ResourcePackLoader.getPackFor("forge").
orElseThrow(()->new RuntimeException("Can't find forge, WHAT!")));
try
{
try {
NativeImage logo = null;
IoSupplier<InputStream> logoResource = resourcePack.getRootResource(logoFile);
if (logoResource != null)
logo = NativeImage.read(logoResource.get());
if (logo != null)
{
var modfile = ModList.get().getModFileById(selectedMod.getModId());
if (modfile != null) {
var path = modfile.getFile().findResource(logoFile);
if (Files.exists(path))
logo = NativeImage.read(Files.newInputStream(path));
}
if (logo != null) {
return Pair.of(tm.register("modlogo", new DynamicTexture(logo) {
@Override
public void upload() {
this.bind();
@ -426,8 +389,8 @@ public class ModListScreen extends Screen
}
}), new Size2i(logo.getWidth(), logo.getHeight()));
}
}
catch (IOException e) { }
} catch (IOException e) { }
return Pair.<ResourceLocation, Size2i>of(null, new Size2i(0, 0));
}).orElse(Pair.of(null, new Size2i(0, 0)));
@ -463,12 +426,10 @@ public class ModListScreen extends Screen
}
*/
if ((vercheck.status() == VersionChecker.Status.OUTDATED || vercheck.status() == VersionChecker.Status.BETA_OUTDATED) && vercheck.changes().size() > 0)
{
if ((vercheck.status() == VersionChecker.Status.OUTDATED || vercheck.status() == VersionChecker.Status.BETA_OUTDATED) && vercheck.changes().size() > 0) {
lines.add(null);
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.changelogheader"));
for (Entry<ComparableVersion, String> entry : vercheck.changes().entrySet())
{
for (Entry<ComparableVersion, String> entry : vercheck.changes().entrySet()) {
lines.add(" " + entry.getKey() + ":");
lines.add(entry.getValue());
lines.add(null);
@ -479,24 +440,25 @@ public class ModListScreen extends Screen
}
@Override
public void resize(Minecraft mc, int width, int height)
{
public void resize(Minecraft mc, int width, int height) {
String s = this.search.getValue();
SortType sort = this.sortType;
ModListWidget.ModEntry selected = this.selected;
this.init(mc, width, height);
this.search.setValue(s);
this.selected = selected;
if (!this.search.getValue().isEmpty())
reloadMods();
if (sort != SortType.NORMAL)
resortMods(sort);
updateCache();
}
@Override
public void onClose()
{
public void onClose() {
this.minecraft.setScreen(this.parentScreen);
}
}

View file

@ -53,16 +53,14 @@ import net.minecraftforge.server.LanguageHook;
import net.minecraftforge.forgespi.language.IModInfo;
@OnlyIn(Dist.CLIENT)
public class ClientModLoader
{
public class ClientModLoader {
private static final Logger LOGGER = LogManager.getLogger();
private static boolean loading;
private static Minecraft mc;
private static boolean loadingComplete;
private static LoadingFailedException error;
public static void begin(final Minecraft minecraft, final PackRepository defaultResourcePacks, final ReloadableResourceManager mcResourceManager)
{
public static void begin(final Minecraft minecraft, final PackRepository defaultResourcePacks, final ReloadableResourceManager mcResourceManager) {
// force log4j to shutdown logging in a shutdown hook. This is because we disable default shutdown hook so the server properly logs it's shutdown
Runtime.getRuntime().addShutdownHook(new Thread(LogManager::shutdown));
ImmediateWindowHandler.updateProgress("Loading mods");
@ -72,7 +70,7 @@ public class ClientModLoader
LanguageHook.loadForgeAndMCLangs();
createRunnableWithCatch(()->ModLoader.get().gatherAndInitializeMods(ModWorkManager.syncExecutor(), ModWorkManager.parallelExecutor(), ImmediateWindowHandler::renderTick)).run();
if (error == null) {
ResourcePackLoader.loadResourcePacks(defaultResourcePacks, ClientModLoader::buildPackFinder);
ResourcePackLoader.loadResourcePacks(defaultResourcePacks, true);
ModLoader.get().postEvent(new AddPackFindersEvent(PackType.CLIENT_RESOURCES, defaultResourcePacks::addPackFinder));
DataPackConfig.DEFAULT.addModPacks(ResourcePackLoader.getPackNames());
mcResourceManager.registerReloadListener(ClientModLoader::onResourceReload);
@ -101,8 +99,7 @@ public class ClientModLoader
createRunnableWithCatch(() -> ModLoader.get().loadMods(syncExecutor, parallelExecutor, ImmediateWindowHandler::renderTick)).run();
}
private static void finishModLoading(ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor)
{
private static void finishModLoading(ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor) {
createRunnableWithCatch(() -> ModLoader.get().finishMods(syncExecutor, parallelExecutor, ImmediateWindowHandler::renderTick)).run();
loading = false;
loadingComplete = true;
@ -110,8 +107,7 @@ public class ClientModLoader
syncExecutor.execute(()->mc.options.load(true));
}
public static VersionChecker.Status checkForUpdates()
{
public static VersionChecker.Status checkForUpdates() {
boolean anyOutdated = ModList.get().getMods().stream()
.map(VersionChecker::getResult)
.map(result -> result.status())
@ -119,8 +115,7 @@ public class ClientModLoader
return anyOutdated ? VersionChecker.Status.OUTDATED : null;
}
public static boolean completeModLoading()
{
public static boolean completeModLoading() {
var warnings = ModLoader.get().getWarnings();
boolean showWarnings = true;
try {
@ -128,6 +123,7 @@ public class ClientModLoader
} catch (NullPointerException | IllegalStateException e) {
// We're in an early error state, config is not available. Assume true.
}
if (!showWarnings) {
//User disabled warning screen, as least log them
if (!warnings.isEmpty()) {
@ -136,6 +132,7 @@ public class ClientModLoader
}
warnings = Collections.emptyList(); //Clear warnings, as the user does not want to see them
}
File dumpedLocation = null;
if (error == null) {
// We can finally start the forge eventbus up
@ -145,81 +142,16 @@ public class ClientModLoader
LanguageHook.loadForgeAndMCLangs();
dumpedLocation = CrashReportExtender.dumpModLoadingCrashReport(LOGGER, error, mc.gameDirectory);
}
if (error != null || !warnings.isEmpty()) {
mc.setScreen(new LoadingErrorScreen(error, warnings, dumpedLocation));
return true;
} else {
return false;
}
return false;
}
public static boolean isLoading()
{
public static boolean isLoading() {
return loading;
}
private static RepositorySource buildPackFinder(Map<IModFile, ? extends PathPackResources> modResourcePacks) {
return packAcceptor -> clientPackFinder(modResourcePacks, packAcceptor);
}
private static void clientPackFinder(Map<IModFile, ? extends PathPackResources> modResourcePacks, Consumer<Pack> packAcceptor) {
var hiddenPacks = new ArrayList<PathPackResources>();
for (Entry<IModFile, ? extends PathPackResources> e : modResourcePacks.entrySet())
{
var supplier = new Pack.ResourcesSupplier() {
@Override
public PackResources openPrimary(String path) {
return e.getValue();
}
@Override
public PackResources openFull(String path, Info info) {
return e.getValue(); // TODO: composite
}
};
IModInfo mod = e.getKey().getModInfos().get(0);
final String name = "mod:" + mod.getModId();
final Pack modPack = Pack.readMetaAndCreate(name, Component.literal(e.getValue().packId()), false, supplier, PackType.CLIENT_RESOURCES, Pack.Position.BOTTOM, PackSource.DEFAULT);
if (modPack == null) {
// Vanilla only logs an error, instead of propagating, so handle null and warn that something went wrong
ModLoader.get().addWarning(new ModLoadingWarning(mod, ModLoadingStage.ERROR, "fml.modloading.brokenresources", e.getKey()));
continue;
}
LOGGER.debug(Logging.CORE, "Generating PackInfo named {} for mod file {}", name, e.getKey().getFilePath());
if (mod.getOwningFile().showAsResourcePack()) {
packAcceptor.accept(modPack);
} else {
hiddenPacks.add(e.getValue());
}
}
var delegating = new DelegatingPackResources("mod_resources", false,
new PackMetadataSection(
Component.translatable("fml.resources.modresources", hiddenPacks.size()),
SharedConstants.getCurrentVersion().getPackVersion(PackType.CLIENT_RESOURCES),
Optional.empty()
),
hiddenPacks
);
var supplier = new Pack.ResourcesSupplier() {
@Override
public PackResources openPrimary(String path) {
return delegating;
}
@Override
public PackResources openFull(String path, Info info) {
return delegating; // TODO: composite
}
};
// Create a resource pack merging all mod resources that should be hidden
final Pack modResourcesPack = Pack.readMetaAndCreate("mod_resources", Component.literal("Mod Resources"), true, supplier,
PackType.CLIENT_RESOURCES, Pack.Position.BOTTOM, PackSource.DEFAULT);
packAcceptor.accept(modResourcesPack);
}
}

View file

@ -22,18 +22,21 @@ import net.minecraft.server.packs.PackResources;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.metadata.MetadataSectionSerializer;
import net.minecraft.server.packs.metadata.pack.PackMetadataSection;
import net.minecraft.server.packs.repository.Pack;
import net.minecraft.server.packs.repository.Pack.Info;
import net.minecraft.server.packs.resources.IoSupplier;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
public class DelegatingPackResources extends AbstractPackResources
{
@ApiStatus.Internal
public class DelegatingPackResources extends AbstractPackResources {
private final PackMetadataSection packMeta;
private final List<PackResources> delegates;
private final Map<String, List<PackResources>> namespacesAssets;
private final Map<String, List<PackResources>> namespacesData;
public DelegatingPackResources(String packId, boolean isBuiltin, PackMetadataSection packMeta, List<? extends PackResources> packs)
{
public DelegatingPackResources(String packId, boolean isBuiltin, PackMetadataSection packMeta, List<? extends PackResources> packs) {
super(packId, isBuiltin);
this.packMeta = packMeta;
this.delegates = ImmutableList.copyOf(packs);
@ -41,15 +44,11 @@ public class DelegatingPackResources extends AbstractPackResources
this.namespacesData = this.buildNamespaceMap(PackType.SERVER_DATA, delegates);
}
private Map<String, List<PackResources>> buildNamespaceMap(PackType type, List<PackResources> packList)
{
private Map<String, List<PackResources>> buildNamespaceMap(PackType type, List<PackResources> packList) {
Map<String, List<PackResources>> map = new HashMap<>();
for (PackResources pack : packList)
{
for (PackResources pack : packList) {
for (String namespace : pack.getNamespaces(type))
{
map.computeIfAbsent(namespace, k -> new ArrayList<>()).add(pack);
}
}
map.replaceAll((k, list) -> ImmutableList.copyOf(list));
return ImmutableMap.copyOf(map);
@ -58,49 +57,38 @@ public class DelegatingPackResources extends AbstractPackResources
@SuppressWarnings("unchecked")
@Nullable
@Override
public <T> T getMetadataSection(MetadataSectionSerializer<T> deserializer) throws IOException
{
public <T> T getMetadataSection(MetadataSectionSerializer<T> deserializer) throws IOException {
return deserializer.getMetadataSectionName().equals("pack") ? (T) this.packMeta : null;
}
@Override
public void listResources(PackType type, String resourceNamespace, String paths, ResourceOutput resourceOutput)
{
public void listResources(PackType type, String resourceNamespace, String paths, ResourceOutput resourceOutput) {
for (PackResources delegate : this.delegates)
{
delegate.listResources(type, resourceNamespace, paths, resourceOutput);
}
}
@Override
public Set<String> getNamespaces(PackType type)
{
public Set<String> getNamespaces(PackType type) {
return type == PackType.CLIENT_RESOURCES ? namespacesAssets.keySet() : namespacesData.keySet();
}
@Override
public void close()
{
public void close() {
for (PackResources pack : delegates)
{
pack.close();
}
}
@Nullable
@Override
public IoSupplier<InputStream> getRootResource(String... paths)
{
public IoSupplier<InputStream> getRootResource(String... paths) {
// Root resources do not make sense here
return null;
}
@Nullable
@Override
public IoSupplier<InputStream> getResource(PackType type, ResourceLocation location)
{
for (PackResources pack : getCandidatePacks(type, location))
{
public IoSupplier<InputStream> getResource(PackType type, ResourceLocation location) {
for (PackResources pack : getCandidatePacks(type, location)) {
IoSupplier<InputStream> ioSupplier = pack.getResource(type, location);
if (ioSupplier != null)
return ioSupplier;
@ -110,15 +98,29 @@ public class DelegatingPackResources extends AbstractPackResources
}
@Nullable
public Collection<PackResources> getChildren()
{
public Collection<PackResources> getChildren() {
return delegates;
}
private List<PackResources> getCandidatePacks(PackType type, ResourceLocation location)
{
private List<PackResources> getCandidatePacks(PackType type, ResourceLocation location) {
Map<String, List<PackResources>> map = type == PackType.CLIENT_RESOURCES ? namespacesAssets : namespacesData;
List<PackResources> packsWithNamespace = map.get(location.getNamespace());
return packsWithNamespace == null ? Collections.emptyList() : packsWithNamespace;
}
public Pack.ResourcesSupplier supplier() {
return new Supplier();
}
private class Supplier implements Pack.ResourcesSupplier {
@Override
public PackResources openPrimary(String path) {
return DelegatingPackResources.this;
}
@Override
public PackResources openFull(String path, Info info) {
return DelegatingPackResources.this;
}
}
}

View file

@ -5,14 +5,12 @@
package net.minecraftforge.resource;
import com.google.common.base.Joiner;
import com.mojang.logging.LogUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
@ -31,10 +29,9 @@ import org.slf4j.Logger;
* <p>
* 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.
* alternative recipes for compatibility to to replace vanilla recipes.
*/
public class PathPackResources extends AbstractPackResources
{
public class PathPackResources extends AbstractPackResources {
private static final Logger LOGGER = LogUtils.getLogger();
private final Path source;
@ -46,8 +43,7 @@ public class PathPackResources extends AbstractPackResources
* @param isBuiltin whether this pack resources should be considered builtin
* @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 PathPackResources(String packId, boolean isBuiltin, final Path source)
{
public PathPackResources(String packId, boolean isBuiltin, final Path source) {
super(packId, isBuiltin);
this.source = source;
}
@ -58,8 +54,7 @@ public class PathPackResources extends AbstractPackResources
*
* @return the root path of the resources.
*/
public Path getSource()
{
public Path getSource() {
return this.source;
}
@ -69,8 +64,7 @@ public class PathPackResources extends AbstractPackResources
* @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)
{
protected Path resolve(String... paths) {
Path path = getSource();
for (String name : paths)
path = path.resolve(name);
@ -79,8 +73,7 @@ public class PathPackResources extends AbstractPackResources
@Nullable
@Override
public IoSupplier<InputStream> getRootResource(String... paths)
{
public IoSupplier<InputStream> getRootResource(String... paths) {
final Path path = resolve(paths);
if (!Files.exists(path))
return null;
@ -89,27 +82,22 @@ public class PathPackResources extends AbstractPackResources
}
@Override
public void listResources(PackType type, String namespace, String path, ResourceOutput resourceOutput)
{
public void listResources(PackType type, String namespace, String path, ResourceOutput resourceOutput) {
FileUtil.decomposePath(path).get()
.ifLeft(parts -> net.minecraft.server.packs.PathPackResources.listPath(namespace, resolve(type.getDirectory(), namespace).toAbsolutePath(), parts, resourceOutput))
.ifRight(dataResult -> LOGGER.error("Invalid path {}: {}", path, dataResult.message()));
}
@Override
public Set<String> getNamespaces(PackType type)
{
public Set<String> getNamespaces(PackType type) {
return getNamespacesFromDisk(type);
}
@NotNull
private Set<String> getNamespacesFromDisk(final PackType type)
{
try
{
private Set<String> getNamespacesFromDisk(final PackType type) {
try {
Path root = resolve(type.getDirectory());
try (Stream<Path> walker = Files.walk(root, 1))
{
try (Stream<Path> walker = Files.walk(root, 1)) {
return walker
.filter(Files::isDirectory)
.map(root::relativize)
@ -118,28 +106,20 @@ public class PathPackResources extends AbstractPackResources
.filter(s -> !s.isEmpty()) // Filter empty strings, otherwise empty strings default to minecraft namespace in ResourceLocations
.collect(Collectors.toSet());
}
}
catch (IOException e)
{
} 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 IoSupplier<InputStream> getResource(PackType type, ResourceLocation location)
{
public IoSupplier<InputStream> getResource(PackType type, ResourceLocation location) {
return this.getRootResource(getPathFromLocation(location.getPath().startsWith("lang/") ? PackType.CLIENT_RESOURCES : type, location));
}
private static String[] getPathFromLocation(PackType type, ResourceLocation location)
{
private static String[] getPathFromLocation(PackType type, ResourceLocation location) {
String[] parts = location.getPath().split("/");
String[] result = new String[parts.length + 2];
result[0] = type.getDirectory();
@ -149,13 +129,10 @@ public class PathPackResources extends AbstractPackResources
}
@Override
public void close()
{
}
public void close() { }
@Override
public String toString()
{
public String toString() {
return String.format(Locale.ROOT, "%s: %s (%s)", getClass().getName(), this.packId(), getSource());
}
}

View file

@ -8,62 +8,97 @@ package net.minecraftforge.resource;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Consumer;
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;
import net.minecraft.server.packs.repository.Pack.Info;
import net.minecraft.SharedConstants;
import net.minecraft.network.chat.Component;
import net.minecraft.server.packs.CompositePackResources;
import net.minecraft.server.packs.PackResources;
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.PackRepository;
import net.minecraft.server.packs.repository.PackSource;
import net.minecraftforge.fml.Logging;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.ModLoader;
import net.minecraftforge.fml.ModLoadingStage;
import net.minecraftforge.fml.ModLoadingWarning;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
@ApiStatus.Internal
public class ResourcePackLoader {
private static Map<IModFile, PathPackResources> modResourcePacks;
private static final Logger LOGGER = LogManager.getLogger();
public static Optional<PathPackResources> getPackFor(String modId) {
return Optional.ofNullable(ModList.get().getModFileById(modId)).
map(IModFileInfo::getFile).map(mf->modResourcePacks.get(mf));
}
@Deprecated
public static void loadResourcePacks(PackRepository resourcePacks, BiFunction<Map<IModFile, ? extends PathPackResources>, BiConsumer<? super PathPackResources, Pack>, ? extends RepositorySource> packFinder) {
loadResourcePacks(resourcePacks, (map) -> packFinder.apply(map, (rp,p) -> {}));
}
public static void loadResourcePacks(PackRepository resourcePacks, Function<Map<IModFile, ? extends PathPackResources>, ? extends RepositorySource> packFinder) {
modResourcePacks = ModList.get().getModFiles().stream()
.filter(mf->mf.requiredLanguageLoaders().stream().noneMatch(ls->ls.languageName().equals("minecraft")))
.map(mf -> Pair.of(mf, createPackForMod(mf)))
.collect(Collectors.toMap(p -> p.getFirst().getFile(), Pair::getSecond, (u,v) -> { throw new IllegalStateException(String.format(Locale.ENGLISH, "Duplicate key %s", u)); }, LinkedHashMap::new));
resourcePacks.addPackFinder(packFinder.apply(modResourcePacks));
public static void loadResourcePacks(PackRepository resourcePacks, boolean client) {
resourcePacks.addPackFinder(repo -> findPacks(repo, client));
}
@NotNull
public static PathPackResources createPackForMod(IModFileInfo mf)
{
return new PathPackResources(mf.getFile().getFileName(), true, mf.getFile().getFilePath())
{
private final IModFile modFile = mf.getFile();
public static PathPackResources createPackForMod(IModFileInfo mf) {
return new ModPathPackResources(mf.getFile());
}
@NotNull
@Override
protected Path resolve(@NotNull String... paths)
{
return this.modFile.findResource(paths);
private static class ModPathPackResources extends PathPackResources {
private final IModFile mod;
private final String[] prefix;
private ModPathPackResources(IModFile mod, String... prefix) {
super(mod.getFileName(), true, mod.getFilePath());
this.mod = mod;
this.prefix = prefix;
}
@NotNull
@Override
protected Path resolve(@NotNull String... paths) {
if (prefix != null && prefix.length > 0) {
var tmp = new String[prefix.length + paths.length];
System.arraycopy(prefix, 0, tmp, 0, prefix.length);
System.arraycopy(paths, 0, tmp, prefix.length, paths.length);
return this.mod.findResource(tmp);
}
};
return this.mod.findResource(paths);
}
private static class Supplier implements Pack.ResourcesSupplier {
private final IModFile mod;
private Supplier(IModFile mod) {
this.mod = mod;
}
@Override
public PackResources openPrimary(String packId) {
return new ModPathPackResources(mod);
}
@Override
public PackResources openFull(String packId, Info info) {
var primary = openPrimary(packId);
if (info.overlays().isEmpty())
return primary;
var lst = new ArrayList<PackResources>(info.overlays().size());
for (var overlay : info.overlays())
lst.add(new ModPathPackResources(mod, overlay));
return new CompositePackResources(primary, lst);
}
}
}
public static List<String> getPackNames() {
@ -99,4 +134,55 @@ public class ResourcePackLoader {
};
}
private static void findPacks(Consumer<Pack> packAcceptor, boolean client) {
var type = client ? PackType.CLIENT_RESOURCES : PackType.SERVER_DATA;
var version = SharedConstants.getCurrentVersion().getPackVersion(type);
var hiddenPacks = new ArrayList<PackResources>();
for (var mod : ModList.get().getModFiles()) {
if (mod.requiredLanguageLoaders().stream().anyMatch(ls -> ls.languageName().equals("minecraft")))
continue;
var file = mod.getFile();
var supplier = new ModPathPackResources.Supplier(file);
var modinfo = file.getModInfos().get(0);
var name = "mod:" + modinfo.getModId();
var meta = Pack.readPackInfo(name, supplier, version);
Pack pack = null;
if (meta != null)
pack = Pack.create(name, Component.literal(file.getFileName()), false, supplier, meta, Pack.Position.BOTTOM, false, PackSource.DEFAULT);
if (pack == null) {
// Vanilla only logs an error, instead of propagating, so handle null and warn that something went wrong
ModLoader.get().addWarning(new ModLoadingWarning(modinfo, ModLoadingStage.ERROR, "fml.modloading.brokenresources", file));
continue;
}
LOGGER.debug(Logging.CORE, "Generating PackInfo named {} for mod file {}", name, file.getFilePath());
if (!client || mod.showAsResourcePack())
packAcceptor.accept(pack);
else
hiddenPacks.add(pack.open());
}
if (!hiddenPacks.isEmpty()) {
@SuppressWarnings("resource")
var delegating = new DelegatingPackResources("mod_resources", false,
new PackMetadataSection(
Component.translatable("fml.resources.modresources", hiddenPacks.size()),
version,
Optional.empty()
),
hiddenPacks
);
var supplier = delegating.supplier();
// Create a resource pack merging all mod resources that should be hidden
var modResourcesPack = Pack.readMetaAndCreate("mod_resources", Component.literal("Mod Resources"), true, supplier, type, Pack.Position.BOTTOM, PackSource.DEFAULT);
packAcceptor.accept(modResourcesPack);
}
}
}

View file

@ -9,13 +9,8 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import net.minecraft.core.Holder;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries;
@ -23,37 +18,24 @@ import net.minecraft.gametest.framework.GameTestServer;
import net.minecraft.network.Connection;
import net.minecraft.network.ConnectionProtocol;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.protocol.handshake.ClientIntent;
import net.minecraft.network.protocol.handshake.ClientIntentionPacket;
import net.minecraft.network.protocol.login.ClientboundLoginDisconnectPacket;
import net.minecraft.server.packs.PackResources;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.repository.PackSource;
import net.minecraft.server.packs.repository.RepositorySource;
import net.minecraft.world.level.storage.LevelResource;
import net.minecraftforge.common.world.StructureModifier;
import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.fml.Logging;
import net.minecraftforge.common.util.LogicalSidedProvider;
import net.minecraftforge.common.world.BiomeModifier;
import net.minecraftforge.fml.ModLoader;
import net.minecraftforge.fml.ModLoadingStage;
import net.minecraftforge.fml.ModLoadingWarning;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.network.ConnectionType;
import net.minecraftforge.network.NetworkContext;
import net.minecraftforge.network.NetworkRegistry;
import net.minecraftforge.registries.ForgeRegistries.Keys;
import net.minecraftforge.resource.PathPackResources;
import net.minecraftforge.server.permission.PermissionAPI;
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.minecraft.server.packs.repository.Pack;
import net.minecraft.server.packs.repository.Pack.Info;
import net.minecraft.server.MinecraftServer;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.common.MinecraftForge;
@ -64,7 +46,6 @@ import net.minecraftforge.event.server.ServerStartedEvent;
import net.minecraftforge.event.server.ServerStartingEvent;
import net.minecraftforge.event.server.ServerStoppedEvent;
import net.minecraftforge.event.server.ServerStoppingEvent;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.GameData;
import org.jetbrains.annotations.ApiStatus;
@ -134,41 +115,6 @@ public class ServerLifecycleHooks {
return currentServer;
}
@ApiStatus.Internal
public static RepositorySource buildPackFinder(Map<IModFile, ? extends PathPackResources> modResourcePacks) {
return packAcceptor -> serverPackFinder(modResourcePacks, packAcceptor);
}
private static void serverPackFinder(Map<IModFile, ? extends PathPackResources> modResourcePacks, Consumer<Pack> packAcceptor) {
for (Entry<IModFile, ? extends PathPackResources> e : modResourcePacks.entrySet()) {
IModInfo mod = e.getKey().getModInfos().get(0);
if (Objects.equals(mod.getModId(), "minecraft")) continue; // skip the minecraft "mod"
final String name = "mod:" + mod.getModId();
var supplier = new Pack.ResourcesSupplier() {
@Override
public PackResources openPrimary(String path) {
return e.getValue();
}
@Override
public PackResources openFull(String path, Info info) {
return e.getValue(); // TODO: composite
}
};
final Pack modPack = Pack.readMetaAndCreate(name, Component.literal(e.getValue().packId()), false, supplier, PackType.SERVER_DATA, Pack.Position.BOTTOM, PackSource.DEFAULT);
if (modPack == null) {
// Vanilla only logs an error, instead of propagating, so handle null and warn that something went wrong
ModLoader.get().addWarning(new ModLoadingWarning(mod, ModLoadingStage.ERROR, "fml.modloading.brokenresources", e.getKey()));
continue;
}
LOGGER.debug(Logging.CORE, "Generating PackInfo named {} for mod file {}", name, e.getKey().getFilePath());
packAcceptor.accept(modPack);
}
}
private static void runModifiers(final MinecraftServer server) {
final RegistryAccess registries = server.registryAccess();