Compare commits

...
28 Commits
Author SHA1 Message Date
LexManos b518c804fb 1.21.10 RB 2025-11-19 15:01:12 -08:00
Paint_Ninja e50a0ffc6c Add c:nuggets/copper item tag (#10716) 2025-11-19 09:12:27 -08:00
Matt 7ea0a41fe2 Fix ItemStack in RenderTooltipEvent always EMPTY, merges RenderTooltipEvent.Background into .Pre (#10655) (#10702) 2025-11-14 07:42:03 -08:00
Matt f3d16621c0 Provide LevelRenderState during frame pass construction Addresses #10867 (#10688) 2025-11-12 13:20:34 -08:00
Lemonnik6484 aa55a6a831 Make Mixin dev mode check a little looser (#10713) 2025-11-10 11:27:19 -08:00
cech12 3449217c65 Expand hooks for custom shears (#10704)
Fix shearing leads with custom shears (interacting with entity & leash knot)
Fix shearing poppy from copper golem for custom shears
2025-11-05 11:53:50 -08:00
JonathingandLexManos 41609a03d2 Introduce MixinExtras as an included, optional dependency (#10709)
Declare ME in userdev config as an effective compile transitive

Co-authored-by: LexManos <[email protected]>
2025-11-05 11:34:23 -08:00
LexManos 9b8e5c7109 Add workaround for Mixin bug. Closes #10710 2025-11-02 18:39:39 -08:00
LexManos f05ad3afd0 Fix ROIMFS not being on boot classloader for dedicated server. 2025-10-31 13:02:08 -07:00
LexManos 6a615f1a4b Add support for Forge shipping with JarInJar options. (#10683)
These are optional libraries that are only loaded when requested by mods.
Includes new ReadOnlyInMemoryFileSystem to improve performance of JarInJar parsing.
2025-10-30 14:48:46 -07:00
LexManos 4376cd6b85 Remove generic from AttachCapabilitiesEvent to help prevent modders from having unchecked casts. Closes #10707 2025-10-27 10:45:43 -07:00
LexManos d17a111dc0 Fix misaligned patch in DedicatedServer 2025-10-23 00:10:46 -07:00
Jonathing 1b99b2cea4 Revert "Remove runtime mods.toml string substitution (#10698)" (#10701)
This reverts commit fc80e30289, 17ce723edf, and 0e6341a23e.
2025-10-21 17:51:24 -07:00
Matt 4ef6695a30 Add support for custom particle groups (#10692) 2025-10-20 17:21:50 -07:00
Jonathing 0e6341a23e Fix Minecraft version being incorrectly included in Forge mods.toml (#10700) 2025-10-20 01:06:07 -07:00
Jonathing 17ce723edf Fix universal jar missing Forge version (#10699) 2025-10-20 00:41:21 +01:00
Paint_Ninja fc80e30289 Remove runtime mods.toml string substitution (#10698) 2025-10-19 21:45:17 +01:00
LexManos ecd1491f96 Fix Manually created RegistryObjects not being populated. Closes #10686
This partially reverts commit 1268d4c3a8acd9f780d2048d5a527206707f8e35.
But makes the classes package private so nobody can access.
2025-10-12 10:49:47 -07:00
Paint_Ninja 8f7fbc76e1 Simplify ModLoader#hasCompletedState, some more cleanup to FML internals 2025-10-11 13:43:14 +01:00
Paint_Ninja ec405958c9 Skip processing vanilla classes in RuntimeDistCleaner when running on a client (#10682) 2025-10-10 21:31:14 +01:00
Paint_Ninja e2ff990601 Require CapabilityToken subclasses to be anonymous (#10681) 2025-10-10 21:30:22 +01:00
Paint_Ninja 8443262e39 Improve event javadocs based on community feedback, apply jSpecify to more events 2025-10-10 14:57:38 +01:00
LexManos 8e09d47cd6 1.21.10 2025-10-07 10:20:47 -07:00
Paint_Ninja 0404d2d776 Bump EventBus for more javadocs 2025-10-06 20:14:28 +01:00
Paint_Ninja aea804cb6e Make BuildCreativeModeTabContentsEvent a record 2025-10-06 20:12:13 +01:00
LexManos 0bffcb83c5 Fix CustomizeGuiOverlayEvent mistakenly being a RecordEvent 2025-10-05 09:51:12 -07:00
Paint_Ninja 9a022d1451 Move more events away from the mod BusGroup (#10676)
Also adds more validation to strict mode. See the GitHub PR for further details.
2025-10-05 14:02:45 +01:00
LexManos 58aa3b276c Add the new vanilla info object to Keyboard and Mouse related events. 2025-10-02 12:09:01 -07:00
168 changed files with 2705 additions and 944 deletions

No files matched your search

+2 -2
View File
@@ -7,8 +7,8 @@ plugins {
id 'net.minecraftforge.gradleutils' version '[2.6.4,2.7)'
id 'eclipse'
id 'de.undercouch.download' version '5.4.0'
id 'net.minecraftforge.gradle.patcher' version '[6.0.22,6.2)' apply false
id 'net.minecraftforge.gradle.mcp' version '[6.0.22,6.2)' apply false
id 'net.minecraftforge.gradle.patcher' version '[6.0.46,6.2)' apply false
id 'net.minecraftforge.gradle.mcp' version '[6.0.46,6.2)' apply false
id 'net.minecraftforge.gradlejarsigner' version '1.0.4'
id 'org.barfuin.gradle.taskinfo' version '2.1.0'
}
+1
View File
@@ -7,6 +7,7 @@ dependencies {
implementation 'org.ow2.asm:asm:9.8'
implementation 'org.ow2.asm:asm-tree:9.8'
implementation 'net.minecraftforge:srgutils:0.5.10'
implementation 'net.minecraftforge:JarJarMetadata:0.3.27'
implementation 'commons-io:commons-io:2.13.0'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'org.eclipse.jgit:org.eclipse.jgit:6.7.0.202309050840-r'
@@ -0,0 +1,415 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.forge.tasks;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import net.minecraftforge.jarjar.metadata.ContainedJarIdentifier;
import net.minecraftforge.jarjar.metadata.ContainedJarMetadata;
import net.minecraftforge.jarjar.metadata.ContainedVersion;
import net.minecraftforge.jarjar.metadata.Metadata;
import net.minecraftforge.jarjar.metadata.MetadataIOHandler;
import net.minecraftforge.jarjar.metadata.json.ArtifactVersionSerializer;
import net.minecraftforge.jarjar.metadata.json.ContainedJarIdentifierSerializer;
import net.minecraftforge.jarjar.metadata.json.ContainedJarMetadataSerializer;
import net.minecraftforge.jarjar.metadata.json.ContainedVersionSerializer;
import net.minecraftforge.jarjar.metadata.json.MetadataSerializer;
import net.minecraftforge.jarjar.metadata.json.VersionRangeSerializer;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.gradle.api.Action;
import org.gradle.api.DefaultTask;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.ExternalModuleDependency;
import org.gradle.api.artifacts.FileCollectionDependency;
import org.gradle.api.artifacts.MinimalExternalModuleDependency;
import org.gradle.api.artifacts.ModuleDependency;
import org.gradle.api.artifacts.ModuleIdentifier;
import org.gradle.api.artifacts.ModuleVersionIdentifier;
import org.gradle.api.artifacts.ResolvedArtifact;
import org.gradle.api.file.ProjectLayout;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Provider;
import org.gradle.api.provider.SetProperty;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.io.Serial;
import java.io.Serializable;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.zip.ZipFile;
// TODO SUPER SUPER SUPER UGLY, CLEAN UP IN FORGEDEV 7
@Deprecated(forRemoval = true) // Will be moved to JarJar plugin in ForgeDev 7
public abstract class JarJarMetadataOptions extends DefaultTask {
private static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(VersionRange.class, new VersionRangeSerializer())
.registerTypeAdapter(ArtifactVersion.class, new ArtifactVersionSerializer())
.registerTypeAdapter(DefaultArtifactVersion.class, new ArtifactVersionSerializer())
.registerTypeAdapter(ContainedJarIdentifier.class, new ContainedJarIdentifierSerializer())
.registerTypeAdapter(ContainedJarMetadata.class, new ContainedJarMetadataSerializer())
.registerTypeAdapter(ContainedVersion.class, new ContainedVersionSerializer())
.registerTypeAdapter(Metadata.class, new MetadataSerializer())
.setPrettyPrinting()
.create();
protected abstract @Input SetProperty<ResolvedDependencyInfoImpl> getResolvedDependencies();
protected abstract @OutputFile RegularFileProperty getMetadataFile();
protected abstract @Inject ProjectLayout getLayout();
// NOTE: I'm not adding a non-provider version. please just use the version catalog entries for now.
public void add(Provider<MinimalExternalModuleDependency> dependency, Action<? super ResolvedDependencyInfo> action) {
this.getResolvedDependencies().add(dependency.map(d -> {
var ret = ResolvedDependencyInfoImpl.from(this.getProject().getConfigurations(), d);
action.execute(ret);
return ret;
}));
}
@Inject
public JarJarMetadataOptions() {
this.getMetadataFile().convention(this.getLayout().getBuildDirectory().file(this.getName() + "/options.json"));
}
@TaskAction
protected void exec() {
record ForgeLocaterOptions(String resource, String layer, String id, List<ContainedJarMetadata> deps, ContainedJarMetadata meta, boolean nested) { }
var resolved = this.getResolvedDependencies().get();
var jars = new ArrayList<ForgeLocaterOptions>(resolved.size());
for (var dependency : resolved) {
var deps = new ArrayList<ContainedJarMetadata>();
try (var zip = new ZipFile(dependency.artifact)) {
var entry = zip.getEntry("META-INF/jarjar/metadata.json");
if (entry != null) {
try (var stream = zip.getInputStream(entry)) {
var meta = MetadataIOHandler.fromStream(stream).orElse(null);
if (meta == null)
throw new IllegalStateException("Corrupt metadata.json in " + dependency.artifact.getAbsolutePath());
for (var dep : meta.jars())
deps.add(new ContainedJarMetadata(dep.identifier(), dep.version(), "", dep.isObfuscated()));
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
jars.add(new ForgeLocaterOptions(
dependency.resource,
dependency.layer,
dependency.identifier,
deps,
new ContainedJarMetadata(
new ContainedJarIdentifier(validateGroup(dependency), dependency.module.getName()),
new ContainedVersion(null, parseVersion(dependency)),
Objects.requireNonNull(dependency.path, "Dependency path is unspecified: " + dependency.asString),
false
),
dependency.nested
));
}
try {
record Meta(List<ForgeLocaterOptions> options){}
Files.writeString(
this.getMetadataFile().getAsFile().get().toPath(),
GSON.toJson(new Meta(jars), Meta.class)
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private String validateGroup(ResolvedDependencyInfoImpl dependency) {
try {
return Objects.requireNonNull(dependency.module.getGroup());
} catch (NullPointerException e) {
throw new IllegalArgumentException("Module dependency has no group: " + dependency.asString, e);
}
}
private ArtifactVersion parseVersion(ResolvedDependencyInfoImpl resolved) {
try {
return VersionRange.createFromVersionSpec(Objects.requireNonNull(resolved.version)).getRecommendedVersion();
} catch (InvalidVersionSpecificationException e) {
throw new IllegalArgumentException("Version is invalid for: " + resolved.asString, e);
} catch (NullPointerException e) {
throw new IllegalArgumentException("Version is unspecified for: " + resolved.asString, e);
}
}
private VersionRange parseVersionRange(ResolvedDependencyInfoImpl dependency) {
if (dependency.hasManuallySpecifiedRange) {
try {
return VersionRange.createFromVersionSpec(dependency.versionRange);
} catch (InvalidVersionSpecificationException e) {
throw new IllegalArgumentException("Version is invalid for: " + dependency.asString, e);
}
} else {
try {
return VersionRange.createFromVersionSpec("[%s,)".formatted(Objects.requireNonNull(dependency.versionRange)));
} catch (InvalidVersionSpecificationException e) {
throw new IllegalArgumentException("Version range is invalid for: " + dependency.asString, e);
} catch (NullPointerException e) {
throw new IllegalArgumentException("Version is unspecified for: " + dependency.asString, e);
}
}
}
public interface ResolvedDependencyInfo {
void containedJarMetadata(Action<? super ContainedJarMetadataInfo> action);
void setResource(String resource);
void setLayer(String layer);
void setId(String identifier);
void setNested(boolean nested);
interface ContainedJarMetadataInfo {
void setGroup(String group);
void setName(String name);
void setPath(String version);
}
}
static final class ResolvedDependencyInfoImpl implements ResolvedDependencyInfo, ResolvedDependencyInfo.ContainedJarMetadataInfo, Serializable {
private static final @Serial long serialVersionUID = -7577318115877822993L;
final MinimalModuleVersionIdentifier module;
final String version;
String versionRange;
boolean hasManuallySpecifiedRange;
boolean nested;
final File artifact;
String path;
String resource;
String layer;
String identifier;
final String asString;
public ResolvedDependencyInfoImpl(MinimalModuleVersionIdentifier module, String version, File artifact, String asString) {
this.module = module;
this.version = version;
this.artifact = artifact;
this.asString = asString;
}
@Override
public void containedJarMetadata(Action<? super ContainedJarMetadataInfo> action) {
action.execute(this);
}
@Override
public void setGroup(String group) {
this.module.group = group;
}
@Override
public void setName(String name) {
this.module.name = name;
}
@Override
public void setPath(String path) {
this.path = path;
}
@Override
public void setResource(String resource) {
this.resource = resource;
}
@Override
public void setLayer(String layer) {
this.layer = layer;
}
@Override
public void setId(String identifier) {
this.identifier = identifier;
}
@Override
public void setNested(boolean nested) {
this.nested = nested;
}
static Set<File> getFiles(Set<ResolvedDependencyInfoImpl> resolvedDependencies) {
var ret = new HashSet<File>(resolvedDependencies.size());
for (var dependency : resolvedDependencies) {
ret.add(dependency.artifact);
}
return ret;
}
static ResolvedDependencyInfoImpl from(ConfigurationContainer configurations, Dependency dependency) {
var group = dependency.getGroup();
var name = dependency.getName();
var version = dependency.getVersion();
if (dependency instanceof FileCollectionDependency filesDependency) {
File artifact;
try {
artifact = filesDependency.getFiles().getSingleFile();
} catch (IllegalStateException e) {
// TODO fileCollectionDependencyIsNotSingleFile
throw e;
}
return new ResolvedDependencyInfoImpl(
new MinimalModuleVersionIdentifier(group, name, version),
version,
artifact,
filesDependency.toString()
);
} else if (dependency instanceof ModuleDependency moduleDependency) {
moduleDependency = moduleDependency.copy();
if (moduleDependency instanceof ExternalModuleDependency externalModuleDependency) {
externalModuleDependency.version(v -> v.strictly(version.toString()));
}
var detachedConfiguration = configurations.detachedConfiguration(moduleDependency);
detachedConfiguration.setTransitive(false);
ResolvedDependencyInfoImpl ret = null;
for (var artifact : detachedConfiguration.getResolvedConfiguration().getFirstLevelModuleDependencies().iterator().next().getModuleArtifacts()) {
var fileName = getFileName(artifact);
if (!fileName.endsWith(".jar"))
continue;
if (ret != null)
throw new IllegalArgumentException("Module dependency has too many Jar artifacts: " + moduleDependency);
ret = new ResolvedDependencyInfoImpl(
new MinimalModuleVersionIdentifier(group, name, artifact.getModuleVersion().getId().getVersion()),
version,
artifact.getFile(),
moduleDependency.toString()
);
}
if (ret == null)
throw new IllegalArgumentException("Module dependency has no Jar artifacts: " + moduleDependency);
return ret;
} else {
throw new IllegalArgumentException("Unsupported dependency type: " + dependency.getClass().getName() + " -- " + dependency);
}
}
private static String getFileName(ResolvedArtifact artifact) {
try {
return InvokerHelper.getProperty(artifact.getId(), "fileName").toString();
} catch (Throwable e) {
// NOTE: Why not just use this to begin with?
// ComponentArtifactIdentifier can have a getFileName() method, which doesn't necessarily resolve the file itself.
// This allows us to get the name of the file to be used without asking Gradle to download the file.
// So, if a file is not a JAR file, we can check the name without actually downloading it.
return artifact.getFile().getName();
}
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (ResolvedDependencyInfoImpl) obj;
return Objects.equals(this.module, that.module) &&
Objects.equals(this.version, that.version) &&
Objects.equals(this.versionRange, that.versionRange) &&
Objects.equals(this.artifact, that.artifact);
}
@Override
public int hashCode() {
return Objects.hash(module, version, versionRange, artifact);
}
@Override
public String toString() {
return "ResolvedDependencyInfo[" +
"module=" + module + ", " +
"fixedVersion=" + version + ", " +
"versionRange=" + versionRange + ", " +
"artifact=" + artifact + ']';
}
static final class MinimalModuleVersionIdentifier implements ModuleIdentifier, ModuleVersionIdentifier {
private static final @Serial long serialVersionUID = -955346236759069739L;
private String group;
private String name;
private final String version;
@Inject
public MinimalModuleVersionIdentifier(String group, String name, String version) {
this.group = group;
this.name = name;
this.version = version;
}
@Override
public ModuleIdentifier getModule() {
return this;
}
@Override
public String getGroup() {
return this.group;
}
@Override
public String getName() {
return this.name;
}
@Override
public String getVersion() {
return this.version;
}
@Override
public boolean equals(Object obj) {
return this == obj || obj instanceof MinimalModuleVersionIdentifier o
&& Objects.equals(this.group, o.group)
&& Objects.equals(this.name, o.name)
&& Objects.equals(this.version, o.version);
}
@Override
public int hashCode() {
return Objects.hash(group, name, version);
}
@Override
public String toString() {
return "MinimalModuleVersionIdentifier[" +
"group=" + group + ", " +
"name=" + name + ", " +
"version=" + version + ']';
}
}
}
}
+8
View File
@@ -113,6 +113,7 @@ dependencies {
// And this is only needed because custom file systems are REQUIRED to be on the boot classloader.
// This has ASM/BootStrap/Unsafe all because I haven't gotten around to moving UnionFileSystem out to its own project.
bootstrap(libs.jarjar.fs) // JarInJar file system
bootstrap(libs.roimfs) // JarInJar File System - FileSystems need to be on boot loader.
bootstrap(libs.bundles.jimfs) // In memory file system used for ForgeDev launches
bootstrap(libs.securemodules) // Has Union file system in it
bootstrap(libs.unsafe) // Needed by securemodules
@@ -134,7 +135,9 @@ dependencies {
installer(libs.maven.artifact)
installer(libs.bundles.terminalconsoleappender)
installer(libs.mixin)
installer(libs.mixinextras.forge)
installer(libs.bundles.jarjar)
installer(libs.roimfs)
installer(project(':fmlcore'))
installer(project(':fmlloader'))
@@ -299,6 +302,11 @@ tasks.userdevConfig.configure {
libraries.add(dep)
}
// TODO [ForgeDev][UserDev] Split API/Runtime elements in userdev config
// so we don't gotta keep making exceptions for MixinExtras
addCompileDependency(libs.mixinextras.common)
addAnnotationProcessorDependency(libs.mixinextras.common)
inject = '' // We don't have a userdev sourceset anymore. Empty as a gradle workaround...
runs {
client {
+4
View File
@@ -38,6 +38,10 @@ tasks.register('generateResources') {
dependsOn('writeManifest')
}
tasks.named('processResources') {
dependsOn(generateResources)
}
// Make sure out manifests get written before compiling the code, IDEA calls this task if you tell it to use the gradle build.
tasks.withType(JavaCompile).configureEach {
dependsOn 'generateResources'
+2 -2
View File
@@ -2,8 +2,8 @@
MinecraftForge
=============
[![Stable Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Stable&prefix=1.21.8-&query=$.promos["1.21.8-recommended"]&color=brightgreen&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc3RhciBmYS13LTE4IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1pY29uPSJzdGFyIiBkYXRhLXByZWZpeD0iZmFzIiBmb2N1c2FibGU9ImZhbHNlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjU5LjMgMTcuOEwxOTQgMTUwLjIgNDcuOSAxNzEuNWMtMjYuMiAzLjgtMzYuNyAzNi4xLTE3LjcgNTQuNmwxMDUuNyAxMDMtMjUgMTQ1LjVjLTQuNSAyNi4zIDIzLjIgNDYgNDYuNCAzMy43TDI4OCA0MzkuNmwxMzAuNyA2OC43YzIzLjIgMTIuMiA1MC45LTcuNCA0Ni40LTMzLjdsLTI1LTE0NS41IDEwNS43LTEwM2MxOS0xOC41IDguNS01MC44LTE3LjctNTQuNkwzODIgMTUwLjIgMzE2LjcgMTcuOGMtMTEuNy0yMy42LTQ1LjYtMjMuOS01Ny40IDB6IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)][Download]
[![Latest Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Latest&prefix=1.21.8-&query=$.promos["1.21.8-latest"]&color=blue&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtYnVnIGZhLXctMTYiIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLWljb249ImJ1ZyIgZGF0YS1wcmVmaXg9ImZhcyIgZm9jdXNhYmxlPSJmYWxzZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUxMS45ODggMjg4LjljLS40NzggMTcuNDMtMTUuMjE3IDMxLjEtMzIuNjUzIDMxLjFINDI0djE2YzAgMjEuODY0LTQuODgyIDQyLjU4NC0xMy42IDYxLjE0NWw2MC4yMjggNjAuMjI4YzEyLjQ5NiAxMi40OTcgMTIuNDk2IDMyLjc1OCAwIDQ1LjI1NS0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDBsLTU0LjczNi01NC43MzZDMzQ1Ljg4NiA0NjcuOTY1IDMxNC4zNTEgNDgwIDI4MCA0ODBWMjM2YzAtNi42MjctNS4zNzMtMTItMTItMTJoLTI0Yy02LjYyNyAwLTEyIDUuMzczLTEyIDEydjI0NGMtMzQuMzUxIDAtNjUuODg2LTEyLjAzNS05MC42MzYtMzIuMTA4bC01NC43MzYgNTQuNzM2Yy0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDAtMTIuNDk2LTEyLjQ5Ny0xMi40OTYtMzIuNzU4IDAtNDUuMjU1bDYwLjIyOC02MC4yMjhDOTIuODgyIDM3OC41ODQgODggMzU3Ljg2NCA4OCAzMzZ2LTE2SDMyLjY2NkMxNS4yMyAzMjAgLjQ5MSAzMDYuMzMuMDEzIDI4OC45LS40ODQgMjcwLjgxNiAxNC4wMjggMjU2IDMyIDI1Nmg1NnYtNTguNzQ1bC00Ni42MjgtNDYuNjI4Yy0xMi40OTYtMTIuNDk3LTEyLjQ5Ni0zMi43NTggMC00NS4yNTUgMTIuNDk4LTEyLjQ5NyAzMi43NTgtMTIuNDk3IDQ1LjI1NiAwTDE0MS4yNTUgMTYwaDIyOS40ODlsNTQuNjI3LTU0LjYyN2MxMi40OTgtMTIuNDk3IDMyLjc1OC0xMi40OTcgNDUuMjU2IDAgMTIuNDk2IDEyLjQ5NyAxMi40OTYgMzIuNzU4IDAgNDUuMjU1TDQyNCAxOTcuMjU1VjI1Nmg1NmMxNy45NzIgMCAzMi40ODQgMTQuODE2IDMxLjk4OCAzMi45ek0yNTcgMGMtNjEuODU2IDAtMTEyIDUwLjE0NC0xMTIgMTEyaDIyNEMzNjkgNTAuMTQ0IDMxOC44NTYgMCAyNTcgMHoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=
[![Stable Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Stable&prefix=1.21.10-&query=$.promos["1.21.10-recommended"]&color=brightgreen&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc3RhciBmYS13LTE4IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1pY29uPSJzdGFyIiBkYXRhLXByZWZpeD0iZmFzIiBmb2N1c2FibGU9ImZhbHNlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjU5LjMgMTcuOEwxOTQgMTUwLjIgNDcuOSAxNzEuNWMtMjYuMiAzLjgtMzYuNyAzNi4xLTE3LjcgNTQuNmwxMDUuNyAxMDMtMjUgMTQ1LjVjLTQuNSAyNi4zIDIzLjIgNDYgNDYuNCAzMy43TDI4OCA0MzkuNmwxMzAuNyA2OC43YzIzLjIgMTIuMiA1MC45LTcuNCA0Ni40LTMzLjdsLTI1LTE0NS41IDEwNS43LTEwM2MxOS0xOC41IDguNS01MC44LTE3LjctNTQuNkwzODIgMTUwLjIgMzE2LjcgMTcuOGMtMTEuNy0yMy42LTQ1LjYtMjMuOS01Ny40IDB6IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)][Download]
[![Latest Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Latest&prefix=1.21.10-&query=$.promos["1.21.10-latest"]&color=blue&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtYnVnIGZhLXctMTYiIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLWljb249ImJ1ZyIgZGF0YS1wcmVmaXg9ImZhcyIgZm9jdXNhYmxlPSJmYWxzZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUxMS45ODggMjg4LjljLS40NzggMTcuNDMtMTUuMjE3IDMxLjEtMzIuNjUzIDMxLjFINDI0djE2YzAgMjEuODY0LTQuODgyIDQyLjU4NC0xMy42IDYxLjE0NWw2MC4yMjggNjAuMjI4YzEyLjQ5NiAxMi40OTcgMTIuNDk2IDMyLjc1OCAwIDQ1LjI1NS0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDBsLTU0LjczNi01NC43MzZDMzQ1Ljg4NiA0NjcuOTY1IDMxNC4zNTEgNDgwIDI4MCA0ODBWMjM2YzAtNi42MjctNS4zNzMtMTItMTItMTJoLTI0Yy02LjYyNyAwLTEyIDUuMzczLTEyIDEydjI0NGMtMzQuMzUxIDAtNjUuODg2LTEyLjAzNS05MC42MzYtMzIuMTA4bC01NC43MzYgNTQuNzM2Yy0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDAtMTIuNDk2LTEyLjQ5Ny0xMi40OTYtMzIuNzU4IDAtNDUuMjU1bDYwLjIyOC02MC4yMjhDOTIuODgyIDM3OC41ODQgODggMzU3Ljg2NCA4OCAzMzZ2LTE2SDMyLjY2NkMxNS4yMyAzMjAgLjQ5MSAzMDYuMzMuMDEzIDI4OC45LS40ODQgMjcwLjgxNiAxNC4wMjggMjU2IDMyIDI1Nmg1NnYtNTguNzQ1bC00Ni42MjgtNDYuNjI4Yy0xMi40OTYtMTIuNDk3LTEyLjQ5Ni0zMi43NTggMC00NS4yNTUgMTIuNDk4LTEyLjQ5NyAzMi43NTgtMTIuNDk3IDQ1LjI1NiAwTDE0MS4yNTUgMTYwaDIyOS40ODlsNTQuNjI3LTU0LjYyN2MxMi40OTgtMTIuNDk3IDMyLjc1OC0xMi40OTcgNDUuMjU2IDAgMTIuNDk2IDEyLjQ5NyAxMi40OTYgMzIuNzU4IDAgNDUuMjU1TDQyNCAxOTcuMjU1VjI1Nmg1NmMxNy45NzIgMCAzMi40ODQgMTQuODE2IDMxLjk4OCAzMi45ek0yNTcgMGMtNjEuODU2IDAtMTEyIDUwLjE0NC0xMTIgMTEyaDIyNEMzNjkgNTAuMTQ0IDMxOC44NTYgMCAyNTcgMHoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=
)][Download] [![Discord](https://img.shields.io/discord/313125603924639766.svg?color=%237289da&label=Discord&logo=discord&logoColor=%237289da)][Discord] [![Support](https://img.shields.io/badge/Patreon-Support-orange.svg?logo=Patreon)][Patreon]
Forge is a free, open-source modding API all of your favourite mods use!
@@ -41,14 +41,14 @@ public class ModList {
private List<ModContainer> sortedContainers;
private ModList(final List<ModFile> modFiles, final List<ModInfo> sortedList) {
this.modFiles = modFiles.stream().map(ModFile::getModFileInfo).toList();
var modFileInfos = this.modFiles = modFiles.stream().map(ModFile::getModFileInfo).toList();
this.sortedList = sortedList.stream().map(IModInfo.class::cast).toList();
var byId = new HashMap<String, IModFileInfo>();
for (var file : this.modFiles) {
for (var file : modFileInfos) {
for (var mod : file.getMods())
byId.put(mod.getModId(), mod.getOwningFile());
}
this.fileById = Collections.unmodifiableMap(byId);
this.fileById = Map.copyOf(byId);
CrashReportCallables.registerCrashCallable("Mod List", this::crashReport);
}
@@ -67,7 +67,7 @@ public class ModList {
}
private String crashReport() {
return "\n"+applyForEachModFile(this::fileToLine).collect(Collectors.joining("\n\t\t", "\t\t", ""));
return "\n" + applyForEachModFile(this::fileToLine).collect(Collectors.joining("\n\t\t", "\t\t", ""));
}
public static ModList of(List<ModFile> modFiles, List<ModInfo> sortedList) {
@@ -126,10 +126,9 @@ public class ModList {
map(IModFileInfo::getFile).
distinct().
map(IModFile::getScanResult).
collect(Collectors.toList());
toList();
}
return modFileScanData;
}
public void forEachModFile(Consumer<IModFile> fileConsumer) {
@@ -84,20 +84,20 @@ public class ModLoader {
private ModList modList;
private ModLoader() {
this.loadingModList = FMLLoader.getLoadingModList();
this.loadingExceptions = this.loadingModList.getErrors().stream()
var loadingModList = this.loadingModList = FMLLoader.getLoadingModList();
var loadingExceptions = this.loadingExceptions = loadingModList.getErrors().stream()
.flatMap(ModLoadingException::fromEarlyException)
.collect(Collectors.toList());
this.loadingWarnings = this.loadingModList.getBrokenFiles().stream()
this.loadingWarnings = loadingModList.getBrokenFiles().stream()
.map(file -> new ModLoadingWarning(null, ModLoadingStage.VALIDATE, InvalidModIdentifier.identifyJarProblem(file.getFilePath()).orElse("fml.modloading.brokenfile"), file.getFileName()))
.collect(Collectors.toList());
if (this.loadingExceptions.isEmpty()) {
if (loadingExceptions.isEmpty()) {
this.erroredModInfos = Collections.emptySet();
} else {
this.erroredModInfos = Collections.newSetFromMap(new IdentityHashMap<>());
this.erroredModInfos.addAll(this.loadingExceptions.stream().map(ModLoadingException::getModInfo).toList());
this.erroredModInfos.addAll(loadingExceptions.stream().map(ModLoadingException::getModInfo).toList());
}
this.loadingModList.getModFiles().stream()
loadingModList.getModFiles().stream()
.filter(ModFileInfo::missingLicense)
.filter(modFileInfo -> modFileInfo.getMods().stream().noneMatch(this.erroredModInfos::contains)) //Ignore files where any other mod already encountered an error
.map(modFileInfo -> new ModLoadingException(null, ModLoadingStage.VALIDATE, "fml.modloading.missinglicense", null, modFileInfo.getFile()))
@@ -112,16 +112,16 @@ public class ModLoader {
private static String computeLanguageList() {
return "\n" + FMLLoader.getLanguageLoadingProvider()
.applyForEach(lp -> lp.name() + "@" + lp.getClass().getPackage().getImplementationVersion())
.applyForEach(lp -> lp.name() + '@' + lp.getClass().getPackage().getImplementationVersion())
.collect(Collectors.joining("\n\t\t", "\t\t", ""));
}
private static String computeModLauncherServiceList() {
final List<Map<String, String>> mods = FMLLoader.modLauncherModList();
return "\n"+mods.stream().map(mod->mod.getOrDefault("file","nofile")+
" "+mod.getOrDefault("name", "missing")+
" "+mod.getOrDefault("type","NOTYPE")+
" "+mod.getOrDefault("description", "")).
return "\n" + mods.stream().map(mod -> mod.getOrDefault("file","nofile") +
' ' + mod.getOrDefault("name", "missing") +
' ' + mod.getOrDefault("type","NOTYPE") +
' ' + mod.getOrDefault("description", "")).
collect(Collectors.joining("\n\t\t","\t\t",""));
}
@@ -254,7 +254,7 @@ public class ModLoader {
}
private void handleInlineTransition(final Consumer<ModList> transition, final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker) {
var pb = StartupMessageManager.addProgressBar("State transition " +state.name()+" running", 0);
var pb = StartupMessageManager.addProgressBar("State transition " + state.name() + " running", 0);
syncExecutor.drive(ticker);
transition.accept(this.modList);
syncExecutor.drive(ticker);
@@ -353,11 +353,17 @@ public class ModLoader {
return loadingStateValid;
}
/** @deprecated Use {@link #hasCompletedState(IModLoadingState)} instead */
@Deprecated(forRemoval = true)
public boolean hasCompletedState(final String stateName) {
IModLoadingState state = stateManager.findState(stateName);
return completedStates.contains(state);
}
public boolean hasCompletedState(IModLoadingState state) {
return completedStates.contains(state);
}
public static <T extends IModBusEvent> void runEventGenerator(Function<ModContainer, T> generator) {
if (!loadingStateValid) {
LOGGER.error("Cowardly refusing to send event generator to a broken mod state");
@@ -19,7 +19,13 @@ public class ModLoadingContext {
private Object languageExtension;
/**
* @deprecated Use the context provided by your language loader in your mod's constructor
* @deprecated Use the context provided by your language loader in your mod's constructor. Example for Java mods: {@snippet :
* @Mod("examplemod")
* public final class ExampleMod {
* public ExampleMod(FMLJavaModLoadingContext context) {
* // Use context from the param instead of FMLJavaModLoadingContext.get() or ModLoadingContext.get()
* }
* }}
*/
@Deprecated(forRemoval = true, since="1.21.1")
public static ModLoadingContext get() {
@@ -7,6 +7,7 @@ package net.minecraftforge.fml;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
@@ -59,7 +60,7 @@ public record ModLoadingState(
*/
@Deprecated(since = "1.21.3", forRemoval = true)
public static ModLoadingState empty(final String name, final String previous, final ModLoadingPhase phase) {
return of(name, phase).after(previous).empty();
return new ModLoadingState(name, previous, ml -> "", ml -> 0, phase, Optional.empty(), Optional.empty());
}
/**
@@ -120,6 +121,22 @@ public record ModLoadingState(
return new Builder(name, phase);
}
@Override
public boolean equals(Object o) {
return o instanceof ModLoadingState that
&& this.phase == that.phase
&& this.name.equals(that.name)
&& Objects.equals(this.previous, that.previous);
}
@Override
public int hashCode() {
int result = phase.hashCode();
result = 31 * result + name.hashCode();
result = 31 * result + Objects.hashCode(previous);
return result;
}
public static class Builder {
private final String name;
private final ModLoadingPhase phase;
@@ -41,11 +41,13 @@ public class ModStateManager {
}
public IModLoadingState findState(final String stateName) {
return stateMap.values()
var result = stateMap.values()
.stream()
.flatMap(List::stream)
.filter(mls -> mls.name().equals(stateName))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown IModLoadingState: " + stateName));
.orElse(null);
if (result != null) return result;
else throw new IllegalArgumentException("Unknown IModLoadingState: " + stateName);
}
}
@@ -8,16 +8,22 @@ package net.minecraftforge.fml.event;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.InheritableEvent;
import org.jspecify.annotations.NonNull;
/**
* Marker interface for events dispatched on the ModLifecycle event bus instead of the primary event bus
* Marker interface for mod lifecycle events dispatched on the
* {@linkplain net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext#getModBusGroup() mod BusGroup} instead
* of the {@linkplain BusGroup#DEFAULT default BusGroup}.
*
* @apiNote Each mod gets its own unique mod BusGroup instance, obtained from the mod loading context. Most events
* should go on the default BusGroup.
*/
public interface IModBusEvent extends InheritableEvent {
/**
* @param modEventBusGroup Obtained from {@link net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext#getModBusGroup()} or your language provider's equivalent.
* @param eventClass The event type you want to get the associated bus for.
*/
static <T extends IModBusEvent> EventBus<T> getBus(BusGroup modEventBusGroup, Class<T> eventClass) {
static <T extends IModBusEvent> @NonNull EventBus<@NonNull T> getBus(@NonNull BusGroup modEventBusGroup, @NonNull Class<T> eventClass) {
// Do not copy! Temporary solution until the FML rewrite is complete. May throw in future
// without prior notice.
//
+21
View File
@@ -36,6 +36,7 @@ dependencies {
implementation(libs.accesstransformers)
implementation(libs.terminalconsoleappender)
implementation(libs.jimfs)
implementation(libs.roimfs)
// Needed because we have a custom log4j plugin, and they removed package scanning and require a data file to be generated
implementation(libs.log4j.core)
@@ -103,11 +104,31 @@ tasks.register('writeForgeVersionJson') {
}
}
tasks.register('jarJarOptionsJson', net.minecraftforge.forge.tasks.JarJarMetadataOptions) {
metadataFile = project.file('src/main/resources/jarjar_options.json')
// This is resolved too early by cpw.mods.modlauncher.TransformationServicesHandler.discoverServices(DiscoveryData) But eventually...
//add(libs.mixin, 'org/spongepowered/asm/mixin/Mixin.class')
add(libs.mixinextras.forge) {
containedJarMetadata {
path = ''
}
resource = 'com/llamalad7/mixinextras/platform/forge/MixinExtrasConfigPlugin.class'
layer = 'GAMELIBRARY'
id = 'mixinextras'
nested = true
}
}
tasks.named('generateResources').configure {
dependsOn('eclipseJdt')
dependsOn('eclipseJdtApt')
dependsOn('eclipseFactorypath')
dependsOn('writeForgeVersionJson')
dependsOn('jarJarOptionsJson')
}
tasks.named('sourcesJar') {
dependsOn('jarJarOptionsJson')
}
eclipse {
@@ -34,11 +34,10 @@ import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
* }</code>
* </pre>
*/
public class CapabilityTokenSubclass implements ILaunchPluginService {
public final class CapabilityTokenSubclass implements ILaunchPluginService {
private static final String FUNC_NAME = "getType";
private static final String FUNC_DESC = "()Ljava/lang/String;";
private static final String CAP_INJECT = "net/minecraftforge/common/capabilities/CapabilityToken"; //Don't directly reference this to prevent class loading.
private static final String CAP_INJECT = "net/minecraftforge/common/capabilities/CapabilityToken"; // Don't directly reference this to prevent class loading.
@Override
public String name() {
@@ -56,8 +55,11 @@ public class CapabilityTokenSubclass implements ILaunchPluginService {
String internalName = classType.getInternalName();
if (internalName.startsWith("net/minecraft/") || internalName.startsWith("com/mojang/"))
return NAY;
if (internalName.startsWith("net/minecraftforge/common/capabilities/"))
return YAY;
return YAY;
return internalName.contains("$") ? YAY : NAY; // Anonymous subclasses only
}
@Override
@@ -70,7 +72,7 @@ public class CapabilityTokenSubclass implements ILaunchPluginService {
}
return ComputeFlags.SIMPLE_REWRITE;
} else if (CAP_INJECT.equals(classNode.superName)) {
Holder cls = new Holder();
var cls = new Object() { String value = null; };
SignatureReader reader = new SignatureReader(classNode.signature); // Having a node version of this would probably be useful.
reader.accept(new SignatureVisitor(Opcodes.ASM9) {
@@ -106,8 +108,4 @@ public class CapabilityTokenSubclass implements ILaunchPluginService {
return ComputeFlags.NO_REWRITE;
}
}
private static final class Holder {
String value;
}
}
@@ -146,9 +146,8 @@ public class FMLLoader {
}
public static List<ITransformationService.Resource> beginModScan(final Map<String,?> arguments) {
LOGGER.debug(SCAN,"Scanning for Mod Locators");
var modDiscoverer = new ModDiscoverer(arguments);
modValidator = modDiscoverer.discoverMods();
LOGGER.debug(SCAN, "Scanning for Mod Locators");
modValidator = ModDiscoverer.discoverMods(arguments);
var pluginResources = modValidator.getPluginResources();
return List.of(pluginResources);
}
@@ -36,9 +36,7 @@ public class LoadingModList {
.map(ModFile::getModFileInfo)
.map(ModFileInfo.class::cast)
.toList();
this.sortedList = sortedList.stream()
.map(ModInfo.class::cast)
.toList();
this.sortedList = sortedList.stream().toList();
this.fileById = this.modFiles.stream()
.map(ModFileInfo::getMods)
.flatMap(Collection::stream)
@@ -7,8 +7,10 @@ package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.jarhandling.SecureJar;
import cpw.mods.modlauncher.Launcher;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import cpw.mods.modlauncher.api.NamedPath;
import cpw.mods.modlauncher.api.TypesafeMap;
import cpw.mods.modlauncher.serviceapi.ITransformerDiscoveryService;
import org.jetbrains.annotations.ApiStatus;
@@ -40,6 +42,17 @@ public class ModDirTransformerDiscoverer implements ITransformerDiscoveryService
@Override
public void earlyInitialization(final String launchTarget, final String[] arguments) {
boolean isMixinEnabledInDev = false;
for (String arg : arguments) {
if (arg.startsWith("-mixin") || arg.startsWith("--mixin")) {
isMixinEnabledInDev = true;
break;
}
}
Launcher.INSTANCE.blackboard().putIfAbsent(
TypesafeMap.Key.getOrCreate(Launcher.INSTANCE.blackboard(), "isMixinEnabledInDev", Boolean.class),
isMixinEnabledInDev
);
ImmediateWindowHandler.load(launchTarget, arguments);
}
@@ -46,7 +46,7 @@ public class ModSorter {
List<ModFile> modFiles;
try {
modFiles = new UniqueModListBuilder(mods).buildUniqueList().modFiles();
modFiles = UniqueModListBuilder.buildUniqueList(mods).modFiles();
} catch (EarlyLoadingException e) {
// We cannot build any list with duped mods. We have to abort immediately and report it
// Note this will never actually throw an error because the duplicate checks are done in ModDiscovererer before we get to this phase
@@ -27,7 +27,7 @@ import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
public class RuntimeDistCleaner implements ILaunchPluginService {
public final class RuntimeDistCleaner implements ILaunchPluginService {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Marker DISTXFORM = MarkerFactory.getMarker("DISTXFORM");
private static String DIST;
@@ -52,6 +52,10 @@ public class RuntimeDistCleaner implements ILaunchPluginService {
if (internalName.startsWith("net/minecraftforge/"))
return NAY;
// No Vanilla classes use @OnlyIn(Dist.SERVER), so we can skip them entirely when we're running on a client
if (FMLEnvironment.dist.isClient() && (internalName.startsWith("net/minecraft/") || internalName.startsWith("com/mojang/")))
return NAY;
return YAY;
}
@@ -17,8 +17,6 @@ import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.joining;
@@ -26,19 +24,12 @@ import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
@ApiStatus.Internal
@Deprecated(since = "1.21.3", forRemoval = true) // TODO: [FML][Loading] Convert to package private in 1.22
public class UniqueModListBuilder {
private final static Logger LOGGER = LogUtils.getLogger();
public final class UniqueModListBuilder {
private UniqueModListBuilder() {}
private final List<ModFile> modFiles;
public UniqueModListBuilder(final List<ModFile> modFiles) {
this.modFiles = modFiles;
}
public UniqueModListData buildUniqueList() {
List<ModFile> uniqueModList;
List<ModFile> uniqueLibListWithVersion;
private static final Logger LOGGER = LogUtils.getLogger();
public static UniqueModListData buildUniqueList(List<ModFile> modFiles) {
// Collect mod files by module name. This will be used for deduping purposes
final Map<String, List<ModFile>> modFilesByFirstId = modFiles.stream()
.filter(mf -> mf.getModFileInfo() != null)
@@ -49,12 +40,12 @@ public class UniqueModListBuilder {
.collect(groupingBy(UniqueModListBuilder::getModId));
// Select the newest by artifact version sorting of non-unique files thus identified
uniqueModList = modFilesByFirstId.entrySet().stream()
final List<ModFile> uniqueModList = modFilesByFirstId.entrySet().stream()
.map(UniqueModListBuilder::selectNewestModInfo)
.toList();
// Select the newest by artifact version sorting of non-unique files thus identified
uniqueLibListWithVersion = libFilesWithVersionByModuleName.entrySet().stream()
final List<ModFile> uniqueLibListWithVersion = libFilesWithVersionByModuleName.entrySet().stream()
.map(UniqueModListBuilder::selectNewestModInfo)
.toList();
@@ -66,12 +57,10 @@ public class UniqueModListBuilder {
.collect(groupingBy(IModInfo::getModId));
// Transform to the full lib id list
final Map<String, List<ModFile>> versionedLibIds = uniqueLibListWithVersion.stream()
final List<List<ModFile>> versionedLibs = uniqueLibListWithVersion.stream()
.map(UniqueModListBuilder::getModId)
.collect(Collectors.toMap(
Function.identity(),
libFilesWithVersionByModuleName::get
));
.map(libFilesWithVersionByModuleName::get)
.toList();
// Its theoretically possible that some mod has somehow moved an id to a secondary place, thus causing a dupe.
// We can't handle this
@@ -84,24 +73,24 @@ public class UniqueModListBuilder {
)).toList();
if (!dupedModErrors.isEmpty()) {
LOGGER.error(LOADING, "Found duplicate mods:\n{}", dupedModErrors.stream().collect(joining("\n")));
LOGGER.error(LOADING, "Found duplicate mods:\n{}", String.join("\n", dupedModErrors));
throw new EarlyLoadingException("Duplicate mods found", null, dupedModErrors.stream()
.map(s -> new EarlyLoadingException.ExceptionData(s))
.map(EarlyLoadingException.ExceptionData::new)
.toList());
}
final List<String> dupedLibErrors = versionedLibIds.values().stream()
.filter(modFiles -> modFiles.size() > 1)
final List<String> dupedLibErrors = versionedLibs.stream()
.filter(libModFiles -> libModFiles.size() > 1)
.map(mods -> String.format("\tLibrary: '%s' from files: %s",
getModId(mods.getFirst()),
mods.stream().map(ModFile::getFileName).collect(joining(", "))
)).toList();
if (!dupedLibErrors.isEmpty()) {
LOGGER.error(LOADING, "Found duplicate plugins or libraries:\n{}", dupedLibErrors.stream().collect(joining("\n")));
LOGGER.error(LOADING, "Found duplicate plugins or libraries:\n{}", String.join("\n", dupedLibErrors));
throw new EarlyLoadingException("Duplicate plugins or libraries found", null, dupedLibErrors.stream()
.map(s -> new EarlyLoadingException.ExceptionData(s))
.map(EarlyLoadingException.ExceptionData::new)
.toList());
}
@@ -122,7 +111,7 @@ public class UniqueModListBuilder {
modInfoList.sort(Comparator.comparing(UniqueModListBuilder::getVersion).reversed());
LOGGER.debug("Selected file {} for modid {} with version {}", modInfoList.getFirst().getFileName(), fullList.getKey(), getVersion(modInfoList.getFirst()));
}
return modInfoList.get(0);
return modInfoList.getFirst();
}
private static ArtifactVersion getVersion(final ModFile mf) {
@@ -130,7 +119,7 @@ public class UniqueModListBuilder {
return mf.getJarVersion();
}
return mf.getModInfos().get(0).getVersion();
return mf.getModInfos().getFirst().getVersion();
}
private static String getModId(ModFile modFile) {
@@ -138,7 +127,7 @@ public class UniqueModListBuilder {
return modFile.getSecureJar().name();
}
return modFile.getModFileInfo().getMods().get(0).getModId();
return modFile.getModFileInfo().getMods().getFirst().getModId();
}
public record UniqueModListData(List<ModFile> modFiles, Map<String, List<ModFile>> modFilesByFirstId) {}
@@ -54,12 +54,16 @@ public abstract class AbstractModProvider implements IModProvider {
@Nullable
protected IModLocator.ModFileOrException createMod(Path path, boolean ignoreUnknown, String defaultType) {
var debug = path.toString();
if (debug.equals("/"))
debug = path.toUri().toString();
var mjm = new ModJarMetadata();
SecureJar sj = null;
try {
sj = SecureJar.from(jar -> loadMetaFromJar(jar, mjm), path);
} catch (Throwable t) {
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Failed to create secure jar for \"" + path + "\" - " + t.getMessage()));
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Failed to create secure jar for \"" + debug + "\" - " + t.getMessage()));
}
IModFile mod;
@@ -68,7 +72,7 @@ public abstract class AbstractModProvider implements IModProvider {
type = defaultType;
if (sj.moduleDataProvider().findFile(MODS_TOML).isPresent()) {
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MODS_TOML, type, path);
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MODS_TOML, type, debug);
mod = new ModFile(sj, this, ModFileParser::modsTomlParser);
// ModJarMetadata is only used when loading mods.toml mods as ModJarMetadata
mjm.setModFile(mod);
@@ -77,17 +81,17 @@ public abstract class AbstractModProvider implements IModProvider {
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("File \"%s\" is not a Forge mod and cannot be loaded. Look for a Forge version of this mod or consider alternative mods.".formatted(mod.getFileName())));
}
} else if (type != null) {
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", JarFile.MANIFEST_NAME, type, path);
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", JarFile.MANIFEST_NAME, type, debug);
mod = new ModFile(sj, this, this::manifestParser, type);
} else if (ignoreUnknown) {
return null;
} else
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Invalid mod file found " + path));
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Invalid mod file found " + debug));
return new IModLocator.ModFileOrException(mod, null);
}
private static JarMetadata loadMetaFromJar(SecureJar jar, ModJarMetadata mjm) {
protected static JarMetadata loadMetaFromJar(SecureJar jar, ModJarMetadata mjm) {
var info = jar.moduleDataProvider().open(MODULE_INFO).orElse(null);
if (info != null) {
try {
@@ -145,6 +149,12 @@ public abstract class AbstractModProvider implements IModProvider {
public <T> Optional<T> getConfigElement(String... key) {
return Optional.empty();
}
@Override
public List<? extends IConfigurable> getConfigList(String key) {
return Collections.emptyList();
}
@Override
public List<? extends IConfigurable> getConfigList(String... key) {
return Collections.emptyList();
@@ -74,7 +74,8 @@ public final class ClasspathLocator extends AbstractModProvider implements IModL
var ret = new ArrayList<ModFileOrException>();
for (var path : claimed) {
// Filter out anything found by the ServiceLoader
if (!ModDirTransformerDiscoverer.isServiceProvider(path))
if (!ModDirTransformerDiscoverer.isServiceProvider(path)
&& !JarInJarDependencyLocator.isOption(path))
ret.add(createMod(path));
}
return ret;
@@ -107,7 +108,7 @@ public final class ClasspathLocator extends AbstractModProvider implements IModL
return Path.of(URI.create(str));
}
private static Path getPathFromResource(ClassLoader cl, String resource) {
static Path getPathFromResource(ClassLoader cl, String resource) {
var url = cl.getResource(resource);
if (url == null)
return null;
@@ -5,22 +5,39 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.google.common.collect.ImmutableMap;
import com.electronwill.nightconfig.core.UnmodifiableConfig;
import com.electronwill.nightconfig.core.file.FileConfig;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.Launcher;
import cpw.mods.modlauncher.api.TypesafeMap;
import net.minecraftforge.fml.loading.EarlyLoadingException;
import net.minecraftforge.fml.loading.EarlyLoadingException.ExceptionData;
import net.minecraftforge.fml.loading.FMLEnvironment;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.locating.IDependencyLocator;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.ModFileLoadingException;
import net.minecraftforge.forgespi.locating.IModFile.Type;
import net.minecraftforge.jarjar.metadata.ContainedJarIdentifier;
import net.minecraftforge.jarjar.metadata.ContainedJarMetadata;
import net.minecraftforge.jarjar.metadata.ContainedVersion;
import net.minecraftforge.jarjar.metadata.MetadataIOHandler;
import net.minecraftforge.jarjar.selection.JarSelector;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.module.ModuleDescriptor;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
@@ -28,14 +45,20 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.StringJoiner;
import java.util.TreeMap;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ApiStatus.Internal
public class JarInJarDependencyLocator extends AbstractModProvider implements IDependencyLocator {
private static final String ROIMFS = "roimfs";
private static final String COLOR_CODE = "\u00a7";
private static final String RESET = COLOR_CODE + "r";
private static final String YELLOW = COLOR_CODE + "e";
@@ -43,32 +66,238 @@ public class JarInJarDependencyLocator extends AbstractModProvider implements ID
private static final String GREEN = COLOR_CODE + "2";
private static final Logger LOGGER = LogUtils.getLogger();
private static final Marker MARKER = MarkerFactory.getMarker("JAR-JAR"); // LogMarkers.SCAN;
private static final Map<Path, Option> OPTIONS = new HashMap<>();
private static volatile boolean optionsLoaded = false;
private static final Attributes.Name MIXIN_CONFIGS_ATTR = new Attributes.Name("MixinConfigs");
private static final Attributes.Name MIXIN_CONNECTOR_ATTR = new Attributes.Name("MixinConnector");
private static final ContainedJarMetadata MIXIN_EXTRAS_DEPENDENCY = makeMixinExtraDependency();
private static ContainedJarMetadata makeMixinExtraDependency() {
VersionRange range = null;
try {
range = VersionRange.createFromVersionSpec("[0,)");
} catch (InvalidVersionSpecificationException e) {
throw new RuntimeException("Failed to create version range for mixinextras-forge dependency", e);
}
return new ContainedJarMetadata(
new ContainedJarIdentifier("io.github.llamalad7", "mixinextras-forge"),
new ContainedVersion(range, null),
null, false
);
}
@Override
public String name() {
return "JarInJar";
}
private int fsID = 0;
private synchronized int nextId() {
return fsID++;
}
@Override
public List<IModFile> scanMods(Iterable<IModFile> loadedMods) {
final List<IModFile> sources = new ArrayList<>();
loadedMods.forEach(sources::add);
var mods = new ArrayList<IModFile>();
loadedMods.forEach(mods::add);
var dependenciesToLoad = JarSelector.detectAndSelect(
sources,
this::loadResourceFromModFile,
this::loadModFileFrom,
this::identifyMod,
this::exception
);
var selector = new Selector(mods);
if (dependenciesToLoad.isEmpty()) {
// Add a synthetic Mixin-Extras dependency if we detect anyone using Mixin
if (anyMixinsLoaded(mods)) {
if (!selector.isRequired(MIXIN_EXTRAS_DEPENDENCY.identifier()))
selector.addRequirement(MIXIN_EXTRAS_DEPENDENCY);
} else if (selector.entries.size() == mods.size()) {
LOGGER.info("No dependencies to load found. Skipping!");
return Collections.emptyList();
}
LOGGER.info("Found {} dependencies adding them to mods collection", dependenciesToLoad.size());
return dependenciesToLoad;
loadOptions();
// Ideally, the json would be in order so we only have to loop once. But its written this way just in case.
var seen = new HashSet<Option>();
var added = false;
do {
added = false;
for (var entry : OPTIONS.values()) {
if (seen.contains(entry) || !selector.isRequired(entry.meta.identifier()))
continue;
seen.add(entry);
entry.addTo(selector);
if (entry.deps != null && !entry.deps.isEmpty()) {
added = true;
for (var dep : entry.deps)
selector.addRequirement(dep);
}
}
} while (added);
var selected = selector.select();
if (LOGGER.isDebugEnabled()) {
var ids = new TreeMap<String, List<Entry>>();
for (var entry : selector.entries.values()) {
if (entry != FAILED && entry.coord != null)
ids.computeIfAbsent(entry.coord, id -> new ArrayList<>()).add(entry);
}
for (var entry : ids.entrySet()) {
LOGGER.info(MARKER, "JarJar Candidated for {}", entry.getKey());
Collections.sort(entry.getValue());
for (var option : entry.getValue())
LOGGER.info(MARKER, "\t{}{}", selected.contains(option) ? '*' : ' ', option);
}
}
if (selected.isEmpty()) {
LOGGER.info("No dependencies to load found. Skipping!");
return Collections.emptyList();
}
/* Since Java 11, the JRE decompresses all zip files to memory when getting a ByteChannel for them.
* JarJar only targets Minecraft 1.18+ which is Java17+ Which means we can exploit the fact that
* these are Disjointed file systems, and close all unselected libraries.
*
* https://github.com/openjdk/jdk11u/blob/25a9b9e5594e88c9e1e66f95c7ca45d5a9da8854/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java#L703
*
* Combined with the fact that the JarJarFileSystem has always simply called into FileSystems.newFileSystem for its wrapped targets.
* https://github.com/MinecraftForge/JarJar/blame/b39b2051439c81519d826d38898778bf3081356f/src/main/java/net/minecraftforge/jarjar/nio/pathfs/PathFileSystem.java#L38
*
* This effectively means that JarJar has ALWAYS been a wrapper around in-memory copies of every jar it scans.
*
* It also never closes these FileSystems, which potentially means they are leaked if the GC can't inherently GC them.
*
* Ideally we would extract these contained jars to a temp folder, so that we can use the nice optimizations Java has for ZipFile/JarFile.
* But in order to do that we would need to write the file to a place that could potentially have conflicts.
* To resolve those conflicts we should take a hint out of maven/gradle/minecraft's book and key the filepath based on a hash of the file being extracted.
* However, because that hash is no available in existing metadata we would need to re-calculate it.
*
* To do it in an efficient way we I needed to be able to share the single opened FileSystem with both the SecureJar implementation, and with our recursive search.
* So I use my new ReadOnlyInMemoryFileSystem which is specifically designed to use the same backing array for the JarInJar search as it does with the resulting dependency
* selected SecureJars.
*
* This means that if we wanted to we could rather efficiently calculate a hash for extracting the file. However, it also means that we don't have to extract to file to
* support ZipFileSystem caching properly. So as long as we are okay with not using the JRE's optimized ZipFile implementation, or we want to support legacy JarInJar metadata,
* using this efficient in memory file system is the best option performance wise. Extracting the files during launch without an efficient caching/unique identifier system
* would incur a lot of Disc IO which can be super slow depending on your setup.
*
* So, it is a stated goal of our future re-write to extract files into a cache directory to gain the benefits of JRE optimizations, using a FileSystem much simpler then
* the existing JarInJarFileSystem is good enough.
*/
// Lets close all unselected FileSystems
var wasSelected = new HashSet<>(selected);
for (var entry : selector.entries.values())
entry.cleanup(wasSelected.contains(entry));
LOGGER.info(MARKER, "Found {} dependencies adding them to mods collection", selected.size());
var ret = new ArrayList<IModFile>(selected.size());
// Now lets build the actual mod files
for (var entry : selected) {
try {
var root = entry.getFinalPath();
var mod = createMod(root, false, entry.type.name());
if (mod.ex() != null)
throw fail("Failed to load JarInJar file " + entry, mod.ex());
ret.add(mod.file());
} catch (EarlyLoadingException e) {
throw e;
} catch (Throwable e) {
throw fail("Failed to load JarInJar file " + entry, e);
}
}
return ret;
}
private static Boolean anyMixinsLoaded = null;
private static boolean anyMixinsLoaded(List<IModFile> mods) {
var ret = anyMixinsLoaded;
if (ret != null) return ret;
if (!FMLEnvironment.production
&& Launcher.INSTANCE.blackboard().get(TypesafeMap.Key.getOrCreate(Launcher.INSTANCE.blackboard(), "isMixinEnabledInDev", Boolean.class)).orElse(false)) {
anyMixinsLoaded = true;
return true;
}
for (var mod : mods) {
var attributes = mod.getSecureJar().moduleDataProvider().getManifest().getMainAttributes();
if (attributes.getValue(MIXIN_CONFIGS_ATTR) != null || attributes.getValue(MIXIN_CONNECTOR_ATTR) != null) {
anyMixinsLoaded = true;
return true;
}
}
anyMixinsLoaded = false;
return false;
}
private record Options(List<OptionMetadata> options) {}
private record OptionMetadata(String resource, Type layer, String id, List<ContainedJarMetadata> deps, ContainedJarMetadata meta, boolean nested) {}
private static void loadOptions() {
if (optionsLoaded)
return;
synchronized (OPTIONS) {
if (optionsLoaded)
return;
optionsLoaded = true;
var self = JarInJarDependencyLocator.class;
try (var stream = self.getModule().getResourceAsStream("/jarjar_options.json")) {
if (stream == null) {
LOGGER.error(MARKER, "Failed to find jarjar_options.json");
return;
}
var meta = MetadataIOHandler.getGson().fromJson(new InputStreamReader(stream), Options.class);
//var meta = MetadataIOHandler.fromStream(stream).orElse(null);
if (meta == null) {
LOGGER.error(MARKER, "Corrupted jarjar_options.json");
return;
}
for (var jar : meta.options()) {
Path path = ClasspathLocator.getPathFromResource(self.getClassLoader(), jar.resource());
if (path == null) {
LOGGER.error(MARKER, "Failed to find JarJar option for {}", jar.resource());
continue;
}
LOGGER.debug(MARKER, "Found JarJar Option: {}", path.toAbsolutePath());
if (jar.nested())
OPTIONS.put(path, new NestedOption(jar.id(), path, jar.layer(), jar.meta(), jar.deps()));
else
OPTIONS.put(path, new Option(jar.id(), path, jar.layer(), jar.meta(), jar.deps()));
}
} catch (Throwable e) {
LOGGER.error(MARKER, "Failed to read JarJar Options file", e);
}
}
}
static boolean isOption(Path path) {
loadOptions();
return OPTIONS.containsKey(path);
}
private static EarlyLoadingException fail(String message, @Nullable Throwable cause) {
LOGGER.error(MARKER, message);
return new EarlyLoadingException(message, cause, Collections.emptyList());
}
@Override
@@ -76,89 +305,7 @@ public class JarInJarDependencyLocator extends AbstractModProvider implements ID
return IModFile.Type.GAMELIBRARY.name();
}
protected Optional<IModFile> loadModFileFrom(IModFile file, Path path) {
try {
final Path pathInModFile = file.findResource(path.toString());
final URI filePathUri = new URI("jij:" + (pathInModFile.toAbsolutePath().toUri().getRawSchemeSpecificPart())).normalize();
final Map<String, ?> outerFsArgs = ImmutableMap.of("packagePath", pathInModFile);
final FileSystem zipFS = FileSystems.newFileSystem(filePathUri, outerFsArgs);
final Path pathInFS = zipFS.getPath("/");
final IModFile.Type parentType = file.getType();
final String modType;
if (parentType == IModFile.Type.LIBRARY || parentType == IModFile.Type.LANGPROVIDER) {
modType = IModFile.Type.LIBRARY.name();
} else {
modType = IModFile.Type.GAMELIBRARY.name();
}
return Optional.of(createMod(pathInFS, false, modType).file());
} catch (Exception e) {
LOGGER.error("Failed to load mod file {} from {}", path, file.getFileName());
final RuntimeException exception = new ModFileLoadingException("Failed to load mod file " + file.getFileName());
exception.initCause(e);
throw exception;
}
}
protected EarlyLoadingException exception(Collection<JarSelector.ResolutionFailureInformation<IModFile>> failedDependencies) {
final List<EarlyLoadingException.ExceptionData> errors = failedDependencies.stream()
.filter(entry -> !entry.sources().isEmpty()) //Should never be the case, but just to be sure
.map(JarInJarDependencyLocator::buildExceptionData)
.toList();
return new EarlyLoadingException(failedDependencies.size() + " Dependency restrictions were not met.", null, errors);
}
@NotNull
private static EarlyLoadingException.ExceptionData buildExceptionData(JarSelector.ResolutionFailureInformation<IModFile> entry) {
return new EarlyLoadingException.ExceptionData(
getErrorTranslationKey(entry),
entry.identifier().group() + ":" + entry.identifier().artifact(),
entry.sources()
.stream()
.flatMap(JarInJarDependencyLocator::getModWithVersionRangeStream)
.map(JarInJarDependencyLocator::formatError)
.collect(Collectors.joining(", "))
);
}
@NotNull
private static String getErrorTranslationKey(JarSelector.ResolutionFailureInformation<IModFile> entry) {
return entry.failureReason() == JarSelector.FailureReason.VERSION_RESOLUTION_FAILED ?
"fml.dependencyloading.conflictingdependencies" :
"fml.dependencyloading.mismatchedcontaineddependencies";
}
@NotNull
private static Stream<ModWithVersionRange> getModWithVersionRangeStream(JarSelector.SourceWithRequestedVersionRange<IModFile> file) {
return file.sources()
.stream()
.map(IModFile::getModFileInfo)
.flatMap(modFileInfo -> modFileInfo.getMods().stream())
.map(modInfo -> new ModWithVersionRange(modInfo, file.requestedVersionRange(), file.includedVersion()));
}
protected Optional<InputStream> loadResourceFromModFile(IModFile modFile, Path path) {
try {
var pathInModFile = modFile.findResource(path.toString());
if (!Files.exists(pathInModFile)) {
LOGGER.debug("Failed to load resource {} from {}, it does not contain dependency information.", path, modFile.getFileName());
return Optional.empty();
}
return Optional.of(Files.newInputStream(pathInModFile));
} catch (Exception e) {
LOGGER.error("Failed to load resource {} from mod {}, cause {}", path, modFile.getFileName(), e);
return Optional.empty();
}
}
@NotNull
private static String formatError(ModWithVersionRange modWithVersionRange){
return YELLOW + modWithVersionRange.modInfo().getModId() + RESET + " - " +
RED + modWithVersionRange.versionRange().toString() + RESET + " - " +
GREEN + modWithVersionRange.artifactVersion().toString() + RESET;
}
protected String identifyMod(IModFile modFile) {
protected static String identifyMod(IModFile modFile) {
if (modFile.getModFileInfo() != null && !modFile.getModInfos().isEmpty())
return modFile.getModInfos().stream().map(IModInfo::getModId).collect(Collectors.joining());
@@ -169,5 +316,377 @@ public class JarInJarDependencyLocator extends AbstractModProvider implements ID
return modFile.getFileName();
}
private record ModWithVersionRange(IModInfo modInfo, VersionRange versionRange, ArtifactVersion artifactVersion) {}
private static final Entry FAILED = new Entry("FAILED ENTRY", Path.of(""), Type.LIBRARY, null);
private static sealed class Entry implements Comparable<Entry> {
final String id;
final Path path;
final Type type;
final @Nullable String coord;
private Entry(String id, Path path, Type type, @Nullable String coord) {
this.id = id;
this.path = path;
this.type = type;
this.coord = coord;
}
Path getFinalPath() {
return path;
}
void cleanup(boolean selected) {
}
@Override
public String toString() {
if (path.getFileName() == null)
return path.toUri().toString();
return path.getFileName().toString();
}
@Nullable
Path getResource(String path) {
return null;
}
@Override
public int compareTo(Entry o) {
return o.toString().compareTo(toString());
}
}
private static final class ModEntry extends Entry {
private final IModFile mod;
ModEntry(IModFile mod) {
super(identifyMod(mod), mod.getFilePath(), mod.getType(), null);
this.mod = mod;
}
@Override
@Nullable
Path getResource(String path) {
return mod.findResource(path);
}
}
private static sealed class Option extends Entry {
final ContainedJarMetadata meta;
final List<ContainedJarMetadata> deps;
private Option(String id, Path path, Type type, ContainedJarMetadata meta, List<ContainedJarMetadata> deps) {
super(id, path, type, meta.identifier().group() + ':' + meta.identifier().artifact());
this.meta = meta;
this.deps = deps;
}
public void addTo(Selector selector) {
selector.option(this, meta);
}
}
private static final class NestedOption extends Option {
private FileSystem zip;
private Path root;
private NestedOption(String id, Path path, Type type, ContainedJarMetadata meta, List<ContainedJarMetadata> deps) {
super(id, path, type, meta, deps);
}
@Override
public void addTo(Selector selector) {
super.addTo(selector);
selector.add(this);
}
@Override
void cleanup(boolean selected) {
if (this.zip != null) {
try {
zip.close();
} catch (IOException e) {
LOGGER.error(MARKER, "Failed to close unselected FileSystem {}", this, e);
}
}
}
@Nullable
Path getResource(String path) {
if (zip == null) {
try {
this.zip = FileSystems.newFileSystem(this.path);
this.root = zip.getRootDirectories().iterator().next();
} catch (IOException e) {
LOGGER.error(MARKER, "Failed to open FileSystem for option root {}", this, e);
return null;
}
}
var target = root.resolve(path);
if (Files.exists(target))
return target;
return null;
}
}
private static final class Nested extends Entry {
private final Entry parent;
private final String nestedPath;
private final Path zipPath;
Nested(String id, Path path, Type type, String coords, Entry parent, String nestedPath, Path zipPath) {
super(id, path, type, coords);
this.parent = parent;
this.nestedPath = nestedPath;
this.zipPath = zipPath;
}
/*
* Due to a bug in Mixin, anything that is not on the DefaultFileSystem MUST be a directory.
* Or else it can't resolve resources correctly. So for anything we put on the roimfs we need
* to return the nested zip file system.
*
* This will be an annoying to backport, but we can cross that bridge when we come to it.
*
* See: https://github.com/SpongePowered/Mixin/blob/4053421aa10aaac6127d969028a29c94fe3054f6/src/main/java/org/spongepowered/asm/launch/platform/MainAttributes.java#L102
*/
@Override
public Path getFinalPath() {
return zipPath;
}
@Override
void cleanup(boolean selected) {
if (selected)
return;
LOGGER.info(MARKER, "Closeing unselected FileSystem {}", this);
try {
zipPath.getFileSystem().close();
path.getFileSystem().close();
} catch (IOException e) {
LOGGER.error(MARKER, "Failed to close unselected FileSystem {}", this, e);
}
}
@Override
@Nullable
Path getResource(String path) {
return zipPath.resolve(path);
}
@Override
public String toString() {
return parent + "!/" + nestedPath;
}
}
private record Key(Entry parent, String path) {}
private final class Selector extends JarSelector<Entry> {
private final HashMap<Key, Entry> entries = new HashMap<>();
private final HashMap<Entry, HashMap<String, String>> children = new HashMap<>();
private Selector(Iterable<IModFile> mods) {
for (var mod : mods)
this.force(new ModEntry(mod));
}
@Override
public void force(Entry entry) {
super.force(entry);
entries.put(new Key(entry, ""), entry);
}
@Override
public void option(Entry entry, ContainedJarMetadata meta) {
super.option(entry, meta);
entries.put(new Key(entry, ""), entry);
}
@Override
public void add(Entry entry) {
super.add(entry);
entries.put(new Key(entry, ""), entry);
}
@Override
@Nullable
protected InputStream getResource(Entry source, String path) {
try {
Path target = source.getResource(path);
if (target == null || !Files.exists(target)) {
LOGGER.debug(MARKER, "Failed to load resource {} from {}, it does not contain dependency information.", path, source);
return null;
}
// Read the metadata file and store identifiers so we can group them later.
if (JarSelector.CONTAINED_JARS_METADATA_PATH.equals(path)) {
try (var is = Files.newInputStream(target)) {
var meta = MetadataIOHandler.fromStream(is).orElse(null);
if (meta == null)
return Files.newInputStream(target);
var children = new HashMap<String, String>();
for (var child : meta.jars())
children.put(child.path().replace('\\', '/'), child.identifier().group() + ':' + child.identifier().artifact());
this.children.put(source, children);
}
}
return Files.newInputStream(target);
} catch (Exception e) {
LOGGER.error(MARKER, "Failed to load resource {} from {}, cause {}", path, source, e);
return null;
}
}
@Override
protected Entry getNested(Entry source, String path) {
var cleaned = path.replace('\\', '/');
var key = new Key(source, cleaned);
var entry = entries.get(key);
if (entry == null) {
entry = createNested(source, path, cleaned);
if (entry == null)
entry = FAILED;
entries.put(key, entry);
}
return entry == FAILED ? null : entry;
}
private Entry createNested(Entry source, String path, String cleaned) {
var targetPath = source.getResource(path);
if (targetPath == null || !Files.isRegularFile(targetPath))
return null;
Path roimfsPath = null;
Path zipPath = null;
try {
// Lets copy the uncompressed file to memory, and then create a ReadOnlyInMemorryFileSystem for it, this allows us to reference it by URI.
// Which is needed because a lot of things require navigation via URI.
// And ZipFileSystem only caches the FileSystem instance when accessed via URI.
byte[] data = Files.readAllBytes(targetPath);
String filename = targetPath.getFileName().toString();
URI uri = new URI(ROIMFS, "jar-jar-" + nextId() + "/" + filename, null);
var roimfs = FileSystems.newFileSystem(uri, Collections.singletonMap("data", data));
roimfsPath = roimfs.getPath(filename);
var zip = FileSystems.newFileSystem(new URI("jar:" + roimfsPath.toUri()), Collections.emptyMap());
zipPath = zip.getRootDirectories().iterator().next(); // Get first root, which on ZipFileSystem is the only root
} catch (IOException | URISyntaxException e) {
LOGGER.error(MARKER, "Failed to create FileSystem for nested jar {}", source, e);
return null;
}
var mf = new Manifest();
var mfPath = zipPath.resolve(JarFile.MANIFEST_NAME);
if (Files.exists(mfPath)) {
try (var is = Files.newInputStream(mfPath)) {
mf = new Manifest(is);
} catch (IOException e) {
LOGGER.error(MARKER, "Failed to read manifest from {}", source, e);
}
}
IModFile.Type type = null;
String id = null;
var tomlPath = zipPath.resolve(MODS_TOML);
if (Files.exists(tomlPath)) {
var cfg = FileConfig.builder(tomlPath).build();
cfg.load();
cfg.close();
if (cfg.getOrElse("mods", null) instanceof Collection collection) {
@SuppressWarnings("unchecked")
var mods = (Collection<UnmodifiableConfig>)collection;
if (!mods.isEmpty()) {
var first = mods.iterator().next();
var modId = first.getOrElse("modId", null);
if (modId instanceof String str)
id = str;
}
}
var value = mf.getMainAttributes().getValue(ModFile.TYPE);
if (value != null)
type = IModFile.Type.valueOf(value);
else
type = IModFile.Type.MOD;
} else {
var value = mf.getMainAttributes().getValue(ModFile.TYPE);
if (value != null)
type = IModFile.Type.valueOf(value);
else {
// No mods.toml, no entry value, so decide based on the container's type
if (source.type == IModFile.Type.LIBRARY || source.type == IModFile.Type.LANGPROVIDER)
type = IModFile.Type.LIBRARY;
else
type = IModFile.Type.GAMELIBRARY;
}
}
if (id == null) {
var modulePath = zipPath.resolve(MODULE_INFO);
if (Files.exists(modulePath)) {
try (var is = Files.newInputStream(modulePath)) {
var module = ModuleDescriptor.read(is);
id = module.name();
} catch (IOException e) {
LOGGER.error(MARKER, "Failed to read module-info.class from {}", source, e);
}
}
}
if (id == null)
id = mf.getMainAttributes().getValue("Automatic-Module-Name");
if (id == null)
id = Path.of(path).getFileName().toString();
String coords = null;
var child = this.children.get(source);
if (child != null)
coords = child.get(cleaned);
return new Nested(id, roimfsPath, type, coords, source, cleaned, zipPath);
}
@Override
protected String getIdentifier(Entry entry) {
return entry.id;
}
@Override
protected Throwable getFailureException(Collection<ResolutionFailureInformation<Entry>> failures) {
var errors = new ArrayList<ExceptionData>();
for (var failure : failures) {
var message = failure.failureReason() == JarSelector.FailureReason.VERSION_RESOLUTION_FAILED ? "fml.dependencyloading.conflictingdependencies" : "fml.dependencyloading.mismatchedcontaineddependencies";
var joiner = new StringJoiner(", ");
for (var source : failure.sources()) {
String paths = null;
if (source.sources().size() == 0) {
paths = "[No Sources]";
} else if (source.sources().size() == 1) {
var entry = source.sources().iterator().next();
paths = entry.toString();
} else {
var j = new StringJoiner(", ", "[", "]");
for (var entry : source.sources())
j.add(entry.toString());
paths = j.toString();
}
var error = YELLOW + paths + RESET + " - " +
RED + source.requestedVersionRange() + RESET + " - " +
GREEN + source.includedVersion() + RESET;
joiner.add(error);
}
errors.add(new ExceptionData(message, failure.identifier().group() + ":" + failure.identifier().artifact(), joiner.toString()));
}
return new EarlyLoadingException(failures.size() + " Dependency restrictions were not met.", null, errors);
}
}
}
@@ -5,7 +5,6 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.google.common.collect.ImmutableList;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.Launcher;
import cpw.mods.modlauncher.api.IModuleLayerManager;
@@ -35,41 +34,36 @@ import java.util.ServiceLoader;
import java.util.stream.Collectors;
@ApiStatus.Internal
public class ModDiscoverer {
private static final Logger LOGGER = LogUtils.getLogger();
private final ServiceLoader<IModLocator> modLocators;
private final ServiceLoader<IDependencyLocator> dependencyLocators;
private final List<IModLocator> modLocatorList;
private final List<IDependencyLocator> dependencyLocatorList;
public final class ModDiscoverer {
private ModDiscoverer() {}
public ModDiscoverer(Map<String, ?> arguments) {
private static final Logger LOGGER = LogUtils.getLogger();
@SuppressWarnings("removal")
public static ModValidator discoverMods(Map<String, ?> arguments) {
Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.MODDIRECTORYFACTORY.get(), v->ModsFolderLocator::new);
Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.PROGRESSMESSAGE.get(), v-> StartupNotificationManager.locatorConsumer().orElseGet(()-> s->{}));
final var moduleLayerManager = Launcher.INSTANCE.environment().findModuleLayerManager().orElseThrow();
modLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IModLocator.class);
dependencyLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IDependencyLocator.class);
modLocatorList = ServiceLoaderUtils.streamServiceLoader(()-> modLocators, sce->LOGGER.error("Failed to load mod locator list", sce)).collect(Collectors.toList());
ServiceLoader<IModLocator> modLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IModLocator.class);
ServiceLoader<IDependencyLocator> dependencyLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IDependencyLocator.class);
List<IModLocator> modLocatorList = ServiceLoaderUtils.streamWithErrorHandling(modLocators, sce -> LOGGER.error("Failed to load mod locator list", sce)).toList();
for (IModLocator iModLocator : modLocatorList) {
iModLocator.initArguments(arguments);
}
dependencyLocatorList = ServiceLoaderUtils.streamServiceLoader(()-> dependencyLocators, sce->LOGGER.error("Failed to load dependency locator list", sce)).collect(Collectors.toList());
List<IDependencyLocator> dependencyLocatorList = ServiceLoaderUtils.streamWithErrorHandling(dependencyLocators, sce -> LOGGER.error("Failed to load dependency locator list", sce)).toList();
for (IDependencyLocator l : dependencyLocatorList) {
l.initArguments(arguments);
}
if (LOGGER.isDebugEnabled(LogMarkers.CORE))
{
if (LOGGER.isDebugEnabled(LogMarkers.CORE)) {
LOGGER.debug(LogMarkers.CORE, "Found Mod Locators : {}", modLocatorList.stream()
.map(modLocator -> "(%s:%s)".formatted(modLocator.name(),
modLocator.getClass().getPackage().getImplementationVersion())).collect(Collectors.joining(",")));
.map(modLocator -> "(%s:%s)".formatted(modLocator.name(),
modLocator.getClass().getPackage().getImplementationVersion())).collect(Collectors.joining(",")));
LOGGER.debug(LogMarkers.CORE, "Found Dependency Locators : {}", dependencyLocatorList.stream()
.map(dependencyLocator -> "(%s:%s)".formatted(dependencyLocator.name(),
dependencyLocator.getClass().getPackage().getImplementationVersion())).collect(Collectors.joining(",")));
.map(dependencyLocator -> "(%s:%s)".formatted(dependencyLocator.name(),
dependencyLocator.getClass().getPackage().getImplementationVersion())).collect(Collectors.joining(",")));
}
}
@SuppressWarnings("removal")
public ModValidator discoverMods() {
LOGGER.debug(LogMarkers.SCAN,"Scanning for mods and other resources to load. We know {} ways to find mods", modLocatorList.size());
List<ModFile> loadedFiles = new ArrayList<>();
List<EarlyLoadingException.ExceptionData> discoveryErrorData = new ArrayList<>();
@@ -117,7 +111,7 @@ public class ModDiscoverer {
}
LOGGER.debug(LogMarkers.SCAN, "Locator {} found {} valid mod files", locator, locatedFiles.size());
handleLocatedFiles(loadedFiles, locatedFiles);
handleLocatedFiles(loadedFiles, locatedFiles, locator);
} catch (InvalidModFileException imfe) {
// We don't generally expect this exception, since it should come from the candidates stream above and be handled in the Locator, but just in case.
LOGGER.error(LogMarkers.SCAN, "Locator {} found an invalid mod file {}", locator, imfe.getBrokenFile(), imfe);
@@ -131,8 +125,7 @@ public class ModDiscoverer {
//First processing run of the mod list. Any duplicates will cause resolution failure and dependency loading will be skipped.
Map<IModFile.Type, List<ModFile>> modFilesMap = new EnumMap<>(IModFile.Type.class);
try {
final UniqueModListBuilder modsUniqueListBuilder = new UniqueModListBuilder(loadedFiles);
final UniqueModListBuilder.UniqueModListData uniqueModsData = modsUniqueListBuilder.buildUniqueList();
final UniqueModListBuilder.UniqueModListData uniqueModsData = UniqueModListBuilder.buildUniqueList(loadedFiles);
//Grab the temporary results.
//This allows loading to continue to a base state, in case dependency loading fails.
@@ -152,14 +145,10 @@ public class ModDiscoverer {
for (IDependencyLocator locator : dependencyLocatorList) {
try {
LOGGER.debug(LogMarkers.SCAN,"Trying locator {}", locator);
final List<IModFile> locatedMods = ImmutableList.copyOf(loadedFiles);
final List<IModFile> locatedMods = List.copyOf(loadedFiles);
var locatedFiles = locator.scanMods(locatedMods);
if (locatedFiles.stream().anyMatch(file -> !(file instanceof ModFile))) {
LOGGER.error(LogMarkers.SCAN, "A dependency locator returned a file which is not a ModFile instance!. They will be skipped!");
}
handleLocatedFiles(loadedFiles, locatedFiles);
handleLocatedFiles(loadedFiles, locatedFiles, locator);
}
catch (EarlyLoadingException exception) {
LOGGER.error(LogMarkers.SCAN, "Failed to load dependencies with locator {}", locator, exception);
@@ -169,8 +158,7 @@ public class ModDiscoverer {
//Second processing run of the mod list. Any duplicates will cause resolution failure and only the mods list will be loaded.
try {
final UniqueModListBuilder modsAndDependenciesUniqueListBuilder = new UniqueModListBuilder(loadedFiles);
final UniqueModListBuilder.UniqueModListData uniqueModsAndDependenciesData = modsAndDependenciesUniqueListBuilder.buildUniqueList();
final UniqueModListBuilder.UniqueModListData uniqueModsAndDependenciesData = UniqueModListBuilder.buildUniqueList(loadedFiles);
//We now only need the mod files map, not the list.
modFilesMap = uniqueModsAndDependenciesData.modFiles().stream()
@@ -193,11 +181,14 @@ public class ModDiscoverer {
return validator;
}
private static void handleLocatedFiles(final List<ModFile> loadedFiles, final List<IModFile> locatedFiles) {
var locatedModFiles = locatedFiles.stream().filter(ModFile.class::isInstance).map(ModFile.class::cast).toList();
for (IModFile mf : locatedModFiles) {
LOGGER.info(LogMarkers.SCAN, "Found mod file {} of type {} with provider {}", mf.getFileName(), mf.getType(), mf.getProvider());
private static void handleLocatedFiles(final List<ModFile> loadedFiles, final List<IModFile> locatedFiles, final Object locator) {
for (IModFile mf : locatedFiles) {
if (mf instanceof ModFile modFile) {
LOGGER.info(LogMarkers.SCAN, "Found mod file {} of type {} with provider {}", mf.getFileName(), mf.getType(), mf.getProvider());
loadedFiles.add(modFile);
} else {
LOGGER.error(LogMarkers.SCAN, "Skipping mod file {} found by {}, as it was not a ModFile instance!", mf.getFileName(), locator);
}
}
loadedFiles.addAll(locatedModFiles);
}
}
@@ -21,13 +21,13 @@ import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.slf4j.Logger;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@@ -98,7 +98,7 @@ public class ModFile implements IModFile {
public boolean identifyMods() {
if (this.modFileInfo == null) return this.getType() != Type.MOD;
LOGGER.debug(LogMarkers.LOADING,"Loading mod file {} with languages {}", this.getFilePath(), this.modFileInfo.requiredLanguageLoaders());
LOGGER.debug(LogMarkers.LOADING,"Loading mod file {} with languages {}", this.getFileName(), this.modFileInfo.requiredLanguageLoaders());
this.coreMods = ModFileParser.getCoreMods(this);
if (!this.coreMods.isEmpty())
LOGGER.warn("Mod file {} contains javascript coremods! JS CoreMods are deprecated and will be removed in a future release. Consider using Mixin or ModLauncher transformers.", this.getFileName());
@@ -179,12 +179,28 @@ public class ModFile implements IModFile {
@Override
public String toString() {
return "Mod File: " + Objects.toString(this.jar.getPrimaryPath());
var path = getFilePath();
return "Mod File: " + path == null ? "null" : path.toUri().toString();
}
@Override
public String getFileName() {
return getFilePath().getFileName().toString();
// This is only used for debugging, so lets try and make it a useful debug
var path = getFilePath();
var filename = path.getFileName();
// ZipFileSystem toString will return the outer file path
var ret = filename == null ? path.getFileSystem().toString() : filename.toString();
// If it's a directory on the default file system, return the full path
if (Files.isDirectory(path) && path.getFileSystem() == FileSystems.getDefault())
ret = path.toAbsolutePath().toString();
// If we are in a FileSystem 'root' like UnionFS, return the full uri
if (ret.isEmpty() || ret.equals("/"))
ret = path.toUri().toString();
return ret;
}
@Override
@@ -264,7 +264,7 @@ public class ModFileInfo implements IModFileInfo, IConfigurable {
@Override
public String versionString() {
return getMods().get(0).getVersion().toString();
return getMods().getFirst().getVersion().toString();
}
@Override
@@ -34,7 +34,6 @@ public class ModValidator {
private LoadingModList loadingModList;
private final List<IModFile> brokenFiles = new ArrayList<>();
private final List<EarlyLoadingException.ExceptionData> discoveryErrorData;
private final List<ModFileLoadingException> modFileLoadingExceptions;
public ModValidator(Map<IModFile.Type, List<ModFile>> modFiles, List<IModFileInfo> brokenFiles, List<EarlyLoadingException.ExceptionData> discoveryErrorData, List<ModFileLoadingException> modFileLoadingExceptions) {
this.candidateMods = lst(modFiles.get(IModFile.Type.MOD));
@@ -44,7 +43,6 @@ public class ModValidator {
this.candidatePlugins.addAll(lst(modFiles.get(IModFile.Type.LIBRARY)));
this.discoveryErrorData = discoveryErrorData;
this.brokenFiles.addAll(brokenFiles.stream().map(IModFileInfo::getFile).toList());
this.modFileLoadingExceptions = modFileLoadingExceptions;
}
public ModValidator(Map<IModFile.Type, List<ModFile>> modFiles, List<IModFileInfo> brokenFiles, List<EarlyLoadingException.ExceptionData> discoveryErrorData) {
@@ -52,7 +50,9 @@ public class ModValidator {
}
private static List<ModFile> lst(List<ModFile> files) {
return files == null ? new ArrayList<>() : new ArrayList<>(files);
if (files == null) return new ArrayList<>();
if (files instanceof ArrayList) return files;
return new ArrayList<>(files);
}
public void stage1Validation() {
@@ -60,7 +60,7 @@ public class ModValidator {
if (LOGGER.isDebugEnabled(LogMarkers.SCAN)) {
LOGGER.debug(LogMarkers.SCAN, "Found {} mod files with {} mods", candidateMods.size(), candidateMods.stream().mapToInt(mf -> mf.getModInfos().size()).sum());
}
ImmediateWindowHandler.updateProgress("Found "+candidateMods.size()+" mod candidates");
ImmediateWindowHandler.updateProgress("Found " + candidateMods.size() + " mod candidates");
}
@NotNull
@@ -0,0 +1,35 @@
{
"options": [
{
"resource": "com/llamalad7/mixinextras/platform/forge/MixinExtrasConfigPlugin.class",
"layer": "GAMELIBRARY",
"id": "mixinextras",
"deps": [
{
"identifier": {
"group": "com.github.LlamaLad7",
"artifact": "MixinExtras"
},
"version": {
"range": "[0.5.0,)",
"artifactVersion": "0.5.0"
},
"path": "",
"isObfuscated": false
}
],
"meta": {
"identifier": {
"group": "io.github.llamalad7",
"artifact": "mixinextras-forge"
},
"version": {
"artifactVersion": "0.5.0"
},
"path": "",
"isObfuscated": false
},
"nested": true
}
]
}
@@ -10,7 +10,6 @@ import cpw.mods.modlauncher.api.ITransformationService;
import cpw.mods.modlauncher.api.ITransformer;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
+3 -3
View File
@@ -5,11 +5,11 @@ org.gradle.daemon=false
org.gradle.parallel=true
JAVA_VERSION=21
MC_VERSION=1.21.9
MC_VERSION=1.21.10
MC_NEXT_VERSION=1.22
MCP_VERSION=20250930.103108
MCP_VERSION=20251007.101210
MAPPING_CHANNEL=official
MAPPING_VERSION=1.21.9
MAPPING_VERSION=1.21.10
# Set to true before the first build of a new MC version, so we don't do compatibility checks
CHECK_COMPATIBILITY=false
@@ -48,11 +48,13 @@ import java.util.stream.Collectors;
* annotations and passes the class instances to the {@link Bus}
* defined by the annotation. Defaults to {@code MinecraftForge#EVENT_BUS}
*/
public class AutomaticEventSubscriber {
public final class AutomaticEventSubscriber {
private static final Logger LOGGER = LogManager.getLogger();
private static final Type AUTO_SUBSCRIBER = Type.getType(EventBusSubscriber.class);
private static final Type MOD_TYPE = Type.getType(Mod.class);
private static final Type ONLY_IN_TYPE = Type.getType(OnlyIn.class);
private static final List<EnumData> DEFAULT_SIDES = List.of(new EnumData(null, "CLIENT"), new EnumData(null, "DEDICATED_SERVER"));
private static final EnumData DEFAULT_BUS = new EnumData(null, "BOTH");
public static void inject(ModContainer mod, ModFileScanData scanData, ClassLoader loader) {
if (scanData == null) return;
@@ -71,9 +73,6 @@ public class AutomaticEventSubscriber {
.filter(data -> MOD_TYPE.equals(data.annotationType()))
.collect(Collectors.toMap(a -> a.clazz().getClassName(), a -> (String)a.annotationData().get("value")));
var defaultSides = List.of(new EnumData(null, "CLIENT"), new EnumData(null, "DEDICATED_SERVER"));
var defaultBus = new EnumData(null, "FORGE");
for (var data : targets) {
if (!FMLEnvironment.production && onlyIns.contains(data.clazz().getClassName())) {
throw new RuntimeException("Found @OnlyIn on @EventBusSubscriber class " + data.clazz().getClassName() + " - this is not allowed as it causes crashes. Remove the OnlyIn and set value=Dist.CLIENT in the EventBusSubscriber annotation instead");
@@ -82,13 +81,13 @@ public class AutomaticEventSubscriber {
var modId = modids.getOrDefault(data.clazz().getClassName(), mod.getModId());
modId = value(data, "modid", modId);
var sidesValue = value(data, "value", defaultSides);
var sidesValue = value(data, "value", DEFAULT_SIDES);
var sides = sidesValue.stream()
.map(EnumData::value)
.map(Dist::valueOf)
.collect(Collectors.toSet());
var busName = value(data, "bus", defaultBus).value();
var busName = value(data, "bus", DEFAULT_BUS).value();
var busTarget = Bus.valueOf(busName);
if (Objects.equals(mod.getModId(), modId) && sides.contains(FMLEnvironment.dist)) {
try {
@@ -173,7 +172,7 @@ public class AutomaticEventSubscriber {
Class<? extends Event> eventType = (Class<? extends Event>) parameterTypes[0];
var subscribeEventAnnotation = method.getAnnotation(SubscribeEvent.class);
registerListener(busGroup, paramCount, returnType, eventType, subscribeEventAnnotation, method);
registerListener(busGroup, paramCount, returnType, eventType, subscribeEventAnnotation, method, false);
listenersCount++;
if (firstValidListenerEventType == null)
@@ -259,7 +258,7 @@ public class AutomaticEventSubscriber {
throw fail(method, "Return type boolean is only valid for cancellable events");
}
registerListener(busGroup, paramCount, returnType, eventType, subscribeEventAnnotation, method);
registerListener(busGroup, paramCount, returnType, eventType, subscribeEventAnnotation, method, true);
listenersCount++;
if (firstValidListenerEventType == null)
@@ -276,11 +275,24 @@ public class AutomaticEventSubscriber {
@SuppressWarnings({"unchecked", "rawtypes"})
private static EventListener registerListener(@Nullable BusGroup busGroup,
int paramCount, Class<?> returnType, Class<? extends Event> eventType,
SubscribeEvent subscribeEventAnnotation, Method method) {
SubscribeEvent subscribeEventAnnotation, Method method, boolean strict) {
if (busGroup == null) {
busGroup = IModBusEvent.class.isAssignableFrom(eventType)
? FMLJavaModLoadingContext.get().getModBusGroup()
: BusGroup.DEFAULT;
} else if (strict) {
String solution = "To fix this, remove the bus param from your @EventBusSubscriber annotation or move this event listener to another class.";
var isDefaultBusGroup = busGroup == BusGroup.DEFAULT;
var isModBusEvent = IModBusEvent.class.isAssignableFrom(eventType);
if (isDefaultBusGroup && isModBusEvent) { // requested forge bus and has IModBusEvent
throw fail(method, "Event type " + eventType.getName()
+ " is on the mod BusGroup but you are asking to register it on the default BusGroup (BusGroup.DEFAULT/EventBusSubscriber.Bus.FORGE). "
+ solution);
} else if (!isDefaultBusGroup && !isModBusEvent) { // requested mod bus and does not have IModBusEvent
throw fail(method, "Event type " + eventType.getName()
+ " is on the default BusGroup but you are asking to register it on the mod BusGroup (context.getModBusGroup()/EventBusSubscriber.Bus.MOD). "
+ solution);
}
}
// determine the listener type from its parameters and return type
@@ -43,7 +43,7 @@ public class FMLModContainer extends ModContainer {
LOGGER.debug(LOADING,"Creating FMLModContainer instance for {}", className);
this.scanResults = modFileScanResults;
activityMap.put(ModLoadingStage.CONSTRUCT, this::constructMod);
this.eventBusGroup = BusGroup.create("modBusFor" + info.getModId());
this.eventBusGroup = BusGroup.create("modBusFor" + info.getModId(), IModBusEvent.class);
this.contextExtension = () -> context;
try {
var moduleName = info.getOwningFile().moduleName();
@@ -193,6 +193,6 @@ public class FMLModContainer extends ModContainer {
@Override
public String toString() {
return "FMLModContainer[" + this.getModInfo().getModId() + ", " + this.getClass().getName() + "]";
return "FMLModContainer[" + this.getModInfo().getModId() + ", " + this.getClass().getName() + ']';
}
}
+2 -2
View File
@@ -2,7 +2,7 @@ plugins {
id 'eclipse'
id 'idea'
id 'maven-publish'
id 'net.minecraftforge.gradle' version '[6.0.36,6.2)'
id 'net.minecraftforge.gradle' version '[6.0.46,6.2)'
}
version = mod_version
@@ -145,7 +145,7 @@ dependencies {
// Forge 1.21.6+ uses EventBus 7, which shifts most of its runtime validation to compile-time via an annotation processor
// to improve performance in production environments. This line is required to enable said compile-time validation
// in your development environment, helping you catch issues early.
annotationProcessor 'net.minecraftforge:eventbus-validator:7.0-beta.10'
annotationProcessor 'net.minecraftforge:eventbus-validator:7.0-beta.12'
// Example mod dependency with JEI
// The JEI API is declared for compile time use, while the full JEI artifact is used at runtime
@@ -4,7 +4,7 @@
this.activeButton = null;
}
+ if (net.minecraftforge.client.event.ForgeEventFactoryClient.onMouseButtonPre(mousebuttoninfo.button(), p_423948_, mousebuttoninfo.modifiers())) return;
+ if (net.minecraftforge.client.event.ForgeEventFactoryClient.onMouseButtonPre(mousebuttoninfo, p_423948_)) return;
if (this.minecraft.getOverlay() == null) {
if (this.minecraft.screen == null) {
if (!this.mouseGrabbed && flag) {
@@ -30,7 +30,7 @@
KeyMapping.click(inputconstants$key);
}
}
+ net.minecraftforge.client.event.ForgeEventFactoryClient.onMouseButtonPost(mousebuttoninfo.button(), p_423948_, mousebuttoninfo.modifiers());
+ net.minecraftforge.client.event.ForgeEventFactoryClient.onMouseButtonPost(mousebuttoninfo, p_423948_);
}
}
@@ -82,11 +82,31 @@
}
public void setComponentTooltipForNextFrame(Font p_408650_, List<Component> p_407512_, int p_406019_, int p_408123_, @Nullable ResourceLocation p_409993_) {
@@ -1069,11 +_,13 @@
ClientTooltipPositioner p_409451_,
@Nullable ResourceLocation p_409298_
@@ -1056,9 +_,14 @@
) {
+ var preEvent = net.minecraftforge.client.ForgeHooksClient.onRenderTooltipPre(this.tooltipStack, this, p_283678_, p_281696_, guiWidth(), guiHeight(), p_282716_, p_283128_, p_409451_);
if (!p_409928_.isEmpty()) {
if (this.deferredTooltip == null || p_410618_) {
- this.deferredTooltip = () -> this.renderTooltip(p_406605_, p_409928_, p_407794_, p_408784_, p_408798_, p_407676_);
+ final var stackContext = this.tooltipStack;
+ this.deferredTooltip = () -> this.renderTooltip(p_406605_, p_409928_, p_407794_, p_408784_, p_408798_, p_407676_, stackContext);
}
}
+ }
+
+ public void renderTooltip(Font p_283128_, List<ClientTooltipComponent> p_282716_, int p_283678_, int p_281696_, ClientTooltipPositioner p_409451_, @Nullable ResourceLocation p_409298_) {
+ renderTooltip(p_283128_, p_282716_, p_283678_, p_281696_, p_409451_, p_409298_, ItemStack.EMPTY);
}
public void renderTooltip(
@@ -1067,13 +_,16 @@
int p_283678_,
int p_281696_,
ClientTooltipPositioner p_409451_,
- @Nullable ResourceLocation p_409298_
+ @Nullable ResourceLocation p_409298_,
+ ItemStack itemstack
) {
+ var preEvent = net.minecraftforge.client.ForgeHooksClient.onRenderTooltipPre(itemstack, this, p_283678_, p_281696_, guiWidth(), guiHeight(), p_282716_, p_283128_, p_409451_, p_409298_);
+ if (preEvent == null) return;
int i = 0;
int j = p_282716_.size() == 1 ? -2 : 0;
@@ -97,7 +117,7 @@
if (k > i) {
i = k;
}
@@ -1083,16 +_,17 @@
@@ -1083,16 +_,16 @@
int l1 = i;
int i2 = j;
@@ -107,8 +127,7 @@
int i1 = vector2ic.y();
this.pose.pushMatrix();
- TooltipRenderUtil.renderTooltipBackground(this, l, i1, i, j, p_409298_);
+ var background_event = net.minecraftforge.client.event.ForgeEventFactoryClient.onRenderTooltipBackground(this.tooltipStack, this, l, i1, preEvent.getFont(), p_282716_, p_409298_);
+ TooltipRenderUtil.renderTooltipBackground(this, l, i1, i, j, background_event.getBackground());
+ TooltipRenderUtil.renderTooltipBackground(this, l, i1, i, j, preEvent.getBackground());
int j1 = i1;
for (int k1 = 0; k1 < p_282716_.size(); k1++) {
@@ -1,14 +1,5 @@
--- a/net/minecraft/client/particle/ParticleEngine.java
+++ b/net/minecraft/client/particle/ParticleEngine.java
@@ -26,7 +_,7 @@
public class ParticleEngine {
private static final List<ParticleRenderType> RENDER_ORDER = List.of(ParticleRenderType.SINGLE_QUADS, ParticleRenderType.ITEM_PICKUP, ParticleRenderType.ELDER_GUARDIANS);
protected ClientLevel level;
- private final Map<ParticleRenderType, ParticleGroup<?>> particles = Maps.newIdentityHashMap();
+ private final Map<ParticleRenderType, ParticleGroup<?>> particles = Maps.newTreeMap(net.minecraftforge.client.ForgeHooksClient.makeParticleRenderTypeComparator(RENDER_ORDER));
private final Queue<TrackingEmitter> trackingEmitters = Queues.newArrayDeque();
private final Queue<Particle> particlesToAdd = Queues.newArrayDeque();
private final Object2IntOpenHashMap<ParticleLimit> trackedParticleCounts = new Object2IntOpenHashMap<>();
@@ -63,7 +_,7 @@
private <T extends ParticleOptions> Particle makeParticle(
T p_107396_, double p_107397_, double p_107398_, double p_107399_, double p_107400_, double p_107401_, double p_107402_
@@ -18,12 +9,34 @@
return particleprovider == null
? null
: particleprovider.createParticle(p_107396_, this.level, p_107397_, p_107398_, p_107399_, p_107400_, p_107401_, p_107402_, this.random);
@@ -123,7 +_,7 @@
@@ -113,6 +_,8 @@
return new ItemPickupParticleGroup(this);
} else if (p_428647_ == ParticleRenderType.ELDER_GUARDIANS) {
return new ElderGuardianParticleGroup(this);
+ } else if (factories.containsKey(p_428647_)) {
+ return factories.get(p_428647_).apply(this);
} else {
return (ParticleGroup<?>)(p_428647_ == ParticleRenderType.NO_RENDER ? new NoRenderParticleGroup(this) : new QuadParticleGroup(this, p_428647_));
}
@@ -122,8 +_,20 @@
this.trackedParticleCounts.addTo(p_423291_, p_172283_);
}
+
+ private static final Map<ParticleRenderType, java.util.function.Function<ParticleEngine, ParticleGroup<?>>> factories = new java.util.HashMap<>();
+ private static final List<ParticleRenderType> particleRenderOrder = new java.util.ArrayList<>(RENDER_ORDER);
+
+ // Forge: allow custom particle groups
+ public static void registerParticleGroup(ParticleRenderType type, java.util.function.Function<ParticleEngine, ParticleGroup<?>> factory) {
+ if (factories.putIfAbsent(type, factory) != null) {
+ throw new IllegalArgumentException(type.name() + " already has a factory registered. Previous factory was " + factories.get(type) + ". This factory was " + factory);
+ }
+ particleRenderOrder.add(type);
+ }
+
public void extract(ParticlesRenderState p_423938_, Frustum p_424803_, Camera p_430521_, float p_426823_) {
- for (ParticleRenderType particlerendertype : RENDER_ORDER) {
+ for (ParticleRenderType particlerendertype : this.particles.keySet()) { // Forge: allow custom ParticleRenderType's
+ for (ParticleRenderType particlerendertype : particleRenderOrder) { // Forge: allow custom ParticleRenderType's
ParticleGroup<?> particlegroup = this.particles.get(particlerendertype);
if (particlegroup != null && !particlegroup.isEmpty()) {
p_423938_.add(particlegroup.extractRenderState(p_424803_, p_430521_, p_426823_));
@@ -25,7 +25,7 @@
}
this.addLateDebugPass(framegraphbuilder, this.levelRenderState.cameraRenderState.pos, p_407881_, frustum);
+ net.minecraftforge.client.FramePassManager.insertForgePasses(framegraphbuilder, targets); // Forge: Modded passes are inserted here.
+ net.minecraftforge.client.FramePassManager.insertForgePasses(framegraphbuilder, targets, this.levelRenderState); // Forge: Modded passes are inserted here.
profilerfiller.popPush("executeFrameGraph");
framegraphbuilder.execute(p_367325_, new FrameGraphBuilder.Inspector() {
@Override
@@ -30,7 +30,7 @@
+ }
+
+ static {
+ if (net.minecraftforge.fml.ModLoader.isLoadingStateValid() && !net.minecraftforge.fml.ModLoader.get().hasCompletedState("LOAD_REGISTRIES")) {
+ if (net.minecraftforge.fml.ModLoader.isLoadingStateValid() && !net.minecraftforge.fml.ModLoader.get().hasCompletedState(net.minecraftforge.common.ForgeStatesProvider.LOAD_REGISTRIES)) {
+ com.mojang.logging.LogUtils.getLogger().error(
+ "net.minecraft.client.renderer.Sheets loaded too early, modded registry-based materials may not work correctly",
+ new IllegalStateException("net.minecraft.client.renderer.Sheets loaded too early")
@@ -4,7 +4,7 @@
this.soundManager = p_120236_;
this.options = p_120237_;
this.soundBuffers = new SoundBufferLibrary(p_249332_);
+ net.minecraftforge.fml.ModLoader.postEvent(new net.minecraftforge.client.event.sound.SoundEngineLoadEvent(this));
+ net.minecraftforge.client.event.sound.SoundEngineLoadEvent.BUS.post(new net.minecraftforge.client.event.sound.SoundEngineLoadEvent(this));
}
public void reload() {
@@ -12,7 +12,7 @@
this.destroy();
this.loadLibrary();
+ net.minecraftforge.fml.ModLoader.postEvent(new net.minecraftforge.client.event.sound.SoundEngineLoadEvent(this));
+ net.minecraftforge.client.event.sound.SoundEngineLoadEvent.BUS.post(new net.minecraftforge.client.event.sound.SoundEngineLoadEvent(this));
}
private synchronized void loadLibrary() {
@@ -53,7 +53,7 @@
while (this.running) {
long i;
@@ -775,6 +_,8 @@
@@ -778,6 +_,8 @@
this.isReady = true;
JvmProfiler.INSTANCE.onServerTick(this.smoothedTickTimeMillis);
}
@@ -62,7 +62,7 @@
} catch (Throwable throwable2) {
LOGGER.error("Encountered an unexpected exception", throwable2);
CrashReport crashreport = constructOrExtractCrashReport(throwable2);
@@ -786,6 +_,7 @@
@@ -789,6 +_,7 @@
LOGGER.error("We were unable to save this crash report to disk.");
}
@@ -70,7 +70,7 @@
this.onServerCrash(crashreport);
} finally {
try {
@@ -794,6 +_,7 @@
@@ -797,6 +_,7 @@
} catch (Throwable throwable) {
LOGGER.error("Exception stopping the server", throwable);
} finally {
@@ -78,7 +78,7 @@
this.onServerExit();
}
}
@@ -991,12 +_,14 @@
@@ -994,12 +_,14 @@
}
}
@@ -93,7 +93,7 @@
}
this.ticksUntilAutosave--;
@@ -1014,6 +_,7 @@
@@ -1017,6 +_,7 @@
this.smoothedTickTimeMillis = this.smoothedTickTimeMillis * 0.8F + (float)k / (float)TimeUtil.NANOSECONDS_PER_MILLISECOND * 0.19999999F;
this.logTickMethodTime(i);
profilerfiller.pop();
@@ -101,7 +101,7 @@
}
private void autoSave() {
@@ -1063,7 +_,8 @@
@@ -1066,7 +_,8 @@
Optional.of(serverstatus$players),
Optional.of(ServerStatus.Version.current()),
Optional.ofNullable(this.statusIcon),
@@ -111,7 +111,7 @@
);
}
@@ -1095,7 +_,8 @@
@@ -1098,7 +_,8 @@
profilerfiller.popPush("levels");
this.updateEffectiveRespawnData();
@@ -121,7 +121,7 @@
profilerfiller.push(() -> serverlevel + " " + serverlevel.dimension().location());
if (this.tickCount % 20 == 0) {
profilerfiller.push("timeSync");
@@ -1104,6 +_,7 @@
@@ -1107,6 +_,7 @@
}
profilerfiller.push("tick");
@@ -129,7 +129,7 @@
try {
serverlevel.tick(p_129954_);
@@ -1112,9 +_,11 @@
@@ -1115,9 +_,11 @@
serverlevel.fillReportDetails(crashreport);
throw new ReportedException(crashreport);
}
@@ -141,7 +141,7 @@
}
profilerfiller.popPush("connection");
@@ -1123,7 +_,7 @@
@@ -1126,7 +_,7 @@
this.playerList.tick();
profilerfiller.popPush("debugSubscribers");
this.debugSubscribers.tick();
@@ -150,7 +150,7 @@
profilerfiller.popPush("gameTests");
GameTestTicker.SINGLETON.tick();
}
@@ -1226,7 +_,7 @@
@@ -1229,7 +_,7 @@
@DontObfuscate
public String getServerModName() {
@@ -159,7 +159,7 @@
}
public SystemReport fillSystemReport(SystemReport p_177936_) {
@@ -1576,6 +_,7 @@
@@ -1579,6 +_,7 @@
this.functionManager.replaceLibrary(this.resources.managers.getFunctionLibrary());
this.structureTemplateManager.onResourceManagerReload(this.resources.resourceManager);
this.fuelValues = FuelValues.vanillaBurnTimes(this.registries.compositeAccess(), this.worldData.enabledFeatures());
@@ -167,7 +167,7 @@
}, this);
if (this.isSameThread()) {
this.managedBlock(completablefuture::isDone);
@@ -1585,12 +_,15 @@
@@ -1588,12 +_,15 @@
}
public static WorldDataConfiguration configurePackRepository(PackRepository p_248681_, WorldDataConfiguration p_331931_, boolean p_249869_, boolean p_330480_) {
@@ -184,7 +184,7 @@
} else {
Set<String> set = Sets.newLinkedHashSet();
@@ -2155,6 +_,48 @@
@@ -2158,6 +_,48 @@
public ServerLinks serverLinks() {
return ServerLinks.EMPTY;
@@ -45,9 +45,9 @@
}
}
@@ -510,6 +_,13 @@
if (this.gui == null) {
this.gui = MinecraftServerGui.showFrameFor(this);
@@ -439,6 +_,13 @@
LOGGER.error("Interrupted while stopping the management server", (Throwable)interruptedexception);
}
}
+
+ if (this.dediLanPinger != null) {
@@ -6,7 +6,7 @@
public static PackRepository createPackRepository(Path p_251569_, DirectoryValidator p_300268_) {
- return new PackRepository(new ServerPacksSource(p_300268_), new FolderRepositorySource(p_251569_, PackType.SERVER_DATA, PackSource.WORLD, p_300268_));
+ var ret = new PackRepository(new ServerPacksSource(p_300268_), new FolderRepositorySource(p_251569_, PackType.SERVER_DATA, PackSource.WORLD, p_300268_));
+ net.minecraftforge.fml.ModLoader.postEvent(new net.minecraftforge.event.AddPackFindersEvent(PackType.SERVER_DATA, ret::addPackFinder));
+ net.minecraftforge.event.AddPackFindersEvent.BUS.post(new net.minecraftforge.event.AddPackFindersEvent(PackType.SERVER_DATA, ret::addPackFinder));
+ return ret;
}
@@ -62,7 +62,7 @@
}
this.checkBelowWorld();
@@ -955,9 +_,7 @@
@@ -958,9 +_,7 @@
return blockpos;
} else {
BlockState blockstate = this.level().getBlockState(blockpos);
@@ -73,7 +73,7 @@
? blockpos.atY(Mth.floor(this.position.y - p_216987_))
: blockpos;
}
@@ -1299,19 +_,19 @@
@@ -1304,19 +_,19 @@
return !blockstate.is(BlockTags.INSIDE_STEP_SOUND_BLOCKS) && !blockstate.is(BlockTags.COMBINATION_STEP_SOUND_BLOCKS) ? p_278049_ : blockpos;
}
@@ -99,7 +99,7 @@
this.playSound(soundtype.getStepSound(), soundtype.getVolume() * 0.15F, soundtype.getPitch());
}
@@ -1444,6 +_,10 @@
@@ -1449,6 +_,10 @@
return this.wasTouchingWater;
}
@@ -110,7 +110,7 @@
boolean isInRain() {
BlockPos blockpos = this.blockPosition();
return this.level().isRainingAt(blockpos)
@@ -1483,24 +_,32 @@
@@ -1488,24 +_,32 @@
public void updateSwimming() {
if (this.isSwimming()) {
@@ -150,7 +150,7 @@
if (!this.wasTouchingWater && !this.firstTick) {
this.doWaterSplashEffect();
}
@@ -1515,6 +_,7 @@
@@ -1520,6 +_,7 @@
private void updateFluidOnEyes() {
this.wasEyeInWater = this.isEyeInFluid(FluidTags.WATER);
this.fluidOnEyes.clear();
@@ -158,7 +158,7 @@
double d0 = this.getEyeY();
if (!(
this.getVehicle() instanceof AbstractBoat abstractboat
@@ -1526,7 +_,7 @@
@@ -1531,7 +_,7 @@
FluidState fluidstate = this.level().getFluidState(blockpos);
double d1 = blockpos.getY() + fluidstate.getHeight(this.level(), blockpos);
if (d1 > d0) {
@@ -167,7 +167,7 @@
}
}
}
@@ -1580,12 +_,13 @@
@@ -1585,12 +_,13 @@
}
public boolean canSpawnSprintParticle() {
@@ -182,7 +182,7 @@
if (blockstate.getRenderShape() != RenderShape.INVISIBLE) {
Vec3 vec3 = this.getDeltaMovement();
BlockPos blockpos1 = this.blockPosition();
@@ -1601,7 +_,7 @@
@@ -1606,7 +_,7 @@
this.level()
.addParticle(
@@ -191,7 +191,7 @@
d0,
this.getY() + 0.1,
d1,
@@ -1612,12 +_,15 @@
@@ -1617,12 +_,15 @@
}
}
@@ -208,7 +208,7 @@
}
public void moveRelative(float p_19921_, Vec3 p_19922_) {
@@ -1982,6 +_,10 @@
@@ -1987,6 +_,10 @@
p_409187_.putBoolean("HasVisualFire", this.hasVisualFire);
}
@@ -219,7 +219,7 @@
if (!this.tags.isEmpty()) {
p_409187_.store("Tags", TAG_LIST_CODEC, List.copyOf(this.tags));
}
@@ -2057,6 +_,9 @@
@@ -2062,6 +_,9 @@
this.setGlowingTag(p_408698_.getBooleanOr("Glowing", false));
this.setTicksFrozen(p_408698_.getIntOr("TicksFrozen", 0));
this.hasVisualFire = p_408698_.getBooleanOr("HasVisualFire", false);
@@ -229,7 +229,7 @@
this.customData = p_408698_.read("data", CustomData.CODEC).orElse(CustomData.EMPTY);
this.tags.clear();
p_408698_.read("Tags", TAG_LIST_CODEC).ifPresent(this.tags::addAll);
@@ -2109,6 +_,8 @@
@@ -2114,6 +_,8 @@
p_409579_, this.getX() + p_408228_.x, this.getY() + p_408228_.y, this.getZ() + p_408228_.z, p_407826_
);
itementity.setDefaultPickUpDelay();
@@ -238,7 +238,12 @@
p_409579_.addFreshEntity(itementity);
return itementity;
}
@@ -2172,7 +_,7 @@
@@ -2173,11 +_,11 @@
}
ItemStack itemstack = p_19978_.getItemInHand(p_19979_);
- if (itemstack.is(Items.SHEARS) && this.shearOffAllLeashConnections(p_19978_)) {
+ if (itemstack.canPerformAction(net.minecraftforge.common.ToolActions.SHEARS_HARVEST) && this.shearOffAllLeashConnections(p_19978_)) {
itemstack.hurtAndBreak(1, p_19978_, p_19979_);
return InteractionResult.SUCCESS;
} else if (this instanceof Mob mob
@@ -247,7 +252,7 @@
&& mob.canShearEquipment(p_19978_)
&& !p_19978_.isSecondaryUseActive()
&& this.attemptToShearEquipment(p_19978_, p_19979_, itemstack, mob)) {
@@ -2277,6 +_,7 @@
@@ -2282,6 +_,7 @@
public void rideTick() {
this.setDeltaMovement(Vec3.ZERO);
@@ -255,7 +260,7 @@
this.tick();
if (this.isPassenger()) {
this.getVehicle().positionRider(this);
@@ -2337,6 +_,7 @@
@@ -2342,6 +_,7 @@
}
}
@@ -263,7 +268,7 @@
if (p_19967_ || this.canRide(p_19966_) && p_19966_.canAddPassenger(this)) {
if (this.isPassenger()) {
this.stopRiding();
@@ -2372,6 +_,7 @@
@@ -2377,6 +_,7 @@
public void removeVehicle() {
if (this.vehicle != null) {
Entity entity = this.vehicle;
@@ -271,7 +276,7 @@
this.vehicle = null;
entity.removePassenger(this);
Entity.RemovalReason entity$removalreason = this.getRemovalReason();
@@ -2422,6 +_,8 @@
@@ -2427,6 +_,8 @@
return this.passengers.isEmpty();
}
@@ -280,7 +285,7 @@
protected boolean couldAcceptPassenger() {
return true;
}
@@ -2618,7 +_,7 @@
@@ -2623,7 +_,7 @@
}
public boolean isVisuallyCrawling() {
@@ -289,7 +294,7 @@
}
public void setSwimming(boolean p_20283_) {
@@ -2735,7 +_,7 @@
@@ -2740,7 +_,7 @@
this.igniteForSeconds(8.0F);
}
@@ -298,7 +303,7 @@
}
public void onAboveBubbleColumn(boolean p_392709_, BlockPos p_391902_) {
@@ -2868,7 +_,7 @@
@@ -2873,7 +_,7 @@
}
protected Component getTypeName() {
@@ -307,7 +312,7 @@
}
public boolean is(Entity p_20356_) {
@@ -3181,6 +_,7 @@
@@ -3186,6 +_,7 @@
return this.stringUUID;
}
@@ -315,7 +320,7 @@
public boolean isPushedByFluid() {
return true;
}
@@ -3565,9 +_,17 @@
@@ -3570,9 +_,17 @@
return Mth.lerp(p_344421_, this.yRotO, this.yRot);
}
@@ -334,7 +339,7 @@
} else {
AABB aabb = this.getBoundingBox().deflate(0.001);
int i = Mth.floor(aabb.minX);
@@ -3582,25 +_,28 @@
@@ -3587,25 +_,28 @@
Vec3 vec3 = Vec3.ZERO;
int k1 = 0;
BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos();
@@ -370,7 +375,7 @@
}
}
}
@@ -3608,27 +_,28 @@
@@ -3613,27 +_,28 @@
}
}
@@ -409,7 +414,7 @@
}
}
@@ -3641,7 +_,10 @@
@@ -3646,7 +_,10 @@
return !this.level().hasChunksAt(i, k, j, l);
}
@@ -420,7 +425,7 @@
return this.fluidHeight.getDouble(p_204037_);
}
@@ -3657,7 +_,9 @@
@@ -3662,7 +_,9 @@
return this.dimensions.height();
}
@@ -430,7 +435,7 @@
return new ClientboundAddEntityPacket(this, p_344981_);
}
@@ -3788,6 +_,11 @@
@@ -3793,6 +_,11 @@
}
}
}
@@ -442,7 +447,7 @@
}
public void checkDespawn() {
@@ -3962,6 +_,100 @@
@@ -3967,6 +_,100 @@
float f1 = (float)Mth.lerp(d0, this.getXRot(), p_298926_);
this.setPos(d1, d2, d3);
this.setRot(f, f1);
@@ -28,7 +28,7 @@
+ public static void fireSpawnPlacementEvent() {
+ Map<EntityType<?>, net.minecraftforge.event.entity.SpawnPlacementRegisterEvent.MergedSpawnPredicate<?>> map = Maps.newHashMap();
+ DATA_BY_TYPE.forEach((type, data) -> map.put(type, new net.minecraftforge.event.entity.SpawnPlacementRegisterEvent.MergedSpawnPredicate<>(data.predicate, data.placement, data.heightMap)));
+ net.minecraftforge.fml.ModLoader.postEvent(new net.minecraftforge.event.entity.SpawnPlacementRegisterEvent(map));
+ net.minecraftforge.event.entity.SpawnPlacementRegisterEvent.BUS.post(new net.minecraftforge.event.entity.SpawnPlacementRegisterEvent(map));
+ map.forEach(((entityType, merged) -> DATA_BY_TYPE.put(entityType, new SpawnPlacements.Data(merged.getHeightmapType(), merged.getSpawnType(), merged.build()))));
}
}
@@ -0,0 +1,30 @@
--- a/net/minecraft/world/entity/animal/coppergolem/CopperGolem.java
+++ b/net/minecraft/world/entity/animal/coppergolem/CopperGolem.java
@@ -216,7 +_,7 @@
}
Level level = this.level();
- if (itemstack.is(Items.SHEARS) && this.readyForShearing()) {
+ if (false && itemstack.is(Items.SHEARS) && this.readyForShearing()) { // Forge: Moved to onSheared
if (level instanceof ServerLevel serverlevel) {
this.shear(serverlevel, SoundSource.PLAYERS, itemstack);
this.gameEvent(GameEvent.SHEAR, p_430585_);
@@ -429,6 +_,18 @@
ItemStack itemstack = this.getItemBySlot(EQUIPMENT_SLOT_ANTENNA);
this.setItemSlot(EQUIPMENT_SLOT_ANTENNA, ItemStack.EMPTY);
this.spawnAtLocation(p_428037_, itemstack, 1.5F);
+ }
+
+ @Override
+ public java.util.List<ItemStack> onSheared(@Nullable Player player, ItemStack item, Level world, net.minecraft.core.BlockPos pos, int fortune) {
+ if (world instanceof ServerLevel server) {
+ server.playSound(null, this, SoundEvents.COPPER_GOLEM_SHEAR, player == null ? SoundSource.BLOCKS : SoundSource.PLAYERS, 1.0F, 1.0F);
+ var ret = new java.util.ArrayList<ItemStack>();
+ ret.add(this.getItemBySlot(EQUIPMENT_SLOT_ANTENNA));
+ this.setItemSlot(EQUIPMENT_SLOT_ANTENNA, ItemStack.EMPTY);
+ return ret;
+ }
+ return java.util.Collections.emptyList();
}
@Override
@@ -0,0 +1,11 @@
--- a/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java
+++ b/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java
@@ -72,7 +_,7 @@
if (this.level().isClientSide()) {
return InteractionResult.SUCCESS;
} else {
- if (p_31842_.getItemInHand(p_31843_).is(Items.SHEARS)) {
+ if (p_31842_.getItemInHand(p_31843_).canPerformAction(net.minecraftforge.common.ToolActions.SHEARS_HARVEST)) {
InteractionResult interactionresult = super.interact(p_31842_, p_31843_);
if (interactionresult instanceof InteractionResult.Success interactionresult$success && interactionresult$success.wasItemInteraction()) {
return interactionresult;
@@ -35,7 +35,7 @@
@@ -151,7 +_,7 @@
@Override
protected void entityInside(BlockState p_52277_, Level p_52278_, BlockPos p_52279_, Entity p_52280_, InsideBlockEffectApplier p_391246_) {
protected void entityInside(BlockState p_52277_, Level p_52278_, BlockPos p_52279_, Entity p_52280_, InsideBlockEffectApplier p_391246_, boolean p_432051_) {
- if (p_52278_ instanceof ServerLevel serverlevel && p_52280_ instanceof Ravager && serverlevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+ if (p_52278_ instanceof ServerLevel serverlevel && p_52280_ instanceof Ravager && net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(serverlevel, p_52280_)) {
serverlevel.destroyBlock(p_52279_, true, p_52280_);
+8 -4
View File
@@ -27,16 +27,20 @@ dependencyResolutionManagement {
library('forgespi', 'net.minecraftforge:forgespi:7.1.5') // Needs modlauncher
library('modlauncher', 'net.minecraftforge:modlauncher:10.2.4') // Needs securemodules
library('securemodules', 'net.minecraftforge:securemodules:2.2.21') // Needs unsafe
library('securemodules', 'net.minecraftforge:securemodules:2.2.23') // Needs unsafe
library('unsafe', 'net.minecraftforge:unsafe:0.9.2')
library('accesstransformers', 'net.minecraftforge:accesstransformers:8.2.2')
library('coremods', 'net.minecraftforge:coremods:5.2.6')
library('nashorn', 'org.openjdk.nashorn:nashorn-core:15.4') // Needed by coremods, because the JRE no longer ships JS
library('eventbus', 'net.minecraftforge:eventbus:7.0-beta.11')
library('eventbus-validator', 'net.minecraftforge:eventbus-validator:7.0-beta.11')
library('eventbus', 'net.minecraftforge:eventbus:7.0-beta.12')
library('eventbus-validator', 'net.minecraftforge:eventbus-validator:7.0-beta.12')
library('mergetool-api', 'net.minecraftforge:mergetool-api:1.0')
library('roimfs', 'net.minecraftforge:roimfs:1.0.0') // ReadOnlyInMemoryFileSystem - Used for JarInJar extraction at runtime
library('mixin', 'org.spongepowered:mixin:0.8.7')
version('mixinextras', '0.5.0')
library('mixinextras-forge', 'io.github.llamalad7', 'mixinextras-forge').versionRef('mixinextras')
library('mixinextras-common', 'io.github.llamalad7', 'mixinextras-common').versionRef('mixinextras')
library('jetbrains-annotations', 'org.jetbrains:annotations:24.1.0') // for ApiStatus annotations
library('jspecify', 'org.jspecify:jspecify:1.0.0') // for nullability annotations
library('slf4j-api', 'org.slf4j:slf4j-api:2.0.7')
@@ -76,7 +80,7 @@ dependencyResolutionManagement {
bundle('night-config', ['night-config-toml', 'night-config-core'])
// Jar in Jar FileSystem
version('jarjar', '0.3.26')
version('jarjar', '0.4.2')
library('jarjar-fs', 'net.minecraftforge', 'JarJarFileSystems').versionRef('jarjar')
library('jarjar-meta', 'net.minecraftforge', 'JarJarMetadata' ).versionRef('jarjar')
library('jarjar-selector', 'net.minecraftforge', 'JarJarSelector' ).versionRef('jarjar')
@@ -2,6 +2,7 @@
"values": [
"#c:nuggets/gold",
"#c:nuggets/iron",
"#c:nuggets/copper",
{
"id": "#forge:nuggets",
"required": false
@@ -0,0 +1,5 @@
{
"values": [
"minecraft:copper_nugget"
]
}
@@ -7,6 +7,7 @@ package net.minecraftforge.client;
import net.minecraft.client.renderer.chunk.ChunkSectionLayer;
import net.minecraft.client.resources.model.UnbakedGeometry;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.ModelEvent;
import net.minecraftforge.client.event.RegisterClientReloadListenersEvent;
@@ -17,14 +18,14 @@ import net.minecraftforge.client.model.obj.ObjLoader;
import net.minecraftforge.eventbus.api.listener.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@Mod.EventBusSubscriber(value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD, modid = "forge")
@Mod.EventBusSubscriber(value = Dist.CLIENT, modid = "forge")
public class ClientForgeMod {
@SubscribeEvent
public static void onRegisterGeometryLoaders(ModelEvent.RegisterGeometryLoaders event) {
event.register("empty", (json, ctx) -> UnbakedGeometry.EMPTY);
event.register("obj", ObjLoader.INSTANCE);
event.register("fluid_container", DynamicFluidContainerModel.Loader.INSTANCE);
event.register("item_layers", ItemLayerGeometry.Loader.INSTANCE);
event.register(forgeRL("empty"), (json, ctx) -> UnbakedGeometry.EMPTY);
event.register(forgeRL("obj"), ObjLoader.INSTANCE);
event.register(forgeRL("fluid_container"), DynamicFluidContainerModel.Loader.INSTANCE);
event.register(forgeRL("item_layers"), ItemLayerGeometry.Loader.INSTANCE);
}
@SubscribeEvent
@@ -34,6 +35,10 @@ public class ClientForgeMod {
@SubscribeEvent
public static void onRegisterNamedRenderTypes(RegisterNamedRenderTypesEvent event) {
event.register("item_unlit", ChunkSectionLayer.TRANSLUCENT, ForgeRenderTypes.ITEM_UNSORTED_UNLIT_TRANSLUCENT.get());
event.register(forgeRL("item_unlit"), ChunkSectionLayer.TRANSLUCENT, ForgeRenderTypes.ITEM_UNSORTED_UNLIT_TRANSLUCENT.get());
}
private static ResourceLocation forgeRL(String path) {
return ResourceLocation.fromNamespaceAndPath("forge", path);
}
}
@@ -9,7 +9,6 @@ import net.minecraft.client.color.block.BlockTintCache;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.world.level.ColorResolver;
import net.minecraftforge.client.event.RegisterColorHandlersEvent;
import net.minecraftforge.fml.ModLoader;
import org.jetbrains.annotations.ApiStatus;
import java.util.ArrayList;
@@ -27,7 +26,7 @@ public final class ColorResolverManager {
@ApiStatus.Internal
public static void init() {
var builder = new ArrayList<ColorResolver>();
ModLoader.postEvent(new RegisterColorHandlersEvent.ColorResolvers(builder));
RegisterColorHandlersEvent.ColorResolvers.BUS.post(new RegisterColorHandlersEvent.ColorResolvers(builder));
colorResolvers = List.copyOf(builder);
}
@@ -9,7 +9,6 @@ import net.minecraft.client.renderer.DimensionSpecialEffects;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.dimension.BuiltinDimensionTypes;
import net.minecraftforge.client.event.RegisterDimensionSpecialEffectsEvent;
import net.minecraftforge.fml.ModLoader;
import org.jetbrains.annotations.ApiStatus;
import java.util.HashMap;
@@ -38,7 +37,7 @@ public final class DimensionSpecialEffectsManager {
var effects = new HashMap<ResourceLocation, DimensionSpecialEffects>();
DEFAULT_EFFECTS = preRegisterVanillaEffects(effects);
var event = new RegisterDimensionSpecialEffectsEvent(effects);
ModLoader.postEventWrapContainerInModOrder(event);
RegisterDimensionSpecialEffectsEvent.BUS.post(event);
EFFECTS = Map.copyOf(effects);
}
@@ -8,9 +8,9 @@ package net.minecraftforge.client;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.EntityType;
import net.minecraftforge.client.event.RegisterEntitySpectatorShadersEvent;
import net.minecraftforge.fml.ModLoader;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
@@ -20,24 +20,24 @@ import java.util.Map;
* <p>
* Provides a lookup.
*/
@NullMarked
public final class EntitySpectatorShaderManager {
private EntitySpectatorShaderManager() {}
private static Map<EntityType<?>, ResourceLocation> SHADERS;
private static @Nullable Map<EntityType<?>, ResourceLocation> SHADERS;
/**
* Finds the path to the spectator mode shader used for the specified entity type, or null if none is registered.
*/
@Nullable
public static ResourceLocation get(EntityType<?> entityType) {
public static @Nullable ResourceLocation get(EntityType<?> entityType) {
assert SHADERS != null;
return SHADERS.get(entityType);
}
@ApiStatus.Internal
public static void init() {
var shaders = new HashMap<EntityType<?>, ResourceLocation>();
var event = new RegisterEntitySpectatorShadersEvent(shaders);
ModLoader.postEventWrapContainerInModOrder(event);
RegisterEntitySpectatorShadersEvent.BUS.post(new RegisterEntitySpectatorShadersEvent(shaders));
SHADERS = Map.copyOf(shaders);
}
}
@@ -269,11 +269,11 @@ public class ForgeHooksClient {
}
public static void onTextureStitchedPost(TextureAtlas map) {
ModLoader.postEvent(new TextureStitchEvent.Post(map));
TextureStitchEvent.Post.BUS.post(new TextureStitchEvent.Post(map));
}
public static void onBlockColorsInit(BlockColors blockColors) {
ModLoader.postEvent(new RegisterColorHandlersEvent.Block(blockColors));
RegisterColorHandlersEvent.Block.BUS.post(new RegisterColorHandlersEvent.Block(blockColors));
}
public static Model getArmorModel(HumanoidRenderState state, ItemStack itemStack, EquipmentSlot slot, HumanoidModel<?> _default) {
@@ -359,11 +359,11 @@ public class ForgeHooksClient {
}
public static void onModifyBakingResult(ModelBakery modelBakery, ModelBakery.BakingResult results) {
ModLoader.postEvent(new ModelEvent.ModifyBakingResult(modelBakery, results));
ModelEvent.ModifyBakingResult.BUS.post(new ModelEvent.ModifyBakingResult(modelBakery, results));
}
public static void onModelBake(ModelManager modelManager, ModelBakery modelBakery) {
ModLoader.postEvent(new ModelEvent.BakingCompleted(modelManager, modelBakery));
ModelEvent.BakingCompleted.BUS.post(new ModelEvent.BakingCompleted(modelManager, modelBakery));
}
public static TextureAtlasSprite[] getFluidSprites(BlockAndTintGetter level, BlockPos pos, FluidState fluidStateIn) {
@@ -428,25 +428,25 @@ public class ForgeHooksClient {
}
public static boolean onScreenKeyPressed(Screen screen, KeyEvent info) {
return ForgeEventFactoryClient.onScreenKeyPressedPre(screen, info.key(), info.scancode(), info.modifiers())
return ForgeEventFactoryClient.onScreenKeyPressedPre(screen, info)
|| screen.keyPressed(info)
|| ForgeEventFactoryClient.onScreenKeyPressedPost(screen, info.key(), info.scancode(), info.modifiers());
|| ForgeEventFactoryClient.onScreenKeyPressedPost(screen, info);
}
public static boolean onScreenKeyReleased(Screen screen, KeyEvent info) {
return ForgeEventFactoryClient.onScreenKeyReleasedPre(screen, info.key(), info.scancode(), info.modifiers())
return ForgeEventFactoryClient.onScreenKeyReleasedPre(screen, info)
|| screen.keyReleased(info)
|| ForgeEventFactoryClient.onScreenKeyReleasedPost(screen, info.key(), info.scancode(), info.modifiers());
|| ForgeEventFactoryClient.onScreenKeyReleasedPost(screen, info);
}
public static boolean onScreenCharTyped(Screen screen, CharacterEvent info) {
return ForgeEventFactoryClient.onScreenCharTypedPre(screen, (char)info.codepoint(), info.modifiers())
return ForgeEventFactoryClient.onScreenCharTypedPre(screen, info)
|| screen.charTyped(info)
|| ForgeEventFactoryClient.onScreenCharTypedPost(screen, (char)info.codepoint(), info.modifiers());
|| ForgeEventFactoryClient.onScreenCharTypedPost(screen, info);
}
public static void onKeyInput(KeyEvent info, int action) {
InputEvent.Key.BUS.post(new InputEvent.Key(info.key(), info.scancode(), action, info.modifiers()));
InputEvent.Key.BUS.post(new InputEvent.Key(info, action));
}
public static boolean isNameplateInRenderDistance(Entity entity, double squareDistance) {
@@ -615,11 +615,11 @@ public class ForgeHooksClient {
}
public static void onRegisterParticleProviders(ParticleResources particles) {
ModLoader.postEvent(new RegisterParticleProvidersEvent(particles));
RegisterParticleProvidersEvent.BUS.post(new RegisterParticleProvidersEvent(particles));
}
public static void onRegisterKeyMappings(Options options) {
ModLoader.postEvent(new RegisterKeyMappingsEvent(options));
RegisterKeyMappingsEvent.BUS.post(new RegisterKeyMappingsEvent(options));
}
public static void onRegisterPictureInPictureRenderers(List<PictureInPictureRenderer<?>> renderers,
@@ -658,8 +658,8 @@ public class ForgeHooksClient {
return stackFont == null ? fallbackFont : stackFont;
}
public static @Nullable RenderTooltipEvent.Pre onRenderTooltipPre(@NotNull ItemStack stack, GuiGraphics graphics, int x, int y, int screenWidth, int screenHeight, @NotNull List<ClientTooltipComponent> components, @NotNull Font fallbackFont, @NotNull ClientTooltipPositioner positioner) {
var preEvent = new RenderTooltipEvent.Pre(stack, graphics, x, y, screenWidth, screenHeight, getTooltipFont(stack, fallbackFont), components, positioner);
public static @Nullable RenderTooltipEvent.Pre onRenderTooltipPre(@NotNull ItemStack stack, GuiGraphics graphics, int x, int y, int screenWidth, int screenHeight, @NotNull List<ClientTooltipComponent> components, @NotNull Font fallbackFont, @NotNull ClientTooltipPositioner positioner, @Nullable ResourceLocation background) {
var preEvent = new RenderTooltipEvent.Pre(stack, graphics, x, y, screenWidth, screenHeight, getTooltipFont(stack, fallbackFont), components, positioner, background);
return RenderTooltipEvent.Pre.BUS.post(preEvent) ? null : preEvent;
}
@@ -729,20 +729,6 @@ public class ForgeHooksClient {
return font.split(text, maxWidth).stream().map(ClientTooltipComponent::create);
}
public static Comparator<ParticleRenderType> makeParticleRenderTypeComparator(List<ParticleRenderType> renderOrder) {
Comparator<ParticleRenderType> vanillaComparator = Comparator.comparingInt(renderOrder::indexOf);
return (typeOne, typeTwo) -> {
boolean vanillaOne = renderOrder.contains(typeOne);
boolean vanillaTwo = renderOrder.contains(typeTwo);
if (vanillaOne && vanillaTwo)
return vanillaComparator.compare(typeOne, typeTwo);
if (!vanillaOne && !vanillaTwo)
return Integer.compare(System.identityHashCode(typeOne), System.identityHashCode(typeTwo));
return vanillaOne ? -1 : 1;
};
}
public static boolean isBlockInSolidLayer(BlockState state) {
var model = Minecraft.getInstance().getBlockRenderer().getBlockModel(state);
return model.getRenderTypes(state, RandomSource.create(), ModelData.EMPTY).contains(ChunkSectionLayer.SOLID);
@@ -799,9 +785,9 @@ public class ForgeHooksClient {
throw new IllegalStateException("Client hooks initialized more than once");
initializedClientHooks = true;
ModLoader.postEvent(new RegisterClientReloadListenersEvent(resourceManager));
ModLoader.postEvent(new EntityRenderersEvent.RegisterLayerDefinitions());
ModLoader.postEvent(new EntityRenderersEvent.RegisterRenderers());
RegisterClientReloadListenersEvent.BUS.post(new RegisterClientReloadListenersEvent(resourceManager));
EntityRenderersEvent.RegisterLayerDefinitions.BUS.post(new EntityRenderersEvent.RegisterLayerDefinitions());
EntityRenderersEvent.RegisterRenderers.BUS.post(new EntityRenderersEvent.RegisterRenderers());
TextureAtlasSpriteLoaderManager.init();
ClientTooltipComponentManager.init();
EntitySpectatorShaderManager.init();
@@ -7,9 +7,12 @@ package net.minecraftforge.client;
import com.mojang.blaze3d.framegraph.FrameGraphBuilder;
import com.mojang.blaze3d.framegraph.FramePass;
import com.mojang.blaze3d.resource.ResourceHandle;
import net.minecraft.client.renderer.LevelTargetBundle;
import net.minecraft.client.renderer.state.LevelRenderState;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
@@ -27,18 +30,43 @@ public class FramePassManager {
// Note: Pass order is determined automatically within FrameGraphBuilder. It's unclear what must be done to guarantee ordering.
@ApiStatus.Internal
public static void insertForgePasses(FrameGraphBuilder graphBuilder, LevelTargetBundle bundle) {
public static void insertForgePasses(FrameGraphBuilder graphBuilder, LevelTargetBundle bundle, LevelRenderState state) {
for (PassInfo info : addedPasses) {
FramePass pass = graphBuilder.addPass(info.name);
PassDefinition forgePass = info.pass;
forgePass.targets(bundle, pass);
pass.executes(forgePass::executes);
forgePass.extracts(bundle, pass);
pass.executes(() -> forgePass.executes(state));
}
}
public interface PassDefinition {
void targets(LevelTargetBundle bundle, FramePass pass);
void executes();
/**
* Use to define which targets your pass will bind against, see {@link FramePass#reads} and {@link FramePass#readsAndWrites}
* A FramePass must bind to at least ONE target. Otherwise, you get freaky issues with >1 modded passes.
* Additionally, this method should be used for extracting render states into instance variables if desired.
*/
default void extracts(LevelTargetBundle bundle, FramePass pass) {
targets(bundle, pass);
}
/**
* Prefer {@link PassDefinition#extracts}
*/
@Deprecated(forRemoval = true, since = "1.21.10")
default void targets(LevelTargetBundle bundle, FramePass pass) {}
/**
* Use to define what your pass does during the render stage.
*/
default void executes(LevelRenderState state) {
executes();
};
/**
* Use to define what your pass does during the render stage, prefer {@link PassDefinition#executes(LevelRenderState)}.
*/
@Deprecated(forRemoval = true, since = "1.21.10")
default void executes(){};
}
private record PassInfo(String name, PassDefinition pass){}
@@ -18,7 +18,6 @@ import net.minecraft.client.gui.Font;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.client.event.RegisterItemDecorationsEvent;
import net.minecraftforge.fml.ModLoader;
@ApiStatus.Internal
public final class ItemDecoratorHandler {
@@ -37,8 +36,7 @@ public final class ItemDecoratorHandler {
public static void init() {
var decorators = new HashMap<Item, List<IItemDecorator>>();
var event = new RegisterItemDecorationsEvent(decorators);
ModLoader.postEventWrapContainerInModOrder(event);
RegisterItemDecorationsEvent.BUS.post(new RegisterItemDecorationsEvent(decorators));
var builder = new ImmutableMap.Builder<Item, ItemDecoratorHandler>();
for (var entry : decorators.entrySet()) {
Item item = entry.getKey();
@@ -5,12 +5,10 @@
package net.minecraftforge.client;
import com.google.common.collect.ImmutableMap;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.chunk.ChunkSectionLayer;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.event.RegisterNamedRenderTypesEvent;
import net.minecraftforge.fml.ModLoader;
import org.jetbrains.annotations.ApiStatus;
import java.util.HashMap;
@@ -22,7 +20,7 @@ import java.util.Map;
* Provides a lookup.
*/
public final class NamedRenderTypeManager {
private static ImmutableMap<ResourceLocation, RenderTypeGroup> RENDER_TYPES;
private static Map<ResourceLocation, RenderTypeGroup> RENDER_TYPES;
/**
* Finds the {@link RenderTypeGroup} for a given name, or the {@link RenderTypeGroup#EMPTY empty group} if not found.
@@ -35,9 +33,8 @@ public final class NamedRenderTypeManager {
public static void init() {
var renderTypes = new HashMap<ResourceLocation, RenderTypeGroup>();
preRegisterVanillaRenderTypes(renderTypes);
var event = new RegisterNamedRenderTypesEvent(renderTypes);
ModLoader.postEventWrapContainerInModOrder(event);
RENDER_TYPES = ImmutableMap.copyOf(renderTypes);
RegisterNamedRenderTypesEvent.BUS.post(new RegisterNamedRenderTypesEvent(renderTypes));
RENDER_TYPES = Map.copyOf(renderTypes);
}
/**
@@ -53,9 +50,9 @@ public final class NamedRenderTypeManager {
blockRenderTypes.put(rl("tripwire"), new RenderTypeGroup(ChunkSectionLayer.TRIPWIRE, ForgeRenderTypes.ITEM_LAYERED_TRANSLUCENT.get()));
}
private static ResourceLocation rl(String paht) {
return ResourceLocation.withDefaultNamespace(paht);
private static ResourceLocation rl(String path) {
return ResourceLocation.withDefaultNamespace(path);
}
private NamedRenderTypeManager() { }
private NamedRenderTypeManager() {}
}
@@ -29,9 +29,7 @@ public record AddGuiOverlayLayersEvent(ForgeLayeredDraw getLayeredDraw) implemen
@ApiStatus.Internal
public AddGuiOverlayLayersEvent {}
/**
* @deprecated {@link AddGuiOverlayLayersEvent} is no longer an {@link IModBusEvent}, so use {@link #BUS} directly.
*/
/** @deprecated {@link AddGuiOverlayLayersEvent} is no longer an {@link IModBusEvent}, so use {@link #BUS} directly. */
@Deprecated(forRemoval = true, since = "1.21.9")
public static EventBus<AddGuiOverlayLayersEvent> getBus(BusGroup modBusGroup) {
return BUS;
@@ -12,16 +12,19 @@ import net.minecraftforge.eventbus.api.event.MutableEvent;
import net.minecraftforge.eventbus.api.event.characteristic.Cancellable;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
/**
* Fired when the client is about to send a chat message to the server.
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* <p>This event is {@linkplain Cancellable cancellable}.
* If the event is cancelled, the chat message will not be sent to the server.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
**/
@NullMarked
public final class ClientChatEvent extends MutableEvent implements Cancellable {
public static final CancellableEventBus<ClientChatEvent> BUS = CancellableEventBus.create(ClientChatEvent.class);
@@ -47,7 +50,7 @@ public final class ClientChatEvent extends MutableEvent implements Cancellable {
*
* @param message the new message to be sent
*/
public void setMessage(String message) {
public void setMessage(@Nullable String message) {
this.message = Strings.nullToEmpty(message);
}
@@ -22,7 +22,7 @@ import java.util.UUID;
* Fired when a chat message is received on the client.
* This can be used for filtering and detecting messages with specific words or phrases, and suppressing them.
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* <p>This event is {@linkplain Cancellable cancellable}.
* If the event is cancelled, the message is not displayed in the chat message window.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
@@ -89,7 +89,7 @@ public sealed class ClientChatReceivedEvent extends MutableEvent implements Canc
/**
* Fired when a player chat message is received on the client.
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* <p>This event is {@linkplain Cancellable cancellable}.
* If the event is cancelled, the message is not displayed in the chat message window.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
@@ -120,8 +120,8 @@ public sealed class ClientChatReceivedEvent extends MutableEvent implements Canc
/**
* Fired when a system message is received on the client.
*
* @Deprecated Mojang made ChatType a registry, which isn't always accessible when the System messages are sent.
* So moved to it's own event. {@link SystemMessageReceivedEvent}
* @deprecated Mojang made ChatType a registry, which isn't always accessible when the System messages are sent.
* So moved to its own event. {@link SystemMessageReceivedEvent}
*/
@Deprecated(forRemoval = true, since = "1.21.1")
public static final class System extends ClientChatReceivedEvent {
@@ -12,6 +12,7 @@ import net.minecraftforge.eventbus.api.event.InheritableEvent;
import net.minecraftforge.eventbus.api.event.RecordEvent;
import net.minecraftforge.eventbus.api.event.characteristic.Cancellable;
import net.minecraftforge.fml.LogicalSide;
import org.jspecify.annotations.NullMarked;
/**
* Fired when pause is about to change
@@ -21,6 +22,7 @@ import net.minecraftforge.fml.LogicalSide;
* @see ClientPauseChangeEvent.Pre
* @see ClientPauseChangeEvent.Post
*/
@NullMarked
public sealed interface ClientPauseChangeEvent extends InheritableEvent {
EventBus<ClientPauseChangeEvent> BUS = EventBus.create(ClientPauseChangeEvent.class);
@@ -7,10 +7,8 @@ package net.minecraftforge.client.event;
import net.minecraft.client.multiplayer.PlayerInfo;
import net.minecraft.world.level.GameType;
import net.minecraftforge.common.util.HasResult;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.RecordEvent;
import net.minecraftforge.eventbus.api.event.characteristic.Cancellable;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
@@ -18,8 +16,6 @@ import org.jspecify.annotations.NullMarked;
/**
* Fired when the client player is notified of a change of {@link GameType} from the server.
*
* <p>This event is not {@linkplain Cancellable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @param getInfo the client player information
@@ -12,17 +12,17 @@ import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.MutableEvent;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* Fired after the field of vision (FOV) modifier for the player is calculated to allow developers to adjust it further.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see ViewportEvent.ComputeFov
*/
@NullMarked
public final class ComputeFovModifierEvent extends MutableEvent {
public static final EventBus<ComputeFovModifierEvent> BUS = EventBus.create(ComputeFovModifierEvent.class);
@@ -62,8 +62,6 @@ public sealed interface ContainerScreenEvent {
* <p>This can be used for rendering elements that must be above other screen elements, but
* below tooltips and the dragged stack, such as slot or item stack specific overlays.</p>
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
@@ -83,8 +81,6 @@ public sealed interface ContainerScreenEvent {
* Fired after the container screen's background layer and elements are drawn.
* This can be used for rendering new background elements.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
@@ -42,7 +42,7 @@ public sealed interface CustomizeGuiOverlayEvent {
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
final class BossEventProgress implements Cancellable, CustomizeGuiOverlayEvent, RecordEvent {
final class BossEventProgress extends MutableEvent implements Cancellable, CustomizeGuiOverlayEvent {
public static final CancellableEventBus<BossEventProgress> BUS = CancellableEventBus.create(BossEventProgress.class);
private final Window window;
@@ -35,12 +35,14 @@ import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraftforge.client.ForgeHooksClient;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.MutableEvent;
import net.minecraftforge.eventbus.api.event.RecordEvent;
import net.minecraftforge.eventbus.api.event.characteristic.SelfDestructing;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import java.util.Map;
import java.util.Set;
@@ -51,30 +53,31 @@ import java.util.function.Supplier;
* Fired for on different events/actions relating to {@linkplain EntityRenderer entity renderers}.
* See the various subclasses for listening to different events.
*
* <p>These events are fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
* <p>These events are fired only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see EntityRenderersEvent.RegisterLayerDefinitions
* @see EntityRenderersEvent.RegisterRenderers
* @see EntityRenderersEvent.AddLayers
* @see EntityRenderersEvent.CreateSkullModels
*/
public abstract sealed class EntityRenderersEvent implements IModBusEvent {
@ApiStatus.Internal
protected EntityRenderersEvent() {}
public sealed interface EntityRenderersEvent {
/**
* Fired for registering layer definitions at the appropriate time.
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
* <p>This event is fired on only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static final class RegisterLayerDefinitions extends EntityRenderersEvent implements SelfDestructing {
@NullMarked
record RegisterLayerDefinitions() implements SelfDestructing, RecordEvent, EntityRenderersEvent {
public static final EventBus<RegisterLayerDefinitions> BUS = EventBus.create(RegisterLayerDefinitions.class);
/** @deprecated {@link EntityRenderersEvent.RegisterLayerDefinitions} is no longer an {@link IModBusEvent}, so use {@link #BUS} directly. */
@Deprecated(forRemoval = true, since = "1.21.9")
public static EventBus<RegisterLayerDefinitions> getBus(BusGroup modBusGroup) {
return IModBusEvent.getBus(modBusGroup, RegisterLayerDefinitions.class);
return BUS;
}
@ApiStatus.Internal
public RegisterLayerDefinitions() {}
public RegisterLayerDefinitions {}
/**
* Registers a layer definition supplier with the given {@link ModelLayerLocation}.
@@ -95,16 +98,20 @@ public abstract sealed class EntityRenderersEvent implements IModBusEvent {
* For registering entity renderer layers to existing entity renderers (whether vanilla or registered through this
* event), listen for the {@link AddLayers} event instead.
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
* <p>This event is fired only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static final class RegisterRenderers extends EntityRenderersEvent implements SelfDestructing {
@NullMarked
record RegisterRenderers() implements SelfDestructing, RecordEvent, EntityRenderersEvent {
public static final EventBus<RegisterRenderers> BUS = EventBus.create(RegisterRenderers.class);
/** @deprecated {@link RegisterRenderers} is no longer an {@link IModBusEvent}, so use {@link #BUS} directly. */
@Deprecated(forRemoval = true, since = "1.21.9")
public static EventBus<RegisterRenderers> getBus(BusGroup modBusGroup) {
return IModBusEvent.getBus(modBusGroup, RegisterRenderers.class);
return BUS;
}
@ApiStatus.Internal
public RegisterRenderers() {}
public RegisterRenderers {}
/**
* Registers an entity renderer for the given entity type.
@@ -131,14 +138,15 @@ public abstract sealed class EntityRenderersEvent implements IModBusEvent {
* Fired for registering entity renderer layers at the appropriate time, after the entity and player renderers maps
* have been created.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
* <p>This event is fired only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static final class AddLayers extends EntityRenderersEvent {
final class AddLayers extends MutableEvent implements EntityRenderersEvent {
public static final EventBus<AddLayers> BUS = EventBus.create(AddLayers.class);
/** @deprecated {@link EntityRenderersEvent.AddLayers} is no longer an {@link IModBusEvent}, so use {@link #BUS} directly. */
@Deprecated(forRemoval = true, since = "1.21.9")
public static EventBus<AddLayers> getBus(BusGroup modBusGroup) {
return IModBusEvent.getBus(modBusGroup, AddLayers.class);
return BUS;
}
private final Map<EntityType<?>, EntityRenderer<?, ?>> renderers;
@@ -222,14 +230,15 @@ public abstract sealed class EntityRenderersEvent implements IModBusEvent {
/**
* Fired for registering additional {@linkplain net.minecraft.client.model.SkullModelBase skull models} at the appropriate time.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
* <p>This event is fired only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static final class CreateSkullModels extends EntityRenderersEvent {
final class CreateSkullModels extends MutableEvent implements EntityRenderersEvent {
public static final EventBus<CreateSkullModels> BUS = EventBus.create(CreateSkullModels.class);
/** @deprecated {@link EntityRenderersEvent.CreateSkullModels} is no longer an {@link IModBusEvent}, so use {@link #BUS} directly. */
@Deprecated(forRemoval = true, since = "1.21.9")
public static EventBus<CreateSkullModels> getBus(BusGroup modBusGroup) {
return IModBusEvent.getBus(modBusGroup, CreateSkullModels.class);
return BUS;
}
private final ImmutableMap.Builder<Type, Function<EntityModelSet, SkullModelBase>> builder;
@@ -33,7 +33,10 @@ import net.minecraft.client.gui.components.toasts.Toast;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent;
import net.minecraft.client.input.CharacterEvent;
import net.minecraft.client.input.KeyEvent;
import net.minecraft.client.input.MouseButtonEvent;
import net.minecraft.client.input.MouseButtonInfo;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.model.SkullModelBase;
import net.minecraft.client.model.geom.EntityModelSet;
@@ -76,8 +79,7 @@ public final class ForgeEventFactoryClient {
private ForgeEventFactoryClient() {}
public static void onGatherLayers(Map<EntityType<?>, EntityRenderer<?, ?>> renderers, Map<PlayerModelType, AvatarRenderer<AbstractClientPlayer>> playerRenderers, Map<PlayerModelType, AvatarRenderer<ClientMannequin>> mannequinRenderers, Context context) {
// TODO: Why is this a ModLoader event...
ModLoader.postEvent(new EntityRenderersEvent.AddLayers(renderers, playerRenderers, mannequinRenderers, context));
EntityRenderersEvent.AddLayers.BUS.post(new EntityRenderersEvent.AddLayers(renderers, playerRenderers, mannequinRenderers, context));
}
public static boolean onScreenMouseReleased(Screen screen, double mouseX, double mouseY, MouseButtonEvent event) {
@@ -90,20 +92,20 @@ public final class ForgeEventFactoryClient {
}
public static boolean onScreenMouseClicked(Screen screen, double mouseX, double mouseY, MouseButtonEvent info, boolean repeate) {
var ret = ScreenEvent.MouseButtonPressed.Pre.BUS.post(new ScreenEvent.MouseButtonPressed.Pre(screen, mouseX, mouseY, info.button()));
var ret = ScreenEvent.MouseButtonPressed.Pre.BUS.post(new ScreenEvent.MouseButtonPressed.Pre(screen, mouseX, mouseY, info));
if (!ret)
ret = screen.mouseClicked(info, repeate);
var result = ScreenEvent.MouseButtonPressed.Post.BUS.fire(new ScreenEvent.MouseButtonPressed.Post(screen, mouseX, mouseY, info.button(), ret)).getResult();
var result = ScreenEvent.MouseButtonPressed.Post.BUS.fire(new ScreenEvent.MouseButtonPressed.Post(screen, mouseX, mouseY, info, ret, new Result.Holder())).getResult();
return result == Result.DEFAULT ? ret : result == Result.ALLOW;
}
public static boolean onMouseButtonPre(int button, int action, int mods) {
return InputEvent.MouseButton.Pre.BUS.post(new InputEvent.MouseButton.Pre(button, action, mods));
public static boolean onMouseButtonPre(MouseButtonInfo info, int action) {
return InputEvent.MouseButton.Pre.BUS.post(new InputEvent.MouseButton.Pre(info, action));
}
public static void onMouseButtonPost(int button, int action, int mods) {
InputEvent.MouseButton.Post.BUS.post(new InputEvent.MouseButton.Post(button, action, mods));
public static void onMouseButtonPost(MouseButtonInfo info, int action) {
InputEvent.MouseButton.Post.BUS.post(new InputEvent.MouseButton.Post(info, action));
}
public static boolean onScreenMouseScrollPre(Screen guiScreen, double mouseX, double mouseY, double deltaX, double deltaY) {
@@ -145,28 +147,28 @@ public final class ForgeEventFactoryClient {
PlayStreamingSourceEvent.BUS.post(new PlayStreamingSourceEvent(engine, sound, channel));
}
public static boolean onScreenKeyPressedPre(Screen screen, int keyCode, int scanCode, int modifiers) {
return ScreenEvent.KeyPressed.Pre.BUS.post(new ScreenEvent.KeyPressed.Pre(screen, keyCode, scanCode, modifiers));
public static boolean onScreenKeyPressedPre(Screen screen, KeyEvent info) {
return ScreenEvent.KeyPressed.Pre.BUS.post(new ScreenEvent.KeyPressed.Pre(screen, info));
}
public static boolean onScreenKeyPressedPost(Screen screen, int keyCode, int scanCode, int modifiers) {
return ScreenEvent.KeyPressed.Post.BUS.post(new ScreenEvent.KeyPressed.Post(screen, keyCode, scanCode, modifiers));
public static boolean onScreenKeyPressedPost(Screen screen, KeyEvent info) {
return ScreenEvent.KeyPressed.Post.BUS.post(new ScreenEvent.KeyPressed.Post(screen, info));
}
public static boolean onScreenKeyReleasedPre(Screen screen, int keyCode, int scanCode, int modifiers) {
return ScreenEvent.KeyReleased.Pre.BUS.post(new ScreenEvent.KeyReleased.Pre(screen, keyCode, scanCode, modifiers));
public static boolean onScreenKeyReleasedPre(Screen screen, KeyEvent info) {
return ScreenEvent.KeyReleased.Pre.BUS.post(new ScreenEvent.KeyReleased.Pre(screen, info));
}
public static boolean onScreenKeyReleasedPost(Screen screen, int keyCode, int scanCode, int modifiers) {
return ScreenEvent.KeyReleased.Post.BUS.post(new ScreenEvent.KeyReleased.Post(screen, keyCode, scanCode, modifiers));
public static boolean onScreenKeyReleasedPost(Screen screen, KeyEvent info) {
return ScreenEvent.KeyReleased.Post.BUS.post(new ScreenEvent.KeyReleased.Post(screen, info));
}
public static boolean onScreenCharTypedPre(Screen screen, char codePoint, int modifiers) {
return ScreenEvent.CharacterTyped.Pre.BUS.post(new ScreenEvent.CharacterTyped.Pre(screen, codePoint, modifiers));
public static boolean onScreenCharTypedPre(Screen screen, CharacterEvent info) {
return ScreenEvent.CharacterTyped.Pre.BUS.post(new ScreenEvent.CharacterTyped.Pre(screen, info));
}
public static boolean onScreenCharTypedPost(Screen screen, char codePoint, int modifiers) {
return ScreenEvent.CharacterTyped.Post.BUS.post(new ScreenEvent.CharacterTyped.Post(screen, codePoint, modifiers));
public static boolean onScreenCharTypedPost(Screen screen, CharacterEvent info) {
return ScreenEvent.CharacterTyped.Post.BUS.post(new ScreenEvent.CharacterTyped.Post(screen, info));
}
public static boolean onClickInputPickBlock(KeyMapping keyBinding) {
@@ -242,10 +244,6 @@ public final class ForgeEventFactoryClient {
TickEvent.RenderTickEvent.Post.BUS.post(new TickEvent.RenderTickEvent.Post(timer));
}
public static RenderTooltipEvent.Background onRenderTooltipBackground(@NotNull ItemStack stack, GuiGraphics graphics, int x, int y, @NotNull Font font, @NotNull List<ClientTooltipComponent> components, @Nullable ResourceLocation backgroundPrefix) {
return RenderTooltipEvent.Background.BUS.fire(new RenderTooltipEvent.Background(stack, graphics, x, y, font, components, backgroundPrefix));
}
public static boolean onToastAdd(Toast toast) {
return ToastAddEvent.BUS.post(new ToastAddEvent(toast));
}
@@ -269,13 +267,12 @@ public final class ForgeEventFactoryClient {
public static Map<Type, Function<EntityModelSet, SkullModelBase>> onCreateSkullModels() {
var builder = ImmutableMap.<Type, Function<EntityModelSet, SkullModelBase>>builder();
ModLoader.postEvent(new EntityRenderersEvent.CreateSkullModels(builder));
EntityRenderersEvent.CreateSkullModels.BUS.post(new EntityRenderersEvent.CreateSkullModels(builder));
return builder.build();
}
public static ModelEvent.RegisterModelStateDefinitions onRegisterModeStateDefinitions() {
// This is on the mod bus because it happens during the initial texture reload, which is while the Forge bus is shut down.
return ModLoader.postEventWithReturn(new ModelEvent.RegisterModelStateDefinitions());
return ModelEvent.RegisterModelStateDefinitions.BUS.fire(new ModelEvent.RegisterModelStateDefinitions());
}
public static void onInitLevelRenderer() {
@@ -7,6 +7,8 @@ package net.minecraftforge.client.event;
import com.mojang.blaze3d.platform.InputConstants;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.input.KeyEvent;
import net.minecraft.client.input.MouseButtonInfo;
import net.minecraft.world.InteractionHand;
import net.minecraftforge.eventbus.api.bus.CancellableEventBus;
import net.minecraftforge.eventbus.api.bus.EventBus;
@@ -15,6 +17,7 @@ import net.minecraftforge.eventbus.api.event.RecordEvent;
import net.minecraftforge.eventbus.api.event.characteristic.Cancellable;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import org.lwjgl.glfw.GLFW;
/**
@@ -37,13 +40,21 @@ public sealed interface InputEvent {
* @see Post
*/
sealed interface MouseButton extends InputEvent {
/**
* {@return the MouseButtonInfo object containing information about the mouse button state}
* @see MouseButtonInfo
*/
MouseButtonInfo getInfo();
/**
* {@return the mouse button's input code}
*
* @see GLFW mouse constants starting with 'GLFW_MOUSE_BUTTON_'
* @see <a href="https://www.glfw.org/docs/latest/group__buttons.html" target="_top">the online GLFW documentation</a>
*/
int getButton();
default int getButton() {
return getInfo().button();
}
/**
* {@return the mouse button's action}
@@ -64,7 +75,9 @@ public sealed interface InputEvent {
* @see GLFW#GLFW_KEY_NUM_LOCK NUM LOCK modifier key bit
* @see <a href="https://www.glfw.org/docs/latest/group__mods.html" target="_top">the online GLFW documentation</a>
*/
int getModifiers();
default int getModifiers() {
return getInfo().modifiers();
}
/**
* Fired when a mouse button is pressed/released, <b>before</b> being processed by vanilla.
@@ -75,7 +88,7 @@ public sealed interface InputEvent {
*
* @see <a href="https://www.glfw.org/docs/latest/input_guide.html#input_mouse_button" target="_top">the online GLFW documentation</a>
*/
record Pre(int getButton, int getAction, int getModifiers) implements Cancellable, MouseButton, RecordEvent {
record Pre(MouseButtonInfo getInfo, int getAction) implements Cancellable, MouseButton, RecordEvent {
public static final CancellableEventBus<Pre> BUS = CancellableEventBus.create(Pre.class);
@ApiStatus.Internal
@@ -89,7 +102,7 @@ public sealed interface InputEvent {
*
* @see <a href="https://www.glfw.org/docs/latest/input_guide.html#input_mouse_button" target="_top">the online GLFW documentation</a>
*/
record Post(int getButton, int getAction, int getModifiers) implements MouseButton, RecordEvent {
record Post(MouseButtonInfo getInfo, int getAction) implements MouseButton, RecordEvent {
public static final EventBus<Post> BUS = EventBus.create(Post.class);
@ApiStatus.Internal
@@ -115,6 +128,7 @@ public sealed interface InputEvent {
* @param getMouseX the X position of the mouse cursor
* @param getMouseY the Y position of the mouse cursor
*/
@NullMarked
record MouseScrollingEvent(
double getDeltaX,
double getDeltaY,
@@ -135,12 +149,20 @@ public sealed interface InputEvent {
*
* <p>This event is fired only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
record Key(int getKey, int getScanCode, int getAction, int getModifiers) implements RecordEvent, InputEvent {
record Key(KeyEvent getInfo, int getAction) implements RecordEvent, InputEvent {
public static final EventBus<Key> BUS = EventBus.create(Key.class);
@ApiStatus.Internal
public Key {}
/**
* {@return the KeyEvent object containing information about the key being pressed/released}
* @see KeyEvent
*/
public KeyEvent getInfo() {
return getInfo;
}
/**
* {@return the {@code GLFW} (platform-agnostic) key code}
*
@@ -149,7 +171,7 @@ public sealed interface InputEvent {
* @see <a href="https://www.glfw.org/docs/latest/group__keys.html" target="_top">the online GLFW documentation</a>
*/
public int getKey() {
return getKey;
return getInfo().key();
}
/**
@@ -162,7 +184,7 @@ public sealed interface InputEvent {
* @see InputConstants#getKey(int, int)
*/
public int getScanCode() {
return getScanCode;
return getInfo().scancode();
}
/**
@@ -188,7 +210,7 @@ public sealed interface InputEvent {
* @see <a href="https://www.glfw.org/docs/latest/group__mods.html" target="_top">the online GLFW documentation</a>
*/
public int getModifiers() {
return getModifiers;
return getInfo().modifiers();
}
}
@@ -15,10 +15,12 @@ import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraftforge.client.model.geometry.IGeometryLoader;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.MutableEvent;
import net.minecraftforge.eventbus.api.event.RecordEvent;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.event.IModBusEvent;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import java.util.Collections;
import java.util.HashMap;
@@ -45,9 +47,13 @@ public sealed interface ModelEvent {
* @param getResults the modifiable registry map of models and their model names
*/
record ModifyBakingResult(ModelBakery getModelBakery, ModelBakery.BakingResult getResults)
implements IModBusEvent, ModelEvent {
implements RecordEvent, ModelEvent {
public static final EventBus<ModifyBakingResult> BUS = EventBus.create(ModifyBakingResult.class);
/** @deprecated {@link ModelEvent.ModifyBakingResult} is no longer an {@link IModBusEvent}, so use {@link #BUS} directly. */
@Deprecated(forRemoval = true, since = "1.21.9")
public static EventBus<ModifyBakingResult> getBus(BusGroup modBusGroup) {
return IModBusEvent.getBus(modBusGroup, ModifyBakingResult.class);
return BUS;
}
@ApiStatus.Internal
@@ -66,9 +72,13 @@ public sealed interface ModelEvent {
* @param getModelBakery the model loader
*/
record BakingCompleted(ModelManager getModelManager, ModelBakery getModelBakery)
implements IModBusEvent, ModelEvent {
implements RecordEvent, ModelEvent {
public static final EventBus<BakingCompleted> BUS = EventBus.create(BakingCompleted.class);
/** @deprecated {@link ModelEvent.BakingCompleted} is no longer an {@link IModBusEvent}, so use {@link #BUS} directly. */
@Deprecated(forRemoval = true, since = "1.21.9")
public static EventBus<BakingCompleted> getBus(BusGroup modBusGroup) {
return IModBusEvent.getBus(modBusGroup, BakingCompleted.class);
return BUS;
}
@ApiStatus.Internal
@@ -83,16 +93,20 @@ public sealed interface ModelEvent {
*
* <p>This event is fired only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
final class RegisterModelStateDefinitions implements IModBusEvent, ModelEvent {
final class RegisterModelStateDefinitions extends MutableEvent implements ModelEvent {
public static final EventBus<RegisterModelStateDefinitions> BUS = EventBus.create(RegisterModelStateDefinitions.class);
/** @deprecated {@link ModelEvent.RegisterModelStateDefinitions} is no longer an {@link IModBusEvent}, so use {@link #BUS} directly. */
@Deprecated(forRemoval = true, since = "1.21.9")
public static EventBus<RegisterModelStateDefinitions> getBus(BusGroup modBusGroup) {
return IModBusEvent.getBus(modBusGroup, RegisterModelStateDefinitions.class);
return BUS;
}
private final Map<ResourceLocation, StateDefinition<Block, BlockState>> states = new HashMap<>();
private final Map<ResourceLocation, StateDefinition<Block, BlockState>> view = Collections.unmodifiableMap(states);
@ApiStatus.Internal
public RegisterModelStateDefinitions() { }
public RegisterModelStateDefinitions() {}
/**
* Returns a read only view of the extra registered models
@@ -114,9 +128,14 @@ public sealed interface ModelEvent {
*
* <p>This event is fired only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
final class RegisterGeometryLoaders implements IModBusEvent, ModelEvent {
@NullMarked
final class RegisterGeometryLoaders extends MutableEvent implements ModelEvent {
public static final EventBus<RegisterGeometryLoaders> BUS = EventBus.create(RegisterGeometryLoaders.class);
/** @deprecated {@link ModelEvent.RegisterGeometryLoaders} is no longer an {@link IModBusEvent}, so use {@link #BUS} directly. */
@Deprecated(forRemoval = true, since = "1.21.9")
public static EventBus<RegisterGeometryLoaders> getBus(BusGroup modBusGroup) {
return IModBusEvent.getBus(modBusGroup, RegisterGeometryLoaders.class);
return BUS;
}
private final Map<ResourceLocation, IGeometryLoader> loaders;
@@ -128,13 +147,11 @@ public sealed interface ModelEvent {
/**
* Registers a new geometry loader.
* @param resourceLocation The namespace should match your mod's namespace, such as your mod ID
*/
public void register(String name, IGeometryLoader loader) {
@SuppressWarnings("removal")
var namespace = ModLoadingContext.get().getActiveNamespace();
var key = ResourceLocation.fromNamespaceAndPath(namespace, name);
Preconditions.checkArgument(!loaders.containsKey(key), "Geometry loader already registered: " + key);
loaders.put(key, loader);
public void register(ResourceLocation resourceLocation, IGeometryLoader loader) {
Preconditions.checkArgument(!loaders.containsKey(resourceLocation), "Geometry loader already registered: " + resourceLocation);
loaders.put(resourceLocation, loader);
}
}
}
@@ -15,8 +15,6 @@ import org.jetbrains.annotations.ApiStatus;
/**
* Fired when the {@link ClientRecipeBook} has updated information about recipes from the server to the client.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
@@ -6,14 +6,13 @@
package net.minecraftforge.client.event;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandBuildContext;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.arguments.ObjectiveArgument;
import net.minecraft.commands.arguments.ResourceLocationArgument;
import net.minecraftforge.common.util.HasResult;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.RecordEvent;
import net.minecraftforge.eventbus.api.event.characteristic.Cancellable;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
@@ -23,14 +22,12 @@ import org.jspecify.annotations.NullMarked;
*
* <p>Some command arguments behave differently for the client commands dispatcher:</p>
* <ul>
* <li>{@link ResourceLocationArgument#getAdvancement(com.mojang.brigadier.context.CommandContext, String)} only returns
* advancements that are shown on the advancements screen.
* <li>{@link ResourceLocationArgument#getId(CommandContext, String)} only returns advancements that are shown on
* the advancements screen.
* <li>{@link ObjectiveArgument#getObjective(com.mojang.brigadier.context.CommandContext, String)} only returns
* objectives that are displayed to the player.
* </ul>
*
* <p>This event is not {@linkplain Cancellable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @param getDispatcher the command dispatcher for registering commands to be executed on the client
@@ -11,11 +11,12 @@ import net.minecraft.server.packs.resources.ReloadableResourceManager;
import net.minecraftforge.event.AddReloadListenerEvent;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.MutableEvent;
import net.minecraftforge.eventbus.api.event.characteristic.SelfDestructing;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* Fired to allow mods to register their reload listeners on the client-side resource manager.
@@ -23,12 +24,16 @@ import org.jetbrains.annotations.ApiStatus;
*
* <p>For registering reload listeners on the server-side resource manager, see {@link AddReloadListenerEvent}.</p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
* <p>This event is fired only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public final class RegisterClientReloadListenersEvent implements SelfDestructing, IModBusEvent {
@NullMarked
public final class RegisterClientReloadListenersEvent extends MutableEvent implements SelfDestructing {
public static final EventBus<RegisterClientReloadListenersEvent> BUS = EventBus.create(RegisterClientReloadListenersEvent.class);
/** @deprecated {@link RegisterClientReloadListenersEvent} is no longer an {@link IModBusEvent}, so use {@link #BUS} directly. */
@Deprecated(forRemoval = true, since = "1.21.9")
public static EventBus<RegisterClientReloadListenersEvent> getBus(BusGroup modBusGroup) {
return IModBusEvent.getBus(modBusGroup, RegisterClientReloadListenersEvent.class);
return BUS;
}
private final ReloadableResourceManager resourceManager;
@@ -9,11 +9,12 @@ import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent
import net.minecraft.world.inventory.tooltip.TooltipComponent;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.MutableEvent;
import net.minecraftforge.eventbus.api.event.characteristic.SelfDestructing;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import java.util.Map;
import java.util.function.Function;
@@ -22,12 +23,16 @@ import java.util.function.Function;
* Allows users to register custom {@link net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent}
* factories for their {@link net.minecraft.world.inventory.tooltip.TooltipComponent} types.
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
* <p>This event is fired only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public final class RegisterClientTooltipComponentFactoriesEvent implements SelfDestructing, IModBusEvent {
@NullMarked
public final class RegisterClientTooltipComponentFactoriesEvent extends MutableEvent implements SelfDestructing {
public static final EventBus<RegisterClientTooltipComponentFactoriesEvent> BUS = EventBus.create(RegisterClientTooltipComponentFactoriesEvent.class);
/** @deprecated {@link RegisterClientTooltipComponentFactoriesEvent} is no longer an {@link IModBusEvent}, so use {@link #BUS} directly. */
@Deprecated(forRemoval = true, since = "1.21.9")
public static EventBus<RegisterClientTooltipComponentFactoriesEvent> getBus(BusGroup modBusGroup) {
return IModBusEvent.getBus(modBusGroup, RegisterClientTooltipComponentFactoriesEvent.class);
return BUS;
}
private final Map<Class<? extends TooltipComponent>, Function<TooltipComponent, ClientTooltipComponent>> factories;
@@ -11,10 +11,11 @@ import net.minecraft.core.BlockPos;
import net.minecraft.world.level.ColorResolver;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.MutableEvent;
import net.minecraftforge.eventbus.api.event.RecordEvent;
import net.minecraftforge.eventbus.api.event.characteristic.SelfDestructing;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
@@ -24,24 +25,24 @@ import java.util.ArrayList;
* Fired for registering block and item color handlers at the appropriate time.
* See the two subclasses for registering block or item color handlers.
*
* <p>These events are fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
* <p>These events are fired only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see RegisterColorHandlersEvent.Block
* @see RegisterColorHandlersEvent.ColorResolvers
*/
public sealed interface RegisterColorHandlersEvent extends IModBusEvent {
public sealed interface RegisterColorHandlersEvent {
/**
* Fired for registering block color handlers.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
* <p>This event is fired only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
record Block(BlockColors getBlockColors) implements RegisterColorHandlersEvent {
record Block(BlockColors getBlockColors) implements RecordEvent, RegisterColorHandlersEvent {
public static final EventBus<Block> BUS = EventBus.create(Block.class);
/** @deprecated {@link RegisterColorHandlersEvent.Block} is no longer an {@link IModBusEvent}, so use {@link #BUS} directly. */
@Deprecated(forRemoval = true, since = "1.21.9")
public static EventBus<Block> getBus(BusGroup modBusGroup) {
return IModBusEvent.getBus(modBusGroup, Block.class);
return BUS;
}
@ApiStatus.Internal
@@ -73,9 +74,13 @@ public sealed interface RegisterColorHandlersEvent extends IModBusEvent {
* {@link net.minecraft.world.level.BlockAndTintGetter#getBlockTint(BlockPos, ColorResolver)}.
*/
@NullMarked
final class ColorResolvers implements RegisterColorHandlersEvent, SelfDestructing {
final class ColorResolvers extends MutableEvent implements SelfDestructing, RegisterColorHandlersEvent {
public static final EventBus<ColorResolvers> BUS = EventBus.create(ColorResolvers.class);
/** @deprecated {@link RegisterColorHandlersEvent.ColorResolvers} is no longer an {@link IModBusEvent}, so use {@link #BUS} directly. */
@Deprecated(forRemoval = true, since = "1.21.9")
public static EventBus<ColorResolvers> getBus(BusGroup modBusGroup) {
return IModBusEvent.getBus(modBusGroup, ColorResolvers.class);
return BUS;
}
private final ArrayList<ColorResolver> builder;
@@ -9,10 +9,10 @@ import net.minecraft.client.renderer.DimensionSpecialEffects;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.MutableEvent;
import net.minecraftforge.eventbus.api.event.characteristic.SelfDestructing;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
@@ -21,13 +21,16 @@ import java.util.Map;
/**
* Allows users to register custom {@link DimensionSpecialEffects} for their dimensions.
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
* <p>This event is fired only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
@NullMarked
public final class RegisterDimensionSpecialEffectsEvent implements SelfDestructing, IModBusEvent {
public final class RegisterDimensionSpecialEffectsEvent extends MutableEvent implements SelfDestructing {
public static final EventBus<RegisterDimensionSpecialEffectsEvent> BUS = EventBus.create(RegisterDimensionSpecialEffectsEvent.class);
/** @deprecated {@link RegisterDimensionSpecialEffectsEvent} is no longer an {@link IModBusEvent}, so use {@link #BUS} directly. */
@Deprecated(forRemoval = true, since = "1.21.9")
public static EventBus<RegisterDimensionSpecialEffectsEvent> getBus(BusGroup modBusGroup) {
return IModBusEvent.getBus(modBusGroup, RegisterDimensionSpecialEffectsEvent.class);
return BUS;
}
private final Map<ResourceLocation, DimensionSpecialEffects> effects;
@@ -9,10 +9,10 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.EntityType;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.MutableEvent;
import net.minecraftforge.eventbus.api.event.characteristic.SelfDestructing;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
@@ -22,13 +22,16 @@ import java.util.Map;
* Allows users to register custom shaders to be used when the player spectates a certain kind of entity.
* Vanilla examples of this are the green effect for creepers and the invert effect for endermen.
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
* <p>This event is fired on only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
@NullMarked
public final class RegisterEntitySpectatorShadersEvent implements SelfDestructing, IModBusEvent {
public final class RegisterEntitySpectatorShadersEvent extends MutableEvent implements SelfDestructing {
public static final EventBus<RegisterEntitySpectatorShadersEvent> BUS = EventBus.create(RegisterEntitySpectatorShadersEvent.class);
/** @deprecated {@link RegisterEntitySpectatorShadersEvent} is no longer an {@link IModBusEvent}, so use {@link #BUS} directly. */
@Deprecated(forRemoval = true, since = "1.21.9")
public static EventBus<RegisterEntitySpectatorShadersEvent> getBus(BusGroup modBusGroup) {
return IModBusEvent.getBus(modBusGroup, RegisterEntitySpectatorShadersEvent.class);
return BUS;
}
private final Map<EntityType<?>, ResourceLocation> shaders;
@@ -11,11 +11,12 @@ import net.minecraftforge.client.IItemDecorator;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.MutableEvent;
import net.minecraftforge.eventbus.api.event.characteristic.SelfDestructing;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import java.util.ArrayList;
import java.util.List;
@@ -24,12 +25,16 @@ import java.util.Map;
/**
* Allows users to register custom {@linkplain IItemDecorator IItemDecorator} to Items.
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
* <p>This event is fired only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public final class RegisterItemDecorationsEvent implements SelfDestructing, IModBusEvent {
@NullMarked
public final class RegisterItemDecorationsEvent extends MutableEvent implements SelfDestructing {
public static final EventBus<RegisterItemDecorationsEvent> BUS = EventBus.create(RegisterItemDecorationsEvent.class);
/** @deprecated {@link RegisterItemDecorationsEvent} is no longer an {@link IModBusEvent}, so use {@link #BUS} directly. */
@Deprecated(forRemoval = true, since = "1.21.9")
public static EventBus<RegisterItemDecorationsEvent> getBus(BusGroup modBusGroup) {
return IModBusEvent.getBus(modBusGroup, RegisterItemDecorationsEvent.class);
return BUS;
}
private final Map<Item, List<IItemDecorator>> decorators;
@@ -9,22 +9,27 @@ import net.minecraft.client.KeyMapping;
import net.minecraft.client.Options;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.MutableEvent;
import net.minecraftforge.eventbus.api.event.characteristic.SelfDestructing;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.apache.commons.lang3.ArrayUtils;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* Allows users to register custom {@link net.minecraft.client.KeyMapping key mappings}.
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
* <p>This event is fired only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public final class RegisterKeyMappingsEvent implements SelfDestructing, IModBusEvent {
@NullMarked
public final class RegisterKeyMappingsEvent extends MutableEvent implements SelfDestructing {
public static final EventBus<RegisterKeyMappingsEvent> BUS = EventBus.create(RegisterKeyMappingsEvent.class);
/** @deprecated {@link RegisterKeyMappingsEvent} is no longer an {@link IModBusEvent}, so use {@link #BUS} directly. */
@Deprecated(forRemoval = true, since = "1.21.9")
public static EventBus<RegisterKeyMappingsEvent> getBus(BusGroup modBusGroup) {
return IModBusEvent.getBus(modBusGroup, RegisterKeyMappingsEvent.class);
return BUS;
}
private final Options options;
@@ -13,8 +13,9 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.RenderTypeGroup;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.MutableEvent;
import net.minecraftforge.eventbus.api.event.characteristic.SelfDestructing;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.event.IModBusEvent;
import org.jetbrains.annotations.ApiStatus;
@@ -25,9 +26,13 @@ import java.util.Map;
*
* <p>This event is fired only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public final class RegisterNamedRenderTypesEvent implements IModBusEvent {
public final class RegisterNamedRenderTypesEvent extends MutableEvent implements SelfDestructing {
public static final EventBus<RegisterNamedRenderTypesEvent> BUS = EventBus.create(RegisterNamedRenderTypesEvent.class);
/** @deprecated {@link RegisterNamedRenderTypesEvent} is no longer an {@link IModBusEvent}, so use {@link #BUS} directly. */
@Deprecated(forRemoval = true, since = "1.21.9")
public static EventBus<RegisterNamedRenderTypesEvent> getBus(BusGroup modBusGroup) {
return IModBusEvent.getBus(modBusGroup, RegisterNamedRenderTypesEvent.class);
return BUS;
}
private final Map<ResourceLocation, RenderTypeGroup> renderTypes;
@@ -40,26 +45,24 @@ public final class RegisterNamedRenderTypesEvent implements IModBusEvent {
/**
* Registers a named {@link RenderTypeGroup}.
*
* @param name The name
* @param resourceLocation The namespace should match your mod's namespace, such as your mod ID
* @param blockRenderType What ChunkSectionLayer to render in
* @param entityRenderType A {@link RenderType} using {@link DefaultVertexFormat#NEW_ENTITY}
*/
public void register(String name, ChunkSectionLayer blockRenderType, RenderType entityRenderType) {
register(name, blockRenderType, entityRenderType, entityRenderType);
public void register(ResourceLocation resourceLocation, ChunkSectionLayer blockRenderType, RenderType entityRenderType) {
register(resourceLocation, blockRenderType, entityRenderType, entityRenderType);
}
/**
* Registers a named {@link RenderTypeGroup}.
*
* @param name The name
* @param key The namespace should match your mod's namespace, such as your mod ID
* @param blockRenderType What ChunkSectionLayer to render in
* @param entityRenderType A {@link RenderType} using {@link DefaultVertexFormat#NEW_ENTITY}
* @param fabulousEntityRenderType A {@link RenderType} using {@link DefaultVertexFormat#NEW_ENTITY} for use when
* "fabulous" rendering is enabled
*/
public void register(String name, ChunkSectionLayer blockRenderType, RenderType entityRenderType, RenderType fabulousEntityRenderType) {
@SuppressWarnings("removal")
var key = ResourceLocation.fromNamespaceAndPath(ModLoadingContext.get().getActiveNamespace(), name);
public void register(ResourceLocation key, ChunkSectionLayer blockRenderType, RenderType entityRenderType, RenderType fabulousEntityRenderType) {
Preconditions.checkArgument(!renderTypes.containsKey(key), "Render type already registered: " + key);
Preconditions.checkArgument(entityRenderType.format() == DefaultVertexFormat.NEW_ENTITY, "The entity render type must use the NEW_ENTITY vertex format.");
Preconditions.checkArgument(fabulousEntityRenderType.format() == DefaultVertexFormat.NEW_ENTITY, "The fabulous entity render type must use the NEW_ENTITY vertex format.");
@@ -11,11 +11,12 @@ import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.core.particles.ParticleType;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.MutableEvent;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.registries.RegisterEvent;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* Fired for registering particle providers at the appropriate time.
@@ -23,14 +24,16 @@ import org.jetbrains.annotations.ApiStatus;
* <p>{@link ParticleType}s must be registered during {@link RegisterEvent} as usual;
* this event is only for the {@link ParticleProvider}s.</p>
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
* <p>This event is fired only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public final class RegisterParticleProvidersEvent implements IModBusEvent {
@NullMarked
public final class RegisterParticleProvidersEvent extends MutableEvent {
public static EventBus<RegisterParticleProvidersEvent> BUS = EventBus.create(RegisterParticleProvidersEvent.class);
/** @deprecated {@link RegisterParticleProvidersEvent} is no longer an {@link IModBusEvent}, so use {@link #BUS} directly. */
@Deprecated(forRemoval = true, since = "1.21.9")
public static EventBus<RegisterParticleProvidersEvent> getBus(BusGroup modBusGroup) {
return IModBusEvent.getBus(modBusGroup, RegisterParticleProvidersEvent.class);
return BUS;
}
private final ParticleResources particles;
@@ -25,7 +25,7 @@ import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
/**
* <p>Event for registering {@link PresetEditor} screen factories for world presets.</p>
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModBusGroup() mod BusGroup},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public final class RegisterPresetEditorsEvent implements SelfDestructing, IModBusEvent {
@@ -12,22 +12,24 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.textures.ITextureAtlasSpriteLoader;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.MutableEvent;
import net.minecraftforge.eventbus.api.event.characteristic.SelfDestructing;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
/**
* Allows users to register custom {@link ITextureAtlasSpriteLoader texture atlas sprite loaders}.
*
* <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod-specific event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
* <p>This event is fired only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public final class RegisterTextureAtlasSpriteLoadersEvent implements SelfDestructing, IModBusEvent {
public final class RegisterTextureAtlasSpriteLoadersEvent extends MutableEvent implements SelfDestructing {
public static final EventBus<RegisterTextureAtlasSpriteLoadersEvent> BUS = EventBus.create(RegisterTextureAtlasSpriteLoadersEvent.class);
/** {@link RegisterTextureAtlasSpriteLoadersEvent} is no longer an {@link IModBusEvent}, so use {@link #BUS} directly. */
@Deprecated(forRemoval = true, since = "1.21.9")
public static EventBus<RegisterTextureAtlasSpriteLoadersEvent> getBus(BusGroup modBusGroup) {
return IModBusEvent.getBus(modBusGroup, RegisterTextureAtlasSpriteLoadersEvent.class);
return BUS;
}
private final BiMap<ResourceLocation, ITextureAtlasSpriteLoader> loaders;
@@ -39,12 +41,11 @@ public final class RegisterTextureAtlasSpriteLoadersEvent implements SelfDestruc
/**
* Registers a custom {@link ITextureAtlasSpriteLoader sprite loader}.
* @param resourceLocation The namespace should match your mod's namespace, such as your mod ID
*/
public void register(String name, ITextureAtlasSpriteLoader loader) {
@SuppressWarnings("removal")
var key = ResourceLocation.fromNamespaceAndPath(ModLoadingContext.get().getActiveNamespace(), name);
Preconditions.checkArgument(!loaders.containsKey(key), "Sprite loader already registered: " + key);
public void register(ResourceLocation resourceLocation, ITextureAtlasSpriteLoader loader) {
Preconditions.checkArgument(!loaders.containsKey(resourceLocation), "Sprite loader already registered: " + resourceLocation);
Preconditions.checkArgument(!loaders.containsValue(loader), "Sprite loader already registered as " + loaders.inverse().get(loader));
loaders.put(key, loader);
loaders.put(resourceLocation, loader);
}
}
@@ -22,9 +22,9 @@ import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
/**
* Fired before a selection highlight is rendered.
* Fired before a selection highlight is rendered.<br>
* You should extract your custom data here so that you can use it in the render callback without effecting world state.
*
* <br><br>
* See the two subclasses to listen for blocks or entities.
*
* @see Block
@@ -21,7 +21,7 @@ import org.jetbrains.annotations.ApiStatus;
/**
* Fired before an entity renderer renders the nameplate of an entity.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and {@linkplain HasResult has a result}.</p>
* <p>This event {@linkplain HasResult has a result}:</p>
* <ul>
* <li>{@link Result#ALLOW} - the nameplate will be forcibly rendered.</li>
* <li>{@link Result#DEFAULT} - the vanilla logic will be used.</li>
@@ -35,7 +35,6 @@ import java.util.List;
*
* @see RenderTooltipEvent.GatherComponents
* @see RenderTooltipEvent.Pre
* @see RenderTooltipEvent.Background
*/
@NullMarked
public abstract sealed class RenderTooltipEvent extends MutableEvent implements InheritableEvent {
@@ -117,7 +116,7 @@ public abstract sealed class RenderTooltipEvent extends MutableEvent implements
*
* <p>This event is {@linkplain Cancellable cancellable}.
* If this event is cancelled, then the list of components will be empty, causing the tooltip to not be rendered and
* the corresponding {@link RenderTooltipEvent.Pre} and {@link RenderTooltipEvent.Background} to not be fired.</p>
* the corresponding {@link RenderTooltipEvent.Pre} to not be fired.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
@@ -195,11 +194,10 @@ public abstract sealed class RenderTooltipEvent extends MutableEvent implements
/**
* Fired <b>before</b> the tooltip is rendered.
* This can be used to modify the positioning and font of the tooltip.
* This can be used to modify the positioning, font, and background of the tooltip.
*
* <p>This event is {@linkplain Cancellable cancellable}.
* If this event is cancelled, then the tooltip will not be rendered and the corresponding
* {@link RenderTooltipEvent.Background} will not be fired.</p>
* If this event is cancelled, then the tooltip will not be rendered
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
@@ -210,13 +208,19 @@ public abstract sealed class RenderTooltipEvent extends MutableEvent implements
private final int screenWidth;
private final int screenHeight;
private final ClientTooltipPositioner positioner;
@Nullable
private ResourceLocation background;
@Nullable
private ResourceLocation originalBackground;
@ApiStatus.Internal
public Pre(ItemStack stack, GuiGraphics graphics, int x, int y, int screenWidth, int screenHeight, Font font, List<ClientTooltipComponent> components, ClientTooltipPositioner positioner) {
public Pre(ItemStack stack, GuiGraphics graphics, int x, int y, int screenWidth, int screenHeight, Font font, List<ClientTooltipComponent> components, ClientTooltipPositioner positioner, @Nullable ResourceLocation background) {
super(stack, graphics, x, y, font, components);
this.screenWidth = screenWidth;
this.screenHeight = screenHeight;
this.positioner = positioner;
this.background = background;
this.originalBackground = background;
}
/**
@@ -266,27 +270,6 @@ public abstract sealed class RenderTooltipEvent extends MutableEvent implements
public void setY(int y) {
this.y = y;
}
}
/**
* Fired when the tooltip background prefix is determined.
* This can be used to modify the textures to be used for the tooltip background.
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public static final class Background extends RenderTooltipEvent {
public static final EventBus<Background> BUS = EventBus.create(Background.class);
private final @Nullable ResourceLocation originalBackground;
private @Nullable ResourceLocation background;
@ApiStatus.Internal
public Background(ItemStack stack, GuiGraphics graphics, int x, int y, Font fr, List<ClientTooltipComponent> components, @Nullable ResourceLocation background) {
super(stack, graphics, x, y, fr, components);
this.originalBackground = background;
this.background = background;
}
/**
* Sets the new prefix for the background texture
@@ -9,6 +9,9 @@ import com.mojang.blaze3d.platform.InputConstants;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.input.CharacterEvent;
import net.minecraft.client.input.KeyEvent;
import net.minecraft.client.input.MouseButtonEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.util.HasResult;
import net.minecraftforge.common.util.Result;
@@ -21,6 +24,7 @@ import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.lwjgl.glfw.GLFW;
import java.util.Collections;
@@ -107,7 +111,7 @@ public sealed interface ScreenEvent {
/**
* Fired <b>before</b> the screen's overridable initialization method is fired.
*
* <p>This event is {@linkplain Cancellable cancellable}, and does not {@linkplain HasResult have a result}.
* <p>This event is {@linkplain Cancellable cancellable}.
* If the event is cancelled, the initialization method will not be called, and the widgets and children lists
* will not be cleared.</p>
*
@@ -126,8 +130,6 @@ public sealed interface ScreenEvent {
/**
* Fired <b>after</b> the screen's overridable initialization method is called.
*
* <p>This event is not {@linkplain Cancellable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
@@ -172,7 +174,7 @@ public sealed interface ScreenEvent {
/**
* Fired <b>before</b> the screen is drawn.
*
* <p>This event is {@linkplain Cancellable cancellable}, and does not {@linkplain HasResult have a result}.
* <p>This event is {@linkplain Cancellable cancellable}.
* If the event is cancelled, the screen will not be drawn.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
@@ -189,8 +191,6 @@ public sealed interface ScreenEvent {
/**
* Fired <b>after</b> the screen is drawn.
*
* <p>This event is not {@linkplain Cancellable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
@@ -322,25 +322,32 @@ public sealed interface ScreenEvent {
* @see MouseButtonPressed.Post
*/
sealed interface MouseButtonPressed extends MouseInput {
/**
* {@return the MouseButtonEvent object containing information about the mouse button being pressed}
*/
MouseButtonEvent getInfo();
/**
* {@return the mouse button's input code}
*
* @see GLFW mouse constants starting with 'GLFW_MOUSE_BUTTON_'
* @see <a href="https://www.glfw.org/docs/latest/group__buttons.html" target="_top">the online GLFW documentation</a>
*/
int getButton();
default int getButton() {
return getInfo().button();
}
/**
* Fired <b>before</b> the mouse click is handled by the screen.
*
* <p>This event is {@linkplain Cancellable cancellable}, and does not {@linkplain HasResult have a result}.
* <p>This event is {@linkplain Cancellable cancellable}.
* If the event is cancelled, the screen's mouse click handler will be bypassed
* and the corresponding {@link MouseButtonPressed.Post} will not be fired.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
record Pre(Screen getScreen, double getMouseX, double getMouseY, int getButton)
record Pre(Screen getScreen, double getMouseX, double getMouseY, MouseButtonEvent getInfo)
implements Cancellable, MouseButtonPressed, RecordEvent {
public static final CancellableEventBus<Pre> BUS = CancellableEventBus.create(Pre.class);
@@ -352,7 +359,7 @@ public sealed interface ScreenEvent {
* Fired <b>after</b> the mouse click is handled, if the corresponding {@link MouseButtonPressed.Pre} was not
* cancelled.
*
* <p>This event is not {@linkplain Cancellable cancellable}, {@linkplain HasResult has a result}.</p>
* <p>This event {@linkplain HasResult has a result}:</p>
* <ul>
* <li>{@link Result#ALLOW} - forcibly sets the mouse click as handled</li>
* <li>{@link Result#DEFAULT} - defaults to the return value of
@@ -367,15 +374,14 @@ public sealed interface ScreenEvent {
Screen getScreen,
double getMouseX,
double getMouseY,
int getButton,
MouseButtonEvent getInfo,
boolean wasHandled,
Result.Holder resultHolder
) implements HasResult.Record, MouseButtonPressed, RecordEvent {
public static final EventBus<Post> BUS = EventBus.create(Post.class);
@ApiStatus.Internal
public Post(Screen screen, double mouseX, double mouseY, int button, boolean handled) {
this(screen, mouseX, mouseY, button, handled, new Result.Holder());
public Post {
}
/**
@@ -406,7 +412,7 @@ public sealed interface ScreenEvent {
/**
* Fired <b>before</b> the mouse release is handled by the screen.
*
* <p>This event is {@linkplain Cancellable cancellable}, and does not {@linkplain HasResult have a result}.
* <p>This event is {@linkplain Cancellable cancellable}.
* If the event is cancelled, the screen's mouse release handler will be bypassed
* and the corresponding {@link MouseButtonReleased.Post} will not be fired.</p>
*
@@ -425,11 +431,11 @@ public sealed interface ScreenEvent {
* Fired <b>after</b> the mouse release is handled, if the corresponding {@link MouseButtonReleased.Pre} was
* not cancelled.
*
* <p>This event is not {@linkplain Cancellable cancellable}, {@linkplain HasResult has a result}.</p>
* <p>This event {@linkplain HasResult has a result}:</p>
* <ul>
* <li>{@link Result#ALLOW} - forcibly sets the mouse release as handled</li>
* <li>{@link Result#DEFAULT} - defaults to the return value of
* {@link Screen#mouseReleased(double, double, int)} from the screen (see {@link #wasHandled()}.</li>
* {@link Screen#mouseReleased(MouseButtonEvent)} from the screen (see {@link #wasHandled()}.</li>
* <li>{@link Result#DENY} - forcibly sets the mouse release as not handled.</li>
* </ul>
*
@@ -467,6 +473,7 @@ public sealed interface ScreenEvent {
* @see MouseDragged.Pre
* @see MouseDragged.Post
*/
@NullMarked
sealed interface MouseDragged extends MouseInput {
/**
* {@return the mouse button's input code}
@@ -489,7 +496,7 @@ public sealed interface ScreenEvent {
/**
* Fired <b>before</b> the mouse drag is handled by the screen.
*
* <p>This event is {@linkplain Cancellable cancellable}, and does not {@linkplain HasResult have a result}.
* <p>This event is {@linkplain Cancellable cancellable}.
* If the event is cancelled, the screen's mouse drag handler will be bypassed
* and the corresponding {@link MouseDragged.Post} will not be fired.</p>
*
@@ -542,6 +549,7 @@ public sealed interface ScreenEvent {
* @see MouseScrolled.Pre
* @see MouseScrolled.Post
*/
@NullMarked
sealed interface MouseScrolled extends MouseInput {
/**
* {@return the amount of change / delta of the mouse scroll in the vertical direction}
@@ -556,7 +564,7 @@ public sealed interface ScreenEvent {
/**
* Fired <b>before</b> the mouse scroll is handled by the screen.
*
* <p>This event is {@linkplain Cancellable cancellable}, and does not {@linkplain HasResult have a result}.
* <p>This event is {@linkplain Cancellable cancellable}.
* If the event is cancelled, the screen's mouse scroll handler will be bypassed
* and the corresponding {@link MouseScrolled.Post} will not be fired.</p>
*
@@ -600,6 +608,12 @@ public sealed interface ScreenEvent {
* @see <a href="https://www.glfw.org/docs/latest/input_guide.html#input_key" target="_top">the online GLFW documentation</a>
*/
sealed interface KeyInput extends ScreenEvent {
/**
* {@return the KeyEvent object containing information about the key being pressed/released}
* @see KeyEvent
*/
KeyEvent getInfo();
/**
* {@return the {@code GLFW} (platform-agnostic) key code}
*
@@ -607,7 +621,9 @@ public sealed interface ScreenEvent {
* @see GLFW key constants starting with {@code GLFW_KEY_}
* @see <a href="https://www.glfw.org/docs/latest/group__keys.html" target="_top">the online GLFW documentation</a>
*/
int getKeyCode();
default int getKeyCode() {
return getInfo().key();
}
/**
* {@return the platform-specific scan code}
@@ -618,7 +634,9 @@ public sealed interface ScreenEvent {
*
* @see InputConstants#getKey(int, int)
*/
int getScanCode();
default int getScanCode() {
return getInfo().scancode();
}
/**
* {@return a bit field representing the active modifier keys}
@@ -631,7 +649,9 @@ public sealed interface ScreenEvent {
* @see GLFW#GLFW_KEY_NUM_LOCK NUM LOCK modifier key bit
* @see <a href="https://www.glfw.org/docs/latest/group__mods.html" target="_top">the online GLFW documentation</a>
*/
int getModifiers();
default int getModifiers() {
return getInfo().modifiers();
}
}
/**
@@ -645,15 +665,14 @@ public sealed interface ScreenEvent {
/**
* Fired <b>before</b> the key press is handled by the screen.
*
* <p>This event is {@linkplain Cancellable cancellable} and does not {@linkplain HasResult have a result}.
* <p>This event is {@linkplain Cancellable cancellable}.
* If the event is cancelled, the screen's key press handler will be bypassed
* and the corresponding {@link KeyPressed.Post} will not be fired.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
record Pre(Screen getScreen, int getKeyCode, int getScanCode, int getModifiers)
implements Cancellable, KeyPressed, RecordEvent {
record Pre(Screen getScreen, KeyEvent getInfo) implements Cancellable, KeyPressed, RecordEvent {
public static final CancellableEventBus<Pre> BUS = CancellableEventBus.create(Pre.class);
@ApiStatus.Internal
@@ -664,14 +683,13 @@ public sealed interface ScreenEvent {
* Fired <b>after</b> the key press is handled, if not handled by the screen
* and the corresponding {@link KeyPressed.Pre} is not cancelled.
*
* <p>This event is {@linkplain Cancellable cancellable}, and does not {@linkplain HasResult have a result}.
* <p>This event is {@linkplain Cancellable cancellable}.
* If the event is cancelled, the key press will be set as handled.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
record Post(Screen getScreen, int getKeyCode, int getScanCode, int getModifiers)
implements Cancellable, KeyPressed, RecordEvent {
record Post(Screen getScreen, KeyEvent getInfo) implements Cancellable, KeyPressed, RecordEvent {
public static final CancellableEventBus<Post> BUS = CancellableEventBus.create(Post.class);
@ApiStatus.Internal
@@ -690,15 +708,14 @@ public sealed interface ScreenEvent {
/**
* Fired <b>before</b> the key release is handled by the screen.
*
* <p>This event is {@linkplain Cancellable cancellable}, and does not {@linkplain HasResult have a result}.
* <p>This event is {@linkplain Cancellable cancellable}.
* If the event is cancelled, the screen's key release handler will be bypassed
* and the corresponding {@link KeyReleased.Post} will not be fired.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
record Pre(Screen getScreen, int getKeyCode, int getScanCode, int getModifiers)
implements Cancellable, KeyReleased, RecordEvent {
record Pre(Screen getScreen, KeyEvent getInfo) implements Cancellable, KeyReleased, RecordEvent {
public static final CancellableEventBus<Pre> BUS = CancellableEventBus.create(Pre.class);
@ApiStatus.Internal
@@ -709,14 +726,13 @@ public sealed interface ScreenEvent {
* Fired <b>after</b> the key release is handled, if not handled by the screen
* and the corresponding {@link KeyReleased.Pre} is not cancelled.
*
* <p>This event is {@linkplain Cancellable cancellable}, and does not {@linkplain HasResult have a result}.
* <p>This event is {@linkplain Cancellable cancellable}.
* If the event is cancelled, the key release will be set as handled.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
record Post(Screen getScreen, int getKeyCode, int getScanCode, int getModifiers)
implements Cancellable, KeyReleased, RecordEvent {
record Post(Screen getScreen, KeyEvent getInfo) implements Cancellable, KeyReleased, RecordEvent {
public static final CancellableEventBus<Post> BUS = CancellableEventBus.create(Post.class);
@ApiStatus.Internal
@@ -733,10 +749,18 @@ public sealed interface ScreenEvent {
* @see <a href="https://www.glfw.org/docs/latest/input_guide.html#input_char" target="_top">the online GLFW documentation</a>
*/
sealed interface CharacterTyped extends ScreenEvent {
/**
* {@return The CharacterEvent object containing information about the character being typed}
*/
CharacterEvent getInfo();
/**
* {@return the character code point}
*/
char getCodePoint();
default char getCodePoint() {
return (char)getInfo().codepoint();
}
/**
* {@return a bit field representing the active modifier keys}
@@ -749,20 +773,21 @@ public sealed interface ScreenEvent {
* @see GLFW#GLFW_KEY_NUM_LOCK NUM LOCK modifier key bit
* @see <a href="https://www.glfw.org/docs/latest/group__mods.html" target="_top">the online GLFW documentation</a>
*/
int getModifiers();
default int getModifiers() {
return getInfo().modifiers();
}
/**
* Fired <b>before</b> the character input is handled by the screen.
*
* <p>This event is {@linkplain Cancellable cancellable}, and does not {@linkplain HasResult have a result}.
* <p>This event is {@linkplain Cancellable cancellable}.
* If the event is cancelled, the screen's character input handler will be bypassed
* and the corresponding {@link CharacterTyped.Post} will not be fired.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
record Pre(Screen getScreen, char getCodePoint, int getModifiers)
implements Cancellable, CharacterTyped, RecordEvent {
record Pre(Screen getScreen, CharacterEvent getInfo) implements Cancellable, CharacterTyped, RecordEvent {
public static final CancellableEventBus<Pre> BUS = CancellableEventBus.create(Pre.class);
@ApiStatus.Internal
@@ -773,13 +798,13 @@ public sealed interface ScreenEvent {
* Fired <b>after</b> the character input is handled, if not handled by the screen
* and the corresponding {@link CharacterTyped.Pre} is not cancelled.
*
* <p>This event is {@linkplain Cancellable cancellable}, and does not {@linkplain HasResult have a result}.
* <p>This event is {@linkplain Cancellable cancellable}.
* If the event is cancelled, the character input will be set as handled.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
record Post(Screen getScreen, char getCodePoint, int getModifiers) implements CharacterTyped, RecordEvent {
record Post(Screen getScreen, CharacterEvent getInfo) implements CharacterTyped, RecordEvent {
public static final EventBus<Post> BUS = EventBus.create(Post.class);
@ApiStatus.Internal
@@ -791,7 +816,7 @@ public sealed interface ScreenEvent {
* Fired before any {@link Screen} is opened, to allow changing it or preventing it from being opened.
* All screen layers on the screen are closed before this event is fired.
*
* <p>This event is {@linkplain Cancellable cancellable}, and does not {@linkplain HasResult have a result}.
* <p>This event is {@linkplain Cancellable cancellable}.
* If this event is cancelled, then the {@code Screen} shall be prevented from opening and any previous screen
* will remain open. However, cancelling this event will not prevent the closing of screen layers which happened before
* this event fired.</p>
@@ -848,8 +873,6 @@ public sealed interface ScreenEvent {
* Fired before a {@link Screen} is closed.
* All screen layers on the screen are closed before this event is fired.
*
* <p>This event is not {@linkplain Cancellable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
@@ -14,7 +14,8 @@ import net.minecraftforge.eventbus.api.event.MutableEvent;
import net.minecraftforge.eventbus.api.event.characteristic.Cancellable;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.io.File;
import java.io.IOException;
@@ -22,7 +23,7 @@ import java.io.IOException;
/**
* Fired when a screenshot is taken, but before it is written to disk.
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* <p>This event is {@linkplain Cancellable cancellable}.
* If this event is cancelled, then the screenshot is not written to disk, and the message in the event will be posted
* to the player's chat.</p>
*
@@ -31,6 +32,7 @@ import java.io.IOException;
*
* @see Screenshot
*/
@NullMarked
public final class ScreenshotEvent extends MutableEvent implements Cancellable {
public static final CancellableEventBus<ScreenshotEvent> BUS = CancellableEventBus.create(ScreenshotEvent.class);
public static final Component DEFAULT_CANCEL_REASON = Component.literal("Screenshot canceled");
@@ -80,7 +82,7 @@ public final class ScreenshotEvent extends MutableEvent implements Cancellable {
*
* @param resultMessage the new result message
*/
public void setResultMessage(Component resultMessage) {
public void setResultMessage(@Nullable Component resultMessage) {
this.resultMessage = resultMessage;
}
@@ -8,6 +8,7 @@ package net.minecraftforge.client.event;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.RecordEvent;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import org.jetbrains.annotations.ApiStatus;
@@ -31,8 +32,7 @@ public sealed interface TextureStitchEvent {
// * <p>Fired <b>before</b> a texture atlas is stitched together.
// * This can be used to add custom sprites to be stitched into the atlas.</p>
// *
// * <p>This event is fired on the {@linkplain FMLJavaModLoadingContext#getModEventBus()} mod-specific event bus},
// * only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
// * <p>This event is fired only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
// */
// public static class Pre extends TextureStitchEvent {
// private final Set<ResourceLocation> sprites;
@@ -61,9 +61,13 @@ public sealed interface TextureStitchEvent {
*
* <p>This event is fired only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
record Post(TextureAtlas getAtlas) implements TextureStitchEvent, IModBusEvent {
record Post(TextureAtlas getAtlas) implements RecordEvent, TextureStitchEvent {
public static final EventBus<Post> BUS = EventBus.create(Post.class);
/** @deprecated {@link TextureStitchEvent.Post} is no longer an {@link IModBusEvent}, so use {@link #BUS} directly. */
@Deprecated(forRemoval = true, since = "1.21.9")
public static EventBus<Post> getBus(BusGroup modBusGroup) {
return IModBusEvent.getBus(modBusGroup, Post.class);
return BUS;
}
@ApiStatus.Internal
@@ -16,7 +16,7 @@ import net.minecraftforge.fml.LogicalSide;
* Fired when the client queues a {@link Toast} message to be shown onscreen.
* Toasts are small popups that appear on the top right of the screen, for certain actions such as unlocking Advancements and Recipes.
*
* <p>This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
* <p>This event is {@linkplain Cancellable cancellable}.
* Cancelling the event stops the toast from being queued, which means it never renders.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
@@ -19,6 +19,7 @@ import net.minecraftforge.eventbus.api.event.characteristic.Cancellable;
import net.minecraftforge.fml.LogicalSide;
import org.jetbrains.annotations.ApiStatus;
import org.joml.Vector4f;
import org.jspecify.annotations.NullMarked;
/**
* Fired for hooking into the entity view rendering in {@link GameRenderer}.
@@ -52,7 +53,7 @@ public sealed interface ViewportEvent {
/**
* Fired for <b>rendering</b> custom fog. The plane distances are based on the player's render distance.
*
* <p>This event is {@linkplain Cancelable cancellable}, and {@linkplain HasResult has a result}. <br/>
* <p>This event is {@linkplain Cancellable cancellable}.<br>
* The event must be cancelled for any changes to the plane distances to take effect.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
@@ -140,8 +141,6 @@ public sealed interface ViewportEvent {
/**
* Fired for customizing the <b>color</b> of the fog visible to the player.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
@@ -234,11 +233,10 @@ public sealed interface ViewportEvent {
* Fired to allow altering the angles of the player's camera.
* This can be used to alter the player's view for different effects, such as applying roll.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
@NullMarked
final class ComputeCameraAngles extends MutableEvent implements ViewportEvent {
public static final EventBus<ComputeCameraAngles> BUS = EventBus.create(ComputeCameraAngles.class);
@@ -327,13 +325,12 @@ public sealed interface ViewportEvent {
* Fired for altering the raw field of view (FOV).
* This is after the FOV settings are applied, and before modifiers such as the Nausea effect.
*
* <p>This event is not {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.</p>
*
* <p>This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*
* @see ComputeFovModifierEvent
*/
@NullMarked
final class ComputeFov extends MutableEvent implements ViewportEvent {
public static final EventBus<ComputeFov> BUS = EventBus.create(ComputeFov.class);
@@ -8,9 +8,11 @@ package net.minecraftforge.client.event.sound;
import net.minecraft.client.sounds.SoundEngine;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.RecordEvent;
import net.minecraftforge.fml.LogicalSide;
import net.minecraftforge.fml.event.IModBusEvent;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* Fired when the {@link SoundEngine} is constructed or (re)loaded, such as during game initialization or when the sound
@@ -18,9 +20,14 @@ import org.jetbrains.annotations.ApiStatus;
*
* <p>This event is fired only on the {@linkplain LogicalSide#CLIENT logical client}.</p>
*/
public record SoundEngineLoadEvent(SoundEngine getEngine) implements IModBusEvent, SoundEvent {
@NullMarked
public record SoundEngineLoadEvent(SoundEngine getEngine) implements RecordEvent, SoundEvent {
public static final EventBus<SoundEngineLoadEvent> BUS = EventBus.create(SoundEngineLoadEvent.class);
/** @deprecated {@link SoundEngineLoadEvent} is no longer an {@link IModBusEvent}, so use {@link #BUS} directly. */
@Deprecated(forRemoval = true, since = "1.21.9")
public static EventBus<SoundEngineLoadEvent> getBus(BusGroup modBusGroup) {
return IModBusEvent.getBus(modBusGroup, SoundEngineLoadEvent.class);
return BUS;
}
@ApiStatus.Internal
@@ -5,15 +5,14 @@
package net.minecraftforge.client.gui;
import com.google.common.collect.ImmutableMap;
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent;
import net.minecraft.world.inventory.tooltip.TooltipComponent;
import net.minecraftforge.client.event.RegisterClientTooltipComponentFactoriesEvent;
import net.minecraftforge.fml.ModLoader;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NonNull;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
/**
@@ -22,13 +21,12 @@ import java.util.function.Function;
* Provides a lookup.
*/
public final class ClientTooltipComponentManager {
private static ImmutableMap<Class<? extends TooltipComponent>, Function<TooltipComponent, ClientTooltipComponent>> FACTORIES;
private static Map<Class<? extends TooltipComponent>, Function<TooltipComponent, ClientTooltipComponent>> FACTORIES;
/**
* Creates a client component for the given argument, or null if unsupported.
* Creates a client component for the given argument
*/
@Nullable
public static ClientTooltipComponent createClientTooltipComponent(TooltipComponent component) {
public static @NonNull ClientTooltipComponent createClientTooltipComponent(@NonNull TooltipComponent component) {
var factory = FACTORIES.get(component.getClass());
var ret = factory != null ? factory.apply(component) : null;
if (ret == null)
@@ -40,9 +38,9 @@ public final class ClientTooltipComponentManager {
public static void init() {
var factories = new HashMap<Class<? extends TooltipComponent>, Function<TooltipComponent, ClientTooltipComponent>>();
var event = new RegisterClientTooltipComponentFactoriesEvent(factories);
ModLoader.postEventWrapContainerInModOrder(event);
FACTORIES = ImmutableMap.copyOf(factories);
RegisterClientTooltipComponentFactoriesEvent.BUS.post(event);
FACTORIES = Map.copyOf(factories);
}
private ClientTooltipComponentManager() { }
private ClientTooltipComponentManager() {}
}
@@ -48,7 +48,7 @@ public class ClientModLoader {
createRunnableWithCatch(()->ModLoader.get().gatherAndInitializeMods(ModWorkManager.syncExecutor(), ModWorkManager.parallelExecutor(), ImmediateWindowHandler::renderTick)).run();
if (error == null) {
ResourcePackLoader.loadResourcePacks(defaultResourcePacks, true);
ModLoader.postEvent(new AddPackFindersEvent(PackType.CLIENT_RESOURCES, defaultResourcePacks::addPackFinder));
net.minecraftforge.event.AddPackFindersEvent.BUS.post(new AddPackFindersEvent(PackType.CLIENT_RESOURCES, defaultResourcePacks::addPackFinder));
DataPackConfig.DEFAULT.addModPacks(ResourcePackLoader.getPackNames());
mcResourceManager.registerReloadListener(ClientModLoader::onResourceReload);
mcResourceManager.registerReloadListener(BrandingControl.resourceManagerReloadListener());
@@ -5,14 +5,13 @@
package net.minecraftforge.client.model.geometry;
import com.google.common.collect.ImmutableMap;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.event.ModelEvent;
import net.minecraftforge.fml.ModLoader;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
/**
@@ -21,7 +20,7 @@ import java.util.stream.Collectors;
* Provides a lookup.
*/
public final class GeometryLoaderManager {
private static ImmutableMap<ResourceLocation, IGeometryLoader> LOADERS;
private static Map<ResourceLocation, IGeometryLoader> LOADERS;
private static String LOADER_LIST;
/**
@@ -42,11 +41,10 @@ public final class GeometryLoaderManager {
@ApiStatus.Internal
public static void init() {
var loaders = new HashMap<ResourceLocation, IGeometryLoader>();
var event = new ModelEvent.RegisterGeometryLoaders(loaders);
ModLoader.postEventWrapContainerInModOrder(event);
LOADERS = ImmutableMap.copyOf(loaders);
ModelEvent.RegisterGeometryLoaders.BUS.post(new ModelEvent.RegisterGeometryLoaders(loaders));
LOADERS = Map.copyOf(loaders);
LOADER_LIST = loaders.keySet().stream().map(ResourceLocation::toString).collect(Collectors.joining(", "));
}
private GeometryLoaderManager() { }
private GeometryLoaderManager() {}
}
@@ -10,7 +10,6 @@ import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableBiMap;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.event.RegisterTextureAtlasSpriteLoadersEvent;
import net.minecraftforge.fml.ModLoader;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
@@ -39,8 +38,7 @@ public final class TextureAtlasSpriteLoaderManager {
@ApiStatus.Internal
public static void init() {
var loaders = HashBiMap.<ResourceLocation, ITextureAtlasSpriteLoader>create();
var event = new RegisterTextureAtlasSpriteLoadersEvent(loaders);
ModLoader.postEventWrapContainerInModOrder(event);
RegisterTextureAtlasSpriteLoadersEvent.BUS.post(new RegisterTextureAtlasSpriteLoadersEvent(loaders));
LOADERS = ImmutableBiMap.copyOf(loaders);
}
@@ -16,9 +16,16 @@ import java.util.Collection;
/**
* A small helper class to aid Forge modders in migrating from EventBus 6 to the new EventBus 7.
* <br>
* Refer to the migration guide mentioned in the Forge 1.21.6 announcement or ask in the Forge Discord for help with
* migrating your mod to the new system.
* <p>
* Refer to the <a href="https://gist.github.com/PaintNinja/ad82c224aecee25efac1ea3e2cf19b91">migration guide</a>
* mentioned in the <a href="https://files.minecraftforge.net">Forge 1.21.6 changelog</a> or
* <a href="https://forums.minecraftforge.net/topic/159525-forge-581-minecraft-1218/">1.21.8 announcement</a> on the
* forums, or ask in <a href="https://discord.minecraftforge.net">the Forge Discord</a> for help with migrating your mod
* to the new system.
* </p>
* <p>
* You can also check the updated example code in <a href="https://files.minecraftforge.net">the MDK</a> for the MC
* version you're using.
*/
public final class EventBusMigrationHelper {
public static final EventBusMigrationHelper INSTANCE = new EventBusMigrationHelper(BusGroup.DEFAULT);
@@ -790,9 +790,9 @@ public final class ForgeHooks {
@Deprecated
public static void modifyAttributes() {
ModLoader.postEvent(new EntityAttributeCreationEvent(FORGE_ATTRIBUTES));
EntityAttributeCreationEvent.BUS.post(new EntityAttributeCreationEvent(FORGE_ATTRIBUTES));
Map<EntityType<? extends LivingEntity>, AttributeSupplier.Builder> finalMap = new HashMap<>();
ModLoader.postEvent(new EntityAttributeModificationEvent(finalMap));
EntityAttributeModificationEvent.BUS.post(new EntityAttributeModificationEvent(finalMap));
finalMap.forEach((k, v) -> {
AttributeSupplier supplier = DefaultAttributes.getSupplier(k);
@@ -1066,7 +1066,7 @@ public final class ForgeHooks {
entries.put(stack, vis);
});
ModLoader.postEvent(new BuildCreativeModeTabContentsEvent(tab, tabKey, params, entries));
BuildCreativeModeTabContentsEvent.BUS.post(new BuildCreativeModeTabContentsEvent(tab, tabKey, params, entries));
for (var entry : entries)
output.accept(entry.getKey(), entry.getValue());
@@ -386,7 +386,7 @@ public class ForgeMod {
BusGroup modBusGroup = context.getModBusGroup();
// Forge-provided datapack registries
DataPackRegistryEvent.NewRegistry.getBus(modBusGroup).addListener(event -> {
DataPackRegistryEvent.NewRegistry.BUS.addListener(event -> {
event.dataPackRegistry(ForgeRegistries.Keys.BIOME_MODIFIERS, BiomeModifier.DIRECT_CODEC);
event.dataPackRegistry(ForgeRegistries.Keys.STRUCTURE_MODIFIERS, StructureModifier.DIRECT_CODEC);
});
@@ -395,7 +395,7 @@ public class ForgeMod {
var registerEventBus = RegisterEvent.getBus(modBusGroup);
registerEventBus.addListener(ForgeMod::registerFluids);
registerEventBus.addListener(ForgeMod::registerVanillaDisplayContexts);
EntityAttributeModificationEvent.getBus(modBusGroup).addListener(ForgeMod::onRegisterAttributes);
EntityAttributeModificationEvent.BUS.addListener(ForgeMod::onRegisterAttributes);
ForgeDeferredRegistriesSetup.setup(modBusGroup);
for (var reg : registries)
reg.register(modBusGroup);
@@ -10,9 +10,12 @@ import net.minecraft.client.gui.screens.Screen;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.ClientCommandHandler;
import net.minecraftforge.client.ConfigScreenHandler;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.config.IConfigSpec;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.fml.loading.FMLEnvironment;
import net.minecraftforge.network.DualStackUtils;
import net.minecraftforge.versions.forge.ForgeVersion;
@@ -27,10 +30,15 @@ import java.util.function.Supplier;
public class MinecraftForge {
/**
* The EventBus for all the Forge Events.
* A helper for partial source-level back-compat with the old event system used in 1.21.5 and older, that acts as
* a wrapper around {@link BusGroup#DEFAULT}.
*
* Events marked with {@link net.minecraftforge.fml.event.IModBusEvent}
* belong on the ModBus and not this bus
* <p>Please refer to the javadocs for {@link EventBusMigrationHelper} for further information and resources on how
* to properly migrate your mod to the new event system.</p>
*
* <p>Events marked with {@link IModBusEvent} belong on the
* {@linkplain FMLJavaModLoadingContext#getModBusGroup() mod BusGroup} and not on the
* {@linkplain BusGroup#DEFAULT default BusGroup}.</p>
*/
public static final EventBusMigrationHelper EVENT_BUS = EventBusMigrationHelper.INSTANCE;
@@ -54,7 +62,7 @@ public class MinecraftForge {
* @param screenFunction A function that takes the mods screen as an argument and returns your config screen to
* show when the player clicks the config button for your mod on the mods screen.
* <p>You should call {@link Minecraft#setScreen(Screen)} with the provided mods screen for the
* action of your close button, using {@link Screen#minecraft} to get the client instance.</p>
* action of your close button, using {@link Screen#getMinecraft()} to get the client instance.</p>
* @see ModLoadingContext#registerExtensionPoint(Class, Supplier)
* @see ModLoadingContext#registerConfig(ModConfig.Type, IConfigSpec)
*/
Loaded 100 of 168 files, more files were not shown because too many files have changed in this diff. Show more