Compare commits

..
57 Commits
Author SHA1 Message Date
LexManos 086bcb7148 1.21.3 update 2024-10-25 18:04:34 -07:00
Joseph T. McQuigg c22589b5b3 Add c:foods/pie tag (#10135)
Replaces `forge:foods/pie`, which is now deprecated.
2024-10-17 18:40:33 +01:00
Paint_Ninja e0fbab3c49 Fix back-compat with legacy forge: tags (#10126) 2024-10-14 20:23:09 +01:00
Paint_Ninja 7cfed38194 Implement the de-facto common tags, add many new Forge tags (#9955)
https://forums.minecraftforge.net/topic/152815-common-tags-in-forge/
2024-10-13 14:38:37 +01:00
Joseph T. McQuigg b8105ca5a6 Patch BlockEntity.java to use getType Method (#10124) 2024-10-10 09:56:17 -07:00
LexManos c3ee633897 Fix and cleanup RuntimeDistCleaner 2024-10-08 14:31:17 -07:00
LexManos 23b41ce9b3 Sync component registry ids
Fix deprecation typo
Bump ModLauncher (better stack traces)
Set Automatic-Module-Name
2024-10-07 17:26:13 -07:00
StavWasPlayZ 2c6156be25 Fix layering of translucent sprites being broken by re-adding blur call Mojang removed (#10115) 2024-09-17 08:13:30 -07:00
Tygyh eeb3eb3689 Cleanup length and size checks (#10108) 2024-09-12 14:14:19 +01:00
Tygyh 44a158a714 Update vulnerable eclipse dependency (#10109) 2024-09-12 14:14:01 +01:00
LexManos 770298b1d5 Add and fire SystemMessageReceivedEvent
Replaces ClientChatReceivedEvent.System which was not fired due to Mojang changing ChatType to a registry.
2024-09-11 11:10:41 -07:00
LexManos 7bd50903a6 Fix highlighted item tooltip rendering twice. Closes #10112 2024-09-11 10:18:49 -07:00
Michael Bunting 64295d04ff Fix Network hooks not being initialized on GameTestServer (#10081)
Add rudimentary custom networking tests.
2024-09-11 09:57:17 -07:00
Michael Bunting f168ce4499 Fix inverted logic in TagEmptyCondition (#10069) 2024-09-04 12:19:15 -07:00
RealMangoRage 419c349a1f [1.21.1] Optionally supply FMLJavaModLoadingContext as a param to mod constructors (#10074)
Also made `FMLJavaModLoadingContext` extend `ModLoadingContext`. See the PR description for example usage.
2024-08-25 21:38:31 +01:00
Martijn van den Brand 26d0f7282f Fix issues with QuadBakingVertexConsumer and Forge's OBJ loader (#10065) 2024-08-22 17:26:44 -07:00
Martijn van den Brand 93c72d2d92 Fire OnDatapackSyncEvent when a player joins a server (#10076) 2024-08-22 14:37:39 -07:00
RealMangoRage f252871322 Add a few Method Helpers onto ResourceKey to make getting Data Driven Entries Simpler (#10071) 2024-08-17 20:50:07 -07:00
LexManos 32c8597305 Fix network channels not initializing during server transfers. Closes #10067 2024-08-17 13:04:02 -07:00
Paint_Ninja 9714512c31 [1.21.1] ModLoading cleanup and optimisations (#10052) 2024-08-16 22:49:11 +01:00
Yeregorix f106ff0c21 Properly ignore unknown mod files (#10063) 2024-08-14 02:17:09 -07:00
LexManos 9c85a6bb64 Add level access to Item.TooltipContext if available. 2024-08-09 19:39:00 -07:00
LexManos dc594f196a Load mods that declare explicit 1.21 compatibility as 1.21.1 is a minor bugfix. 2024-08-08 14:01:31 -07:00
LexManos db4d8a4729 1.21.1 2024-08-08 13:40:50 -07:00
andan42 0ca1d0ebd0 Removed unneeded boat patch related to MC-119811 (#10061) 2024-08-07 21:49:40 -07:00
LexManos 4995902bc0 Fix network sync of custom datapack registries.
Add gametest to verify that it functions.
2024-08-05 13:30:14 -07:00
James Mitchell 28e55b2843 Add a way to render tooltips from Formatted text and TooltipComponents elements (#10056) 2024-08-04 19:25:23 +01:00
LexManos decaace84a Fix custom creative tabs with search listing all items instead of just their own. 2024-08-01 17:21:35 -07:00
Joseph T. McQuigg 72f98dc4a3 Patch CropBlock to use instanceOf FarmBlock check instead of hard coding to Blocks.FARMLAND (#10044)
Signed-off-by: Joseph T. McQuigg <[email protected]>
2024-07-26 13:06:07 -07:00
LexManos 2951c1dac8 Fix ItemStack sensitive getDefaultAttributeModifiers not being called. 2024-07-25 16:01:53 -07:00
LexManos 40a0a16db0 Add missed @user_jvm_args.txt to run.bat 2024-07-25 16:00:04 -07:00
Martijn van den Brand 520680b037 Fix onPlaceItemIntoWorld always resetting item stack when in creative mode (#10047) 2024-07-18 12:43:11 -07:00
Martijn van den Brand 2844bead6e Fix matrix stack translations for RenderHighlightEvent (#10050) 2024-07-17 20:17:33 -07:00
LexManos d498ec4a45 Fix Global Loot Modifier deserialization not having registry access. Closes #10042 and #10043 2024-07-10 11:46:23 -07:00
LexManos e2d1ca7d0e Bump Mixin to 0.8.7 and allocate more ram to java compile tasks. 2024-07-08 13:11:00 -07:00
Paint_Ninja a547327c61 Use the new fire() and Result#isAllowed/isDenied/isDefault methods from EventBus (#10028) 2024-07-06 11:35:37 -07:00
LexManos e9a7ce9544 Some build cleanup 2024-07-02 10:25:49 -07:00
LexManos 511be6415c Make HangingSignBlockEntity useable with custom BlockEntityTypes. #10038 2024-07-02 10:25:35 -07:00
LexManos 4d85053a4d Send last PoseStack pos to RenderLevelStageEvent AFTER_PARTICLES stage. Fixes #10031 2024-07-02 10:12:16 -07:00
Jonathing 4fcf1877c1 Fix compile errors in MDK (#10029) 2024-06-24 11:52:00 -07:00
LexManos 41fd15fc57 Fix converting dirt to mud consumes whole bottle (not just emptying) Closes #10027 2024-06-21 13:34:37 -07:00
LexManos 8e0845d032 Add support for named sub-caps 2024-06-19 15:30:46 -07:00
LexManos c04e533a61 Fix conditional checking swallowing decoding errors when loading registry entries from datapacks. Fixes #9995 2024-06-19 15:09:37 -07:00
LexManos 54f4997f71 Apply license headers 2024-06-19 12:54:22 -07:00
LexManos 3272e995ab Some code cleanup in ModInfo/ModFileInfo to use less streams and make things easier to debug. 2024-06-18 11:47:11 -07:00
LexManos 8edb551571 Bump NightConfig to 3.7.3, to fix toml sub-configs being completely broken. Fixes #10015 2024-06-18 11:46:34 -07:00
LexManos 4bf0de4763 Make ForgeSpawnEgg assume color is opaque if alpha is set to 0.
Fixes "invisible" spawn eggs by default, but still allows modders to specify partial transparency.
2024-06-18 11:45:13 -07:00
vicmatskiv f6a54436b9 Fixed perspective rendering in SeparateTransformsModel (#10014) 2024-06-17 15:55:22 -07:00
Gabriel Harris-Rouquette 7a497cdbd6 Fix missed PartEntity patch in Player.attack (#10011) 2024-06-16 19:05:01 -07:00
thedarkcolour 06336a40d8 Choose default JarJar mod file type based on parent JAR Closes #9939 2024-06-14 09:50:05 -07:00
Ven 842c95cfb9 Fixed falling block entities not rendering as moving blocks (#10006) 2024-06-14 09:03:14 -07:00
LexManos aa2f471e87 Bump Night Config, Should fix #9122 2024-06-14 08:37:07 -07:00
LexManos 4209d1fc12 Fix ClientTickEvent not firing properly. 2024-06-13 17:13:15 -07:00
LexManos 583f363b6b Change tick event to have pre/post classes (#9890) 2024-06-13 15:15:04 -07:00
LexManos 1c633b4624 Fix Bogged and Wolf armor not being shearable by custom shears. Closes #10005 2024-06-13 14:21:30 -07:00
LexManos 2677511da6 Set team city branch 2024-06-13 14:20:03 -07:00
LexManos 387441c045 Remove Non-HolderLookup methods from INBTSerializeable and mark it as deprecated in favor of using Vanilla's new Component system. Closes #9998 2024-06-13 13:17:03 -07:00
1321 changed files with 15376 additions and 12823 deletions

No files matched your search

+1 -12
View File
@@ -26,7 +26,6 @@
/projects/mcp/
/projects/clean/
/projects/forge/
#/patches/*
#occupational hazards
/projects/**/build/
@@ -35,20 +34,10 @@
/projects/**/*.launch
/repo/
src/*/generated/**/.cache/
# Generated by gradle every import
**/src/main/resources/META-INF/MANIFEST.MF
/fmlloader/src/main/resources/forge_version.json
#Patch rejects
#/patches-/
#*.patch.rej
#/projects/*/rejects/
# FML changelog
changelog.txt
*.py
*.7z
/logs/
/ForgeRoot.ipr
/ForgeRoot.iws
+1 -1
View File
@@ -38,7 +38,7 @@ project {
params {
text("docker_jdk_version", "21", label = "Gradle version", description = "The version of the JDK to use during execution of tasks in a JDK.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("docker_gradle_version", "8.7", label = "Gradle version", description = "The version of Gradle to use during execution of Gradle tasks.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("git_main_branch", "1.20.x", label = "Git Main Branch", description = "The git main or default branch to use in VCS operations.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("git_main_branch", "1.21.x", label = "Git Main Branch", description = "The git main or default branch to use in VCS operations.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("git_branch_spec", """
+:refs/heads/(main*)
+:refs/heads/(master*)
+1 -1
View File
@@ -9,5 +9,5 @@ dependencies {
implementation 'net.minecraftforge:srgutils:0.5.10'
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.5.0.202303070854-r'
implementation 'org.eclipse.jgit:org.eclipse.jgit:6.7.0.202309050840-r'
}
+6 -34
View File
@@ -47,6 +47,7 @@ jarSigner.autoDetect('forge')
applyPatches {
level 'WARNING'
failOnError = UPDATING != 'true'
rejects = rootProject.layout.projectDirectory.dir('rejects').asFile
}
sourceSets {
@@ -194,6 +195,7 @@ patcher {
forge_server_gametest {
args '--launchTarget', 'forge_dev_server_gametest'
args '--uniqueWorld' // Unique world is used so that the world regenerates, as well as the world config isn't influenced by other runs
}
forge_server_gametest_test {
@@ -386,7 +388,7 @@ tasks.register("findFinalizeSpawnTargets", BytecodePredicateFinder) {
return 'net/minecraft/world/level/BaseSpawner' != parent.name // Ignore this class as we special case it.
&& insn.getOpcode().equals(Opcodes.INVOKEVIRTUAL)
&& insn.name == 'm_6518_'
&& insn.desc == '(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/MobSpawnType;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;';
&& insn.desc == '(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;';
}
}
tasks.register('validateDeprecations', ValidateDeprecations) {
@@ -404,16 +406,6 @@ tasks.register("downloadServerShim", DownloadMavenArtifact) {
artifact = libs.bootstrap.shim.get().toString()
changing = true
}
tasks.register("downloadMCPConfig", DownloadMavenArtifact) {
artifact = MCP_ARTIFACT
}
tasks.register('extractMCPConfigMappings', net.minecraftforge.gradle.common.tasks.JarExec) {
ext.output = file('build/mcp_mappings.tsrg')
tool = INSTALLER_TOOLS + ':fatjar'
args = ['--task', 'MCP_DATA', '--key', 'mappings', '--input', downloadMCPConfig.output.get().asFile.absolutePath, '--output', output.absolutePath]
outputs.file(output)
inputs.file(downloadMCPConfig.output)
}
tasks.register('downloadClientMappings', net.minecraftforge.gradle.common.tasks.JarExec) {
ext.output = file('build/client_mappings.tsrg')
tool = INSTALLER_TOOLS + ':fatjar'
@@ -620,9 +612,6 @@ tasks.register('installerJson', InstallerJson) {
dependsOn(launcherJson)
input.from(launcherJson.output)
// Extract MCP Mappings
dependsOn(extractMCPConfigMappings)
input.from(extractMCPConfigMappings.output)
// Download Official Mappings
dependsOn(downloadClientMappings, downloadServerMappings)
input.from(downloadClientMappings.output, downloadServerMappings.output)
@@ -649,14 +638,6 @@ tasks.register('installerJson', InstallerJson) {
minecraft: MC_VERSION,
serverJarPath: '{LIBRARY_DIR}/net/minecraft/server/{MINECRAFT_VERSION}/server-{MINECRAFT_VERSION}-bundled.jar',
data: [
MAPPINGS: [
client: "[${MCP_ARTIFACT.group}:${MCP_ARTIFACT.name}:${MCP_ARTIFACT.version}:mappings@tsrg]",
server: "[${MCP_ARTIFACT.group}:${MCP_ARTIFACT.name}:${MCP_ARTIFACT.version}:mappings@tsrg]"
],
MAPPINGS_SHA: [
client: "'${extractMCPConfigMappings.output.sha1}'",
server: "'${extractMCPConfigMappings.output.sha1}'"
],
MOJMAPS: [
client: "[net.minecraft:client:${MC_VERSION}:mappings@tsrg]",
server: "[net.minecraft:server:${MC_VERSION}:mappings@tsrg]"
@@ -746,18 +727,6 @@ tasks.register('installerJson', InstallerJson) {
outputs: [
'{MC_UNPACKED}': '{MC_UNPACKED_SHA}'
]
], [
jar: INSTALLER_TOOLS,
classpath: INSTALLER_TOOLS_CLASSPATH,
args: [
'--task', 'MCP_DATA',
'--input', "[${MCP_ARTIFACT.descriptor}]",
'--output', '{MAPPINGS}',
'--key', 'mappings'
],
outputs: [
'{MAPPINGS}': '{MAPPINGS_SHA}'
]
], [
jar: INSTALLER_TOOLS,
classpath: INSTALLER_TOOLS_CLASSPATH,
@@ -848,6 +817,9 @@ tasks.named('universalJar').configure {
exclude '.cache'
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.forge'
] as LinkedHashMap)
attributes([
'Specification-Title': 'Forge',
'Specification-Vendor': 'Forge Development LLC',
+1
View File
@@ -44,6 +44,7 @@ tasks.withType(JavaCompile).configureEach {
dependsOn 'processResources' // Needed because we merge the output of this with the output of the compile task. And gradle detects downstream tasks using the output without a hard dep
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
options.warnings = false // Shutup deprecated for removal warnings
options.forkOptions.jvmArgs += '-Xmx3G' // Needed to make compiling faster, and not run out of heap space in some cases.
}
// Merge the resources and classes into the same directory. We'll need to split them at runtime because
@@ -57,7 +57,9 @@ public class DeferredWorkQueue
LOGGER.debug(LOADING, "Dispatching synchronous work for work queue {}: {} jobs", modLoadingStage, tasks.size());
RuntimeException aggregate = new RuntimeException();
Stopwatch timer = Stopwatch.createStarted();
tasks.forEach(t -> makeRunnable(t, Runnable::run, aggregate));
for (TaskInfo t : tasks) {
makeRunnable(t, Runnable::run, aggregate);
}
timer.stop();
if (aggregate.getSuppressed().length > 0) {
LOGGER.fatal(
@@ -114,7 +116,7 @@ public class DeferredWorkQueue
return future;
}
private static class TaskInfo {
private static final class TaskInfo {
private final ModContainer owner;
private Runnable task;
private CompletableFuture<?> future;
@@ -61,7 +61,7 @@ public class InterModComms
}
}
private static ConcurrentMap<String, ConcurrentLinkedQueue<IMCMessage>> containerQueues = new ConcurrentHashMap<>();
private static final ConcurrentMap<String, ConcurrentLinkedQueue<IMCMessage>> containerQueues = new ConcurrentHashMap<>();
/**
* Send IMC to remote. Sender will default to the active modcontainer, or minecraft if not.
@@ -115,16 +115,14 @@ public class InterModComms
return getMessages(modId, s->Boolean.TRUE);
}
private static class QueueFilteringSpliterator implements Spliterator<IMCMessage>
{
private final ConcurrentLinkedQueue<IMCMessage> queue;
private final Predicate<String> methodFilter;
private final Iterator<IMCMessage> iterator;
private record QueueFilteringSpliterator(
ConcurrentLinkedQueue<IMCMessage> queue,
Predicate<String> methodFilter,
Iterator<IMCMessage> iterator
) implements Spliterator<IMCMessage> {
public QueueFilteringSpliterator(final ConcurrentLinkedQueue<IMCMessage> queue, final Predicate<String> methodFilter) {
this.queue = queue;
this.iterator = queue.iterator();
this.methodFilter = methodFilter;
this(queue, methodFilter, queue.iterator());
}
@Override
@@ -45,7 +45,7 @@ public abstract class ModContainer
protected final IModInfo modInfo;
protected ModLoadingStage modLoadingStage;
protected Supplier<?> contextExtension;
protected final Map<ModLoadingStage, Runnable> activityMap = new HashMap<>();
protected final Map<ModLoadingStage, Runnable> activityMap = new EnumMap<>(ModLoadingStage.class);
protected final Map<Class<? extends IExtensionPoint<?>>, Supplier<?>> extensionPoints = new IdentityHashMap<>();
protected final EnumMap<ModConfig.Type, ModConfig> configs = new EnumMap<>(ModConfig.Type.class);
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@@ -168,7 +168,10 @@ public abstract class ModContainer
}
public void dispatchConfigEvent(IConfigEvent event) {
configHandler.ifPresent(configHandler->configHandler.accept(event));
var handler = configHandler.orElse(null);
if (handler != null) {
handler.accept(event);
}
}
/**
@@ -15,12 +15,8 @@ import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
@@ -32,8 +28,6 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinWorkerThread;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
@@ -45,9 +39,7 @@ import java.util.stream.Stream;
* Master list of all mods - game-side version. This is classloaded in the game scope and
* can dispatch game level events as a result.
*/
public class ModList
{
//private static final Logger LOGGER = LogManager.getLogger();
public class ModList {
private static ModList INSTANCE;
private final List<IModFileInfo> modFiles;
private final List<IModInfo> sortedList;
@@ -57,8 +49,7 @@ public class ModList
private List<ModFileScanData> modFileScanData;
private List<ModContainer> sortedContainers;
private ModList(final List<ModFile> modFiles, final List<ModInfo> sortedList)
{
private ModList(final List<ModFile> modFiles, final List<ModInfo> sortedList) {
this.modFiles = modFiles.stream().map(ModFile::getModFileInfo).map(ModFileInfo.class::cast).collect(Collectors.toList());
this.sortedList = sortedList.stream().
map(ModInfo.class::cast).
@@ -74,19 +65,20 @@ public class ModList
}
private String fileToLine(IModFile mf) {
var mainMod = mf.getModInfos().getFirst();
return String.format(Locale.ENGLISH, "%-50.50s|%-30.30s|%-30.30s|%-20.20s|%-10.10s|Manifest: %s", mf.getFileName(),
mf.getModInfos().get(0).getDisplayName(),
mf.getModInfos().get(0).getModId(),
mf.getModInfos().get(0).getVersion(),
getModContainerState(mf.getModInfos().get(0).getModId()),
mainMod.getDisplayName(),
mainMod.getModId(),
mainMod.getVersion(),
getModContainerState(mainMod.getModId()),
((ModFileInfo)mf.getModFileInfo()).getCodeSigningFingerprint().orElse("NOSIGNATURE"));
}
private String crashReport() {
return "\n"+applyForEachModFile(this::fileToLine).collect(Collectors.joining("\n\t\t", "\t\t", ""));
}
public static ModList of(List<ModFile> modFiles, List<ModInfo> sortedList)
{
public static ModList of(List<ModFile> modFiles, List<ModInfo> sortedList) {
INSTANCE = new ModList(modFiles, sortedList);
return INSTANCE;
}
@@ -95,21 +87,11 @@ public class ModList
return INSTANCE;
}
private static ForkJoinWorkerThread newForkJoinWorkerThread(ForkJoinPool pool) {
ForkJoinWorkerThread thread = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
thread.setName("modloading-worker-" + thread.getPoolIndex());
// The default sets it to the SystemClassloader, so copy the current one.
thread.setContextClassLoader(Thread.currentThread().getContextClassLoader());
return thread;
}
public List<IModFileInfo> getModFiles()
{
public List<IModFileInfo> getModFiles() {
return modFiles;
}
public IModFileInfo getModFileById(String modid)
{
public IModFileInfo getModFileById(String modid) {
return this.fileById.get(modid);
}
@@ -131,16 +113,18 @@ public class ModList
CompletableFuture<Void> cf = new CompletableFuture<>();
final RuntimeException accumulator = new RuntimeException();
cf.completeExceptionally(accumulator);
throwables.forEach(exception -> {
for (Throwable exception : throwables) {
if (exception instanceof CompletionException) {
exception = exception.getCause();
}
if (exception.getSuppressed().length!=0) {
Arrays.stream(exception.getSuppressed()).forEach(accumulator::addSuppressed);
if (exception.getSuppressed().length != 0) {
for (Throwable throwable : exception.getSuppressed()) {
accumulator.addSuppressed(throwable);
}
} else {
accumulator.addSuppressed(exception);
}
});
}
return cf;
}
}
@@ -148,56 +132,47 @@ public class ModList
static <V> CompletableFuture<List<Map.Entry<V, Throwable>>> gather(List<? extends CompletableFuture<? extends V>> futures) {
List<Map.Entry<V, Throwable>> list = new ArrayList<>(futures.size());
CompletableFuture<?>[] results = new CompletableFuture[futures.size()];
futures.forEach(future -> {
for (var future : futures) {
int i = list.size();
list.add(null);
results[i] = future.whenComplete((result, exception) -> list.set(i, new AbstractMap.SimpleImmutableEntry<>(result, exception)));
});
}
return CompletableFuture.allOf(results).handle((r, th)->null).thenApply(res -> list);
}
void setLoadedMods(final List<ModContainer> modContainers)
{
void setLoadedMods(final List<ModContainer> modContainers) {
this.mods = modContainers;
this.sortedContainers = modContainers.stream().sorted(Comparator.comparingInt(c->sortedList.indexOf(c.getModInfo()))).toList();
this.indexedMods = modContainers.stream().collect(Collectors.toMap(ModContainer::getModId, Function.identity()));
}
@SuppressWarnings("unchecked")
public <T> Optional<T> getModObjectById(String modId)
{
public <T> Optional<T> getModObjectById(String modId) {
return getModContainerById(modId).map(ModContainer::getMod).map(o -> (T) o);
}
public Optional<? extends ModContainer> getModContainerById(String modId)
{
public Optional<? extends ModContainer> getModContainerById(String modId) {
return Optional.ofNullable(this.indexedMods.get(modId));
}
public Optional<? extends ModContainer> getModContainerByObject(Object obj)
{
public Optional<? extends ModContainer> getModContainerByObject(Object obj) {
return mods.stream().filter(mc -> mc.getMod() == obj).findFirst();
}
public List<IModInfo> getMods()
{
public List<IModInfo> getMods() {
return this.sortedList;
}
public boolean isLoaded(String modTarget)
{
public boolean isLoaded(String modTarget) {
return this.indexedMods.containsKey(modTarget);
}
public int size()
{
public int size() {
return mods.size();
}
public List<ModFileScanData> getAllScanData()
{
if (modFileScanData == null)
{
public List<ModFileScanData> getAllScanData() {
if (modFileScanData == null) {
modFileScanData = this.sortedList.stream().
map(IModInfo::getOwningFile).
filter(Objects::nonNull).
@@ -210,8 +185,7 @@ public class ModList
}
public void forEachModFile(Consumer<IModFile> fileConsumer)
{
public void forEachModFile(Consumer<IModFile> fileConsumer) {
modFiles.stream().map(IModFileInfo::getFile).forEach(fileConsumer);
}
@@ -230,10 +204,4 @@ public class ModList
public <T> Stream<T> applyForEachModContainer(Function<ModContainer, T> function) {
return indexedMods.values().stream().map(function);
}
private static class UncaughtModLoadingException extends ModLoadingException {
public UncaughtModLoadingException(ModLoadingStage stage, Throwable originalException) {
super(null, stage, "fml.modloading.uncaughterror", originalException);
}
}
}
@@ -77,13 +77,13 @@ import static net.minecraftforge.fml.Logging.LOADING;
public class ModLoader
{
private static final Logger LOGGER = LogManager.getLogger();
private static ModLoader INSTANCE;
private final LoadingModList loadingModList;
private final List<ModLoadingException> loadingExceptions;
private final Set<IModInfo> erroredModInfos;
private final List<ModLoadingWarning> loadingWarnings;
private final ModStateManager stateManager;
private boolean loadingStateValid;
private static boolean loadingStateValid;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private final Optional<Consumer<String>> statusConsumer = StartupNotificationManager.modLoaderConsumer();
private final Set<IModLoadingState> completedStates = new HashSet<>();
@@ -91,7 +91,6 @@ public class ModLoader
private ModLoader()
{
INSTANCE = this;
this.loadingModList = FMLLoader.getLoadingModList();
this.loadingExceptions = this.loadingModList.getErrors().stream()
.flatMap(ModLoadingException::fromEarlyException)
@@ -99,24 +98,32 @@ public class ModLoader
this.loadingWarnings = this.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()) {
this.erroredModInfos = Collections.emptySet();
} else {
this.erroredModInfos = Collections.newSetFromMap(new IdentityHashMap<>());
this.erroredModInfos.addAll(this.loadingExceptions.stream().map(ModLoadingException::getModInfo).toList());
}
this.loadingModList.getModFiles().stream()
.filter(ModFileInfo::missingLicense)
.filter(modFileInfo -> modFileInfo.getMods().stream().noneMatch(thisModInfo -> this.loadingExceptions.stream().map(ModLoadingException::getModInfo).anyMatch(otherInfo -> otherInfo == thisModInfo))) //Ignore files where any other mod already encountered an error
.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()))
.forEach(this.loadingExceptions::add);
this.stateManager = new ModStateManager();
CrashReportCallables.registerCrashCallable("ModLauncher", FMLLoader::getLauncherInfo);
CrashReportCallables.registerCrashCallable("ModLauncher launch target", FMLLoader::launcherHandlerName);
CrashReportCallables.registerCrashCallable("ModLauncher naming", FMLLoader::getNaming);
CrashReportCallables.registerCrashCallable("ModLauncher services", this::computeModLauncherServiceList);
CrashReportCallables.registerCrashCallable("FML Language Providers", this::computeLanguageList);
CrashReportCallables.registerCrashCallable("ModLauncher services", ModLoader::computeModLauncherServiceList);
CrashReportCallables.registerCrashCallable("FML Language Providers", ModLoader::computeLanguageList);
}
private String computeLanguageList() {
return "\n"+FMLLoader.getLanguageLoadingProvider().applyForEach(lp->lp.name() +"@"+ lp.getClass().getPackage().getImplementationVersion()).collect(Collectors.joining("\n\t\t", "\t\t", ""));
private static String computeLanguageList() {
return "\n" + FMLLoader.getLanguageLoadingProvider()
.applyForEach(lp -> lp.name() + "@" + lp.getClass().getPackage().getImplementationVersion())
.collect(Collectors.joining("\n\t\t", "\t\t", ""));
}
private String computeModLauncherServiceList() {
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")+
@@ -125,9 +132,13 @@ public class ModLoader
collect(Collectors.joining("\n\t\t","\t\t",""));
}
public static ModLoader get()
{
return INSTANCE == null ? INSTANCE = new ModLoader() : INSTANCE;
public static ModLoader get() {
return LazyInit.INSTANCE;
}
private static final class LazyInit {
private static final ModLoader INSTANCE = new ModLoader();
private LazyInit() {}
}
/**
@@ -144,7 +155,7 @@ public class ModLoader
final ModList modList = ModList.of(loadingModList.getModFiles().stream().map(ModFileInfo::getFile).toList(),
loadingModList.getMods());
if (!this.loadingExceptions.isEmpty()) {
LOGGER.fatal(CORE, "Error during pre-loading phase", loadingExceptions.get(0));
LOGGER.fatal(CORE, "Error during pre-loading phase", loadingExceptions.getFirst());
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
modList.setLoadedMods(Collections.emptyList());
loadingStateValid = false;
@@ -152,7 +163,7 @@ public class ModLoader
}
List<? extends ForgeFeature.Bound> failedBounds = loadingModList.getMods().stream()
.map(ModInfo::getForgeFeatures)
.flatMap(Collection::stream)
.flatMap(List::stream)
.filter(bound -> !ForgeFeature.testFeature(FMLEnvironment.dist, bound))
.toList();
@@ -169,10 +180,10 @@ public class ModLoader
final List<ModContainer> modContainers = loadingModList.getModFiles().stream()
.map(ModFileInfo::getFile)
.map(this::buildMods)
.<ModContainer>mapMulti(Iterable::forEach)
.flatMap(List::stream)
.toList();
if (!loadingExceptions.isEmpty()) {
LOGGER.fatal(CORE, "Failed to initialize mod containers", loadingExceptions.get(0));
LOGGER.fatal(CORE, "Failed to initialize mod containers", loadingExceptions.getFirst());
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
modList.setLoadedMods(Collections.emptyList());
loadingStateValid = false;
@@ -181,22 +192,28 @@ public class ModLoader
modList.setLoadedMods(modContainers);
this.modList = modList;
var stateList = stateManager.getStates(ModLoadingPhase.GATHER);
var progress = StartupMessageManager.addProgressBar("Mod Gather", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
stateList.forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress));
var progress = StartupMessageManager.addProgressBar("Mod Gather", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(this.modList)).sum());
for (IModLoadingState mls : stateList) {
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
}
progress.complete();
}
public void loadMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
var stateList = stateManager.getStates(ModLoadingPhase.LOAD);
var progress = StartupMessageManager.addProgressBar("Mod Loading", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
stateList.forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress));
for (IModLoadingState mls : stateList) {
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
}
progress.complete();
}
public void finishMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
var stateList = stateManager.getStates(ModLoadingPhase.COMPLETE);
var progress = StartupMessageManager.addProgressBar("Mod Complete", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
stateList.forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress));
for (IModLoadingState mls : stateList) {
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
}
statusConsumer.ifPresent(c->c.accept(String.format("Mod loading complete - %d mods loaded", this.modList.size())));
progress.complete();
}
@@ -206,10 +223,15 @@ public class ModLoader
LOGGER.error("Cowardly refusing to process mod state change request from {}", state);
return;
}
progressBar.label(progressBar.name()+ " working");
progressBar.label(progressBar.name() + " working");
syncExecutor.drive(ticker);
state.inlineRunnable().ifPresent(a->this.handleInlineTransition(a, state, syncExecutor, ticker));
state.buildTransition(syncExecutor, parallelExecutor, progressBar).ifPresent(t->waitForTransition(state, syncExecutor, ticker, t));
var inlineRunnable = state.inlineRunnable().orElse(null);
if (inlineRunnable != null) handleInlineTransition(inlineRunnable, state, syncExecutor, ticker);
var transition = state.buildTransition(syncExecutor, parallelExecutor, progressBar).orElse(null);
if (transition != null) waitForTransition(state, syncExecutor, ticker, transition);
completedStates.add(state);
}
@@ -286,9 +308,12 @@ public class ModLoader
try {
final String modId = idToProviderEntry.getKey();
final IModLanguageProvider.IModLanguageLoader languageLoader = idToProviderEntry.getValue();
IModInfo info = Optional.ofNullable(modInfoMap.get(modId)).
// throw a missing metadata error if there is no matching modid in the modInfoMap from the mods.toml file
orElseThrow(()->new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingmetadata", null, modId));
IModInfo info = modInfoMap.get(modId);
// throw a missing metadata error if there is no matching modid in the modInfoMap from the mods.toml file
if (info == null)
throw new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingmetadata", null, modId);
return languageLoader.loadMod(info, modFile.getScanResult(), FMLLoader.getGameLayer());
} catch (ModLoadingException mle) {
// exceptions are caught and added to the error list for later handling
@@ -303,7 +328,7 @@ public class ModLoader
* and don't want to cause extraneous crashes due to trying to do things that aren't possible in a "broken load"
*/
public static boolean isLoadingStateValid() {
return get().loadingStateValid;
return loadingStateValid;
}
public boolean hasCompletedState(final String stateName) {
@@ -8,39 +8,57 @@ package net.minecraftforge.fml;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.config.IConfigSpec;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.loading.FMLLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.function.BiPredicate;
import java.util.function.Supplier;
public class ModLoadingContext
{
public class ModLoadingContext {
private static final Logger LOGGER = LogUtils.getLogger();
private static final ThreadLocal<ModLoadingContext> context = ThreadLocal.withInitial(ModLoadingContext::new);
private ModContainer activeContainer;
private Object languageExtension;
private ModLoadingStage stage;
/**
* @deprecated Use the context provided by your language loader in your mod's constructor
*/
@Deprecated(forRemoval = true, since="1.21.1")
public static ModLoadingContext get() {
return context.get();
}
private ModContainer activeContainer;
/**
* @deprecated Going to be moved to ForgeHooks for Internal use.
*/
@Deprecated(forRemoval = true, since="1.21.1")
public void setActiveContainer(final ModContainer container) {
this.activeContainer = container;
this.languageExtension = container == null ? null : container.contextExtension.get();
}
/**
* Going to be moved to ForgeHooks for Internal use.
* @deprecated Override/Use {@link ModLoadingContext#getContainer()}
*/
@Deprecated(forRemoval = true, since="1.21.1")
public ModContainer getActiveContainer() {
return activeContainer == null ? ModList.get().getModContainerById("minecraft").orElseThrow(()->new RuntimeException("Where is minecraft???!")) : activeContainer;
}
/**
* @deprecated Going to be moved to ForgeHooks for Internal use.
*/
@Deprecated(forRemoval = true, since="1.21.1")
public String getActiveNamespace() {
return activeContainer == null ? "minecraft" : activeContainer.getNamespace();
}
/**
* @return {@link ModLoadingContext#getActiveContainer()} by default.
*/
public ModContainer getContainer() {
return getActiveContainer();
}
/**
* Register an {@link IExtensionPoint} with the mod container.
* @param point The extension point to register
@@ -48,7 +66,7 @@ public class ModLoadingContext
* @param <T> The type signature of the extension operator
*/
public <T extends Record & IExtensionPoint<T>> void registerExtensionPoint(Class<? extends IExtensionPoint<T>> point, Supplier<T> extension) {
getActiveContainer().registerExtensionPoint(point, extension);
getContainer().registerExtensionPoint(point, extension);
}
/**
@@ -57,7 +75,7 @@ public class ModLoadingContext
* @param displayTest The {@link IExtensionPoint.DisplayTest} to register
*/
public void registerDisplayTest(IExtensionPoint.DisplayTest displayTest) {
getActiveContainer().registerDisplayTest(() -> displayTest);
getContainer().registerDisplayTest(() -> displayTest);
}
/**
@@ -66,7 +84,7 @@ public class ModLoadingContext
* @param displayTest The {@link Supplier<IExtensionPoint.DisplayTest>} to register
*/
public void registerDisplayTest(Supplier<IExtensionPoint.DisplayTest> displayTest) {
getActiveContainer().registerDisplayTest(displayTest);
getContainer().registerDisplayTest(displayTest);
}
/**
@@ -76,7 +94,7 @@ public class ModLoadingContext
* @see IExtensionPoint.DisplayTest#DisplayTest(String, BiPredicate)
*/
public void registerDisplayTest(String version, BiPredicate<String, Boolean> remoteVersionTest) {
getActiveContainer().registerDisplayTest(new IExtensionPoint.DisplayTest(version, remoteVersionTest));
getContainer().registerDisplayTest(new IExtensionPoint.DisplayTest(version, remoteVersionTest));
}
/**
@@ -86,29 +104,29 @@ public class ModLoadingContext
* @see IExtensionPoint.DisplayTest#DisplayTest(Supplier, BiPredicate)
*/
public void registerDisplayTest(Supplier<String> suppliedVersion, BiPredicate<String, Boolean> remoteVersionTest) {
getActiveContainer().registerDisplayTest(new IExtensionPoint.DisplayTest(suppliedVersion, remoteVersionTest));
getContainer().registerDisplayTest(new IExtensionPoint.DisplayTest(suppliedVersion, remoteVersionTest));
}
public void registerConfig(ModConfig.Type type, IConfigSpec<?> spec) {
if (spec.isEmpty())
{
// This handles the case where a mod tries to register a config, without any options configured inside it.
LOGGER.debug("Attempted to register an empty config for type {} on mod {}", type, getActiveContainer().getModId());
LOGGER.debug("Attempted to register an empty config for type {} on mod {}", type, getContainer().getModId());
return;
}
getActiveContainer().addConfig(new ModConfig(type, spec, getActiveContainer()));
getContainer().addConfig(new ModConfig(type, spec, getContainer()));
}
public void registerConfig(ModConfig.Type type, IConfigSpec<?> spec, String fileName) {
if (spec.isEmpty())
{
// This handles the case where a mod tries to register a config, without any options configured inside it.
LOGGER.debug("Attempted to register an empty config for type {} on mod {} using file name {}", type, getActiveContainer().getModId(), fileName);
LOGGER.debug("Attempted to register an empty config for type {} on mod {} using file name {}", type, getContainer().getModId(), fileName);
return;
}
getActiveContainer().addConfig(new ModConfig(type, spec, getActiveContainer(), fileName));
getContainer().addConfig(new ModConfig(type, spec, getContainer(), fileName));
}
@@ -39,7 +39,10 @@ public record ModLoadingState(String name, String previous,
final ProgressMeter progressBar,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask) {
return transition.map(t -> t.build(name, syncExecutor, parallelExecutor, progressBar, preSyncTask, postSyncTask));
var transition = this.transition.orElse(null);
return transition == null
? Optional.empty()
: Optional.ofNullable(transition.build(name, syncExecutor, parallelExecutor, progressBar, preSyncTask, postSyncTask));
}
/**
@@ -16,15 +16,13 @@ import java.util.stream.Collectors;
@SuppressWarnings("UnstableApiUsage")
public class ModStateManager {
static ModStateManager INSTANCE;
private final EnumMap<ModLoadingPhase, List<IModLoadingState>> stateMap;
public ModStateManager() {
INSTANCE = this;
final var sp = ServiceLoader.load(FMLLoader.getGameLayer(), IModStateProvider.class);
this.stateMap = ServiceLoaderUtils.streamWithErrorHandling(sp, sce->{})
.map(IModStateProvider::getAllStates)
.<IModLoadingState>mapMulti(Iterable::forEach)
.flatMap(List::stream)
.collect(Collectors.groupingBy(IModLoadingState::phase, ()->new EnumMap<>(ModLoadingPhase.class), Collectors.toUnmodifiableList()));
}
@@ -36,14 +34,16 @@ public class ModStateManager {
var dummy = ModLoadingState.empty("", "", phase);
nodes.forEach(graph::addNode);
graph.addNode(dummy);
nodes.forEach(n->graph.putEdge(lookup.getOrDefault(n.previous(), dummy), n));
for (IModLoadingState node : nodes) {
graph.putEdge(lookup.getOrDefault(node.previous(), dummy), node);
}
return TopologicalSort.topologicalSort(graph, Comparator.comparingInt(nodes::indexOf)).stream().filter(st->st!=dummy).toList();
}
public IModLoadingState findState(final String stateName) {
return stateMap.values()
.stream()
.flatMap(Collection::stream)
.flatMap(List::stream)
.filter(mls -> mls.name().equals(stateName))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown IModLoadingState: " + stateName));
@@ -17,6 +17,7 @@ import static net.minecraftforge.fml.Logging.LOADING;
public class ModWorkManager {
private static final Logger LOGGER = LogManager.getLogger();
private static final long PARK_TIME = TimeUnit.MILLISECONDS.toNanos(1);
public interface DrivenExecutor extends Executor {
boolean selfDriven();
boolean driveOne();
@@ -33,8 +34,11 @@ public class ModWorkManager {
}
}
}
private static class SyncExecutor implements DrivenExecutor {
private ConcurrentLinkedDeque<Runnable> tasks = new ConcurrentLinkedDeque<>();
private record SyncExecutor(ConcurrentLinkedDeque<Runnable> tasks) implements DrivenExecutor {
public SyncExecutor() {
this(new ConcurrentLinkedDeque<>());
}
@Override
public boolean driveOne() {
@@ -56,13 +60,7 @@ public class ModWorkManager {
}
}
private static class WrappingExecutor implements DrivenExecutor {
private final Executor wrapped;
public WrappingExecutor(final Executor executor) {
this.wrapped = executor;
}
private record WrappingExecutor(Executor wrapped) implements DrivenExecutor {
@Override
public boolean selfDriven() {
return true;
@@ -79,11 +77,9 @@ public class ModWorkManager {
}
}
private static SyncExecutor syncExecutor;
private static final SyncExecutor syncExecutor = new SyncExecutor();
public static DrivenExecutor syncExecutor() {
if (syncExecutor == null)
syncExecutor = new SyncExecutor();
return syncExecutor;
}
@@ -91,14 +87,8 @@ public class ModWorkManager {
return new WrappingExecutor(executor);
}
private static ForkJoinPool parallelThreadPool;
public static Executor parallelExecutor() {
if (parallelThreadPool == null) {
final int loadingThreadCount = FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.MAX_THREADS);
LOGGER.debug(LOADING, "Using {} threads for parallel mod-loading", loadingThreadCount);
parallelThreadPool = new ForkJoinPool(loadingThreadCount, ModWorkManager::newForkJoinWorkerThread, null, false);
}
return parallelThreadPool;
return LazyInit.PARALLEL_EXECUTOR;
}
private static ForkJoinWorkerThread newForkJoinWorkerThread(ForkJoinPool pool) {
@@ -109,4 +99,14 @@ public class ModWorkManager {
return thread;
}
private static final class LazyInit {
private LazyInit() {}
private static final ForkJoinPool PARALLEL_EXECUTOR;
static {
final int loadingThreadCount = FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.MAX_THREADS);
LOGGER.debug(LOADING, "Using {} threads for parallel mod-loading", loadingThreadCount);
PARALLEL_EXECUTOR = new ForkJoinPool(loadingThreadCount, ModWorkManager::newForkJoinWorkerThread, null, false);
}
}
}
@@ -23,7 +23,7 @@ public final class OptionalMod<T>
return new OptionalMod<>(modId);
}
private static OptionalMod<?> EMPTY = new OptionalMod<>(true);
private static final OptionalMod<?> EMPTY = new OptionalMod<>(true);
private static <T> OptionalMod<T> empty() {
@SuppressWarnings("unchecked")
OptionalMod<T> t = (OptionalMod<T>) EMPTY;
@@ -213,8 +213,8 @@ public final class OptionalMod<T>
public boolean equals(Object obj)
{
if (this == obj) return true;
if (obj instanceof OptionalMod) {
return Objects.equals(((OptionalMod)obj).modId, modId);
if (obj instanceof OptionalMod<?> optionalMod) {
return Objects.equals(optionalMod.modId, modId);
}
return false;
}
@@ -25,7 +25,7 @@ import static net.minecraftforge.fml.config.ConfigTracker.CONFIG;
public class ConfigFileTypeHandler {
private static final Logger LOGGER = LogUtils.getLogger();
static ConfigFileTypeHandler TOML = new ConfigFileTypeHandler();
static final ConfigFileTypeHandler TOML = new ConfigFileTypeHandler();
private static final Path defaultConfigPath = FMLPaths.GAMEDIR.get().resolve(FMLConfig.getConfigValue(FMLConfig.ConfigValue.DEFAULT_CONFIG_PATH));
public Function<ModConfig, CommentedFileConfig> reader(Path configBasePath) {
@@ -47,12 +47,8 @@ public class ConfigFileTypeHandler {
throw new ConfigLoadingException(c, ex);
}
LOGGER.debug(CONFIG, "Loaded TOML config file {}", configPath.toString());
try {
FileWatcher.defaultInstance().addWatch(configPath, new ConfigWatcher(c, configData, Thread.currentThread().getContextClassLoader()));
LOGGER.debug(CONFIG, "Watching TOML config file {} for changes", configPath.toString());
} catch (IOException e) {
throw new RuntimeException("Couldn't watch config file", e);
}
FileWatcher.defaultInstance().addWatch(configPath, new ConfigWatcher(c, configData, Thread.currentThread().getContextClassLoader()));
LOGGER.debug(CONFIG, "Watching TOML config file {} for changes", configPath.toString());
return configData;
};
}
@@ -66,7 +62,7 @@ public class ConfigFileTypeHandler {
}
}
private boolean setupConfigFile(final ModConfig modConfig, final Path file, final ConfigFormat<?> conf) throws IOException {
private static boolean setupConfigFile(final ModConfig modConfig, final Path file, final ConfigFormat<?> conf) throws IOException {
if (!Files.isDirectory(file.getParent())) {
Files.createDirectories(file.getParent());
}
@@ -113,17 +109,11 @@ public class ConfigFileTypeHandler {
}
}
private static class ConfigWatcher implements Runnable {
private final ModConfig modConfig;
private final CommentedFileConfig commentedFileConfig;
private final ClassLoader realClassLoader;
ConfigWatcher(final ModConfig modConfig, final CommentedFileConfig commentedFileConfig, final ClassLoader classLoader) {
this.modConfig = modConfig;
this.commentedFileConfig = commentedFileConfig;
this.realClassLoader = classLoader;
}
private record ConfigWatcher(
ModConfig modConfig,
CommentedFileConfig commentedFileConfig,
ClassLoader realClassLoader
) implements Runnable {
@Override
public void run() {
// Force the regular classloader onto the special thread
@@ -47,15 +47,19 @@ public class ConfigTracker {
public void loadConfigs(ModConfig.Type type, Path configBasePath) {
LOGGER.debug(CONFIG, "Loading configs type {}", type);
this.configSets.get(type).forEach(config -> openConfig(config, configBasePath));
for (ModConfig config : this.configSets.get(type)) {
openConfig(config, configBasePath);
}
}
public void unloadConfigs(ModConfig.Type type, Path configBasePath) {
LOGGER.debug(CONFIG, "Unloading configs type {}", type);
this.configSets.get(type).forEach(config -> closeConfig(config, configBasePath));
for (ModConfig config : this.configSets.get(type)) {
closeConfig(config, configBasePath);
}
}
private void openConfig(final ModConfig config, final Path configBasePath) {
private static void openConfig(final ModConfig config, final Path configBasePath) {
LOGGER.trace(CONFIG, "Loading config file type {} at {} for {}", config.getType(), config.getFileName(), config.getModId());
final CommentedFileConfig configData = config.getHandler().reader(configBasePath).apply(config);
config.setConfigData(configData);
@@ -63,7 +67,7 @@ public class ConfigTracker {
config.save();
}
private void closeConfig(final ModConfig config, final Path configBasePath) {
private static void closeConfig(final ModConfig config, final Path configBasePath) {
if (config.getConfigData() != null) {
LOGGER.trace(CONFIG, "Closing config file type {} at {} for {}", config.getType(), config.getFileName(), config.getModId());
// stop the filewatcher before we save the file and close it, so reload doesn't fire
@@ -24,7 +24,6 @@ public class ModConfig
private final ModContainer container;
private final ConfigFileTypeHandler configHandler;
private CommentedConfig configData;
private Callable<Void> saveHandler;
public ModConfig(final Type type, final IConfigSpec<?> spec, final ModContainer container, final String fileName) {
this.type = type;
@@ -22,9 +22,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Desktop;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URI;
@@ -87,6 +85,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
private ScheduledFuture<?> initializationFuture;
private PerformanceInfo performanceInfo;
@SuppressWarnings("unused")
private ScheduledFuture<?> performanceTick;
// The GL ID of the window. Used for all operations
private long window;
@@ -259,7 +258,11 @@ public class DisplayWindow implements ImmediateWindowProvider {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
this.elements.removeIf(element -> !element.render(context, framecount));
for (var itr = this.elements.iterator(); itr.hasNext(); ) {
var element = itr.next();
if (!element.render(context, framecount))
itr.remove();
}
if (animationTimerTrigger.compareAndSet(true, false)) // we only increment the framecount on a periodic basis
framecount++;
}
@@ -302,13 +305,6 @@ public class DisplayWindow implements ImmediateWindowProvider {
}
private void crashElegantly(String errorDetails) {
String qrText;
try (var is = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream("/glfailure.txt")))) {
qrText = is.lines().collect(Collectors.joining("\n"));
} catch (IOException ioe) {
qrText = "";
}
StringBuilder msgBuilder = new StringBuilder(2000);
msgBuilder.append("Failed to initialize graphics window with current settings.\n");
msgBuilder.append("\n\n");
@@ -407,13 +403,13 @@ public class DisplayWindow implements ImmediateWindowProvider {
if (showHelpLog && versidx == 0) {
LOGGER.info("""
If this message is the only thing at the bottom of your log before a crash, you probably have a driver issue.
Possible solutions:
A) Make sure Minecraft is set to prefer high performance graphics in the OS and/or driver control panel
B) Check for driver updates on the graphics brand's website
C) Try reinstalling your graphics drivers
D) If still not working after trying all of the above, ask for further help on the Forge forums or Discord
You can safely ignore this message if the game starts up successfully.""");
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, GL_VERSIONS[versidx][0]); // we try our versions one at a time
@@ -497,10 +493,6 @@ public class DisplayWindow implements ImmediateWindowProvider {
glfwPollEvents();
}
private void badWindowHandler(final int code, final long desc) {
LOGGER.error("Got error from GLFW window init: "+code+ " "+MemoryUtil.memUTF8(desc));
}
private void winResize(long window, int width, int height) {
if (window == this.window && width != 0 && height != 0) {
this.winWidth = width;
@@ -634,7 +626,6 @@ public class DisplayWindow implements ImmediateWindowProvider {
public void addMojangTexture(final int textureId) {
this.elements.add(0, RenderElement.mojang(textureId, framecount));
// this.elements.get(0).retire(framecount + 1);
}
public void close() {
@@ -11,8 +11,6 @@ import org.lwjgl.stb.STBTTFontinfo;
import org.lwjgl.stb.STBTTPackContext;
import org.lwjgl.stb.STBTTPackRange;
import org.lwjgl.stb.STBTTPackedchar;
import org.lwjgl.system.MemoryStack;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
@@ -25,7 +23,7 @@ public class SimpleFont {
private final int lineSpacing;
private final int descent;
private final int GLYPH_COUNT = 127-32;
private Glyph[] glyphs;
private final Glyph[] glyphs;
private record Glyph(char c, int charwidth, int[] pos, float[] uv) {
Pos loadQuad(Pos pos, int colour, SimpleBufferBuilder bb) {
@@ -36,9 +36,9 @@ import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
*/
public class CapabilityTokenSubclass implements ILaunchPluginService {
private final String FUNC_NAME = "getType";
private final String FUNC_DESC = "()Ljava/lang/String;";
private final String CAP_INJECT = "net/minecraftforge/common/capabilities/CapabilityToken"; //Don't directly reference this to prevent class loading.
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.
@Override
public String name() {
@@ -49,49 +49,38 @@ public class CapabilityTokenSubclass implements ILaunchPluginService {
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
@Override
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty)
{
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
return isEmpty ? NAY : YAY;
}
@Override
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason)
{
if (CAP_INJECT.equals(classNode.name))
{
for (MethodNode mtd : classNode.methods)
{
if (FUNC_NAME.equals(mtd.name) && FUNC_DESC.equals(mtd.desc))
{
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason) {
if (CAP_INJECT.equals(classNode.name)) {
for (MethodNode mtd : classNode.methods) {
if (FUNC_NAME.equals(mtd.name) && FUNC_DESC.equals(mtd.desc)) {
mtd.access &= ~Opcodes.ACC_FINAL; // We have it final in code so people don't override it, cuz that'd be stupid, and make our transformer more complicated.
}
}
return ComputeFlags.SIMPLE_REWRITE;
}
else if (CAP_INJECT.equals(classNode.superName))
{
} else if (CAP_INJECT.equals(classNode.superName)) {
Holder cls = new Holder();
SignatureReader reader = new SignatureReader(classNode.signature); // Having a node version of this would probably be useful.
reader.accept(new SignatureVisitor(Opcodes.ASM9)
{
Deque<String> stack = new ArrayDeque<>();
reader.accept(new SignatureVisitor(Opcodes.ASM9) {
final Deque<String> stack = new ArrayDeque<>();
@Override
public void visitClassType(final String name)
{
public void visitClassType(final String name) {
stack.push(name);
}
@Override
public void visitInnerClassType(final String name)
{
public void visitInnerClassType(final String name) {
stack.push(stack.pop() + '$' + name);
}
@Override
public void visitEnd()
{
public void visitEnd() {
var val = stack.pop();
if (!stack.isEmpty() && CAP_INJECT.equals(stack.peek()))
cls.value = val;
@@ -106,15 +95,12 @@ public class CapabilityTokenSubclass implements ILaunchPluginService {
mtd.visitInsn(Opcodes.ARETURN);
mtd.visitEnd();
return ComputeFlags.COMPUTE_MAXS;
}
else
{
} else {
return ComputeFlags.NO_REWRITE;
}
}
private static class Holder {
private static final class Holder {
String value;
}
}
@@ -34,8 +34,9 @@ public class ObjectHolderDefinalize implements ILaunchPluginService {
new VanillaObjectHolderData("net.minecraft.world.effect.MobEffects", "mob_effect", "net.minecraft.world.effect.MobEffect"),
new VanillaObjectHolderData("net.minecraft.core.particles.ParticleTypes", "particle_type", "net.minecraft.core.particles.ParticleType"),
new VanillaObjectHolderData("net.minecraft.sounds.SoundEvents", "sound_event", "net.minecraft.sounds.SoundEvent")
).collect(Collectors.toMap(VanillaObjectHolderData::holderClass, Function.identity()));
private final String OBJECT_HOLDER = "Lnet/minecraftforge/registries/ObjectHolder;"; //Don't directly reference this to prevent class loading.
).collect(Collectors.toUnmodifiableMap(VanillaObjectHolderData::holderClass, Function.identity()));
private static final String OBJECT_HOLDER = "Lnet/minecraftforge/registries/ObjectHolder;"; //Don't directly reference this to prevent class loading.
private static final int PUBLIC_STATIC_FINAL_FLAGS = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL;
@Override
public String name() {
@@ -51,8 +52,7 @@ public class ObjectHolderDefinalize implements ILaunchPluginService {
return isEmpty ? NAY : YAY;
}
private boolean hasHolder(List<AnnotationNode> lst)
{
private static boolean hasHolder(List<AnnotationNode> lst) {
return lst != null && lst.stream().anyMatch(n -> n.desc.equals(OBJECT_HOLDER));
}
@@ -76,10 +76,9 @@ public class ObjectHolderDefinalize implements ILaunchPluginService {
{
final AtomicBoolean changes = new AtomicBoolean();
//Must be public static finals, and non-array objects
final int flags = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL;
//Fix Annotated Fields before injecting from class level
classNode.fields.stream().filter(f -> ((f.access & flags) == flags) && f.desc.startsWith("L") && hasHolder(f.visibleAnnotations)).forEach(f ->
classNode.fields.stream().filter(f -> ((f.access & PUBLIC_STATIC_FINAL_FLAGS) == PUBLIC_STATIC_FINAL_FLAGS) && f.desc.startsWith("L") && hasHolder(f.visibleAnnotations)).forEach(f ->
{
int prev = f.access;
f.access &= ~Opcodes.ACC_FINAL; //Strip final
@@ -89,7 +88,7 @@ public class ObjectHolderDefinalize implements ILaunchPluginService {
if (VANILLA_OBJECT_HOLDERS.containsKey(classType.getClassName())) //Class level, de-finalize all fields and add @ObjectHolder to them!
{
classNode.fields.stream().filter(f -> ((f.access & flags) == flags) && f.desc.startsWith("L")).forEach(f ->
classNode.fields.stream().filter(f -> ((f.access & PUBLIC_STATIC_FINAL_FLAGS) == PUBLIC_STATIC_FINAL_FLAGS) && f.desc.startsWith("L")).forEach(f ->
{
int prev = f.access;
f.access &= ~Opcodes.ACC_FINAL;
@@ -28,15 +28,16 @@ import org.slf4j.Logger;
public class RuntimeEnumExtender implements ILaunchPluginService {
private static final Logger LOGGER = LogUtils.getLogger();
private final Type STRING = Type.getType(String.class);
private final Type ENUM = Type.getType(Enum.class);
private final Type MARKER_IFACE = Type.getType("Lnet/minecraftforge/common/IExtensibleEnum;");
private final Type ARRAY_UTILS = Type.getType("Lorg/apache/commons/lang3/ArrayUtils;"); //Don't directly reference this to prevent class loading.
private final String ADD_DESC = Type.getMethodDescriptor(Type.getType(Object[].class), Type.getType(Object[].class), Type.getType(Object.class));
private final Type UNSAFE_HACKS = Type.getType("Lnet/minecraftforge/fml/unsafe/UnsafeHacks;"); //Again, not direct reference to prevent class loading.
private final String CLEAN_DESC = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Class.class));
private final String NAME_DESC = Type.getMethodDescriptor(STRING);
private final String EQUALS_DESC = Type.getMethodDescriptor(Type.BOOLEAN_TYPE, STRING);
private static final Type STRING = Type.getType(String.class);
private static final Type ENUM = Type.getType(Enum.class);
private static final Type MARKER_IFACE = Type.getType("Lnet/minecraftforge/common/IExtensibleEnum;");
private static final Type ARRAY_UTILS = Type.getType("Lorg/apache/commons/lang3/ArrayUtils;"); //Don't directly reference this to prevent class loading.
private static final String ADD_DESC = Type.getMethodDescriptor(Type.getType(Object[].class), Type.getType(Object[].class), Type.getType(Object.class));
private static final Type UNSAFE_HACKS = Type.getType("Lnet/minecraftforge/fml/unsafe/UnsafeHacks;"); //Again, not direct reference to prevent class loading.
private static final String CLEAN_DESC = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Class.class));
private static final String NAME_DESC = Type.getMethodDescriptor(STRING);
private static final String EQUALS_DESC = Type.getMethodDescriptor(Type.BOOLEAN_TYPE, STRING);
private static final int FLAGS = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC;
@Override
public String name() {
@@ -59,9 +60,9 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
return ComputeFlags.NO_REWRITE;
Type array = Type.getType("[" + classType.getDescriptor());
final int flags = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC;
String arrayDesc = array.getDescriptor();
FieldNode values = classNode.fields.stream().filter(f -> f.desc.contentEquals(array.getDescriptor()) && ((f.access & flags) == flags)).findFirst().orElse(null);
FieldNode values = classNode.fields.stream().filter(f -> f.desc.equals(arrayDesc) && ((f.access & FLAGS) == FLAGS)).findFirst().orElse(null);
if (!classNode.interfaces.contains(MARKER_IFACE.getInternalName())) {
return ComputeFlags.NO_REWRITE;
@@ -70,13 +71,13 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
//Static methods named "create" with first argument as a string
List<MethodNode> candidates = classNode.methods.stream()
.filter(m -> ((m.access & Opcodes.ACC_STATIC) != 0) && m.name.equals("create"))
.collect(Collectors.toList());
.toList();
if (candidates.isEmpty()) {
throw new IllegalStateException("IExtensibleEnum has no candidate factory methods: " + classType.getClassName());
}
candidates.forEach(mtd ->
for (var mtd : candidates)
{
Type[] args = Type.getArgumentTypes(mtd.desc);
if (args.length == 0 || !args[0].equals(STRING)) {
@@ -108,8 +109,7 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
Type[] ctrArgs = new Type[args.length + 1];
ctrArgs[0] = STRING;
ctrArgs[1] = Type.INT_TYPE;
for (int x = 1; x < args.length; x++)
ctrArgs[1 + x] = args[x];
System.arraycopy(args, 1, ctrArgs, 2, args.length - 1);
String desc = Type.getMethodDescriptor(Type.VOID_TYPE, ctrArgs);
@@ -231,7 +231,7 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
ins.load(vars, classType);
ins.areturn(classType);
}
});
}
return ComputeFlags.COMPUTE_FRAMES;
}
@@ -17,11 +17,9 @@ import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.loading.targets.CommonLaunchHandler;
import net.minecraftforge.forgespi.Environment;
import net.minecraftforge.forgespi.coremod.ICoreModProvider;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -36,7 +34,6 @@ import static net.minecraftforge.fml.loading.LogMarkers.SCAN;
public class FMLLoader {
private static final Logger LOGGER = LogUtils.getLogger();
private static AccessTransformerService accessTransformer;
private static ModDiscoverer modDiscoverer;
private static ICoreModProvider coreModProvider;
private static LanguageLoadingProvider languageLoadingProvider;
private static Dist dist;
@@ -62,7 +59,7 @@ public class FMLLoader {
/*eventBus =*/ getPlugin(env, "eventbus", "1.0", "EventBus");
runtimeDistCleaner = getPlugin(env, "runtimedistcleaner", "1.0", "RuntimeDistCleaner");
coreModProvider = getSingleService(ICoreModProvider.class, "CoreMod");
LOGGER.debug(CORE,"FML found CoreMod version : {}", JarVersionLookupHandler.getInfo(coreModProvider.getClass()).impl().version().orElse("MISSING"));
LOGGER.debug(CORE, "FML found CoreMod version : {}", JarVersionLookupHandler.getInfo(coreModProvider.getClass()).impl().version().orElse("MISSING"));
checkPackage(Environment.class, "2.0", "ForgeSPI");
try {
@@ -74,14 +71,15 @@ public class FMLLoader {
}
}
@SuppressWarnings("unchecked")
private static <T> T getPlugin(IEnvironment env, String id, String version, String name) throws IncompatibleEnvironmentException {
@SuppressWarnings("unchecked")
var plugin = (T)env.findLaunchPlugin(id).orElseThrow(() -> {
var plugin = env.findLaunchPlugin(id).orElse(null);
if (plugin == null) {
LOGGER.error(CORE, "{} library is missing, we need this to run", name);
return new IncompatibleEnvironmentException("Missing " + name + ", cannot run");
});
throw new IncompatibleEnvironmentException("Missing " + name + ", cannot run");
}
checkPackage(plugin.getClass(), version, name);
return plugin;
return (T) plugin;
}
private static void checkPackage(Class<?> cls, String version, String name) throws IncompatibleEnvironmentException {
@@ -137,20 +135,20 @@ public class FMLLoader {
}
commonLaunchHandler = (CommonLaunchHandler)launchHandler.get();
launchHandlerName = launchHandler.get().name();
gamePath = environment.getProperty(IEnvironment.Keys.GAMEDIR.get()).orElse(Paths.get(".").toAbsolutePath());
gamePath = environment.getProperty(IEnvironment.Keys.GAMEDIR.get()).orElse(Path.of(".").toAbsolutePath());
naming = commonLaunchHandler.getNaming();
dist = commonLaunchHandler.getDist();
production = commonLaunchHandler.isProduction();
accessTransformer.getExtension().accept(Pair.of(naming, "srg"));
accessTransformer.getExtension().accept(Map.entry(naming, "srg"));
runtimeDistCleaner.getExtension().accept(dist);
}
public static List<ITransformationService.Resource> beginModScan(final Map<String,?> arguments) {
LOGGER.debug(SCAN,"Scanning for Mod Locators");
modDiscoverer = new ModDiscoverer(arguments);
var modDiscoverer = new ModDiscoverer(arguments);
modValidator = modDiscoverer.discoverMods();
var pluginResources = modValidator.getPluginResources();
return List.of(pluginResources);
@@ -172,10 +170,6 @@ public class FMLLoader {
return languageLoadingProvider;
}
static ModDiscoverer getModDiscoverer() {
return modDiscoverer;
}
public static CommonLaunchHandler getLaunchHandler() {
return commonLaunchHandler;
}
@@ -24,7 +24,7 @@ import static net.minecraftforge.fml.loading.LogMarkers.CORE;
public class FMLServiceProvider implements ITransformationService {
private static final Logger LOGGER = LogUtils.getLogger();
private Map<String, Object> arguments;
private final Map<String, Object> arguments = new HashMap<>();
public FMLServiceProvider() {
var markers = System.getProperty("forge.logging.markers", "").split(",");
@@ -45,7 +45,6 @@ public class FMLServiceProvider implements ITransformationService {
FMLConfig.load();
LOGGER.debug(CORE, "Preparing ModFile");
environment.computePropertyIfAbsent(Environment.Keys.MODFILEFACTORY.get(), k->ModFile::new);
arguments = new HashMap<>();
LOGGER.debug(CORE, "Preparing launch handler");
FMLLoader.setupLaunchHandler(environment, arguments);
FMLEnvironment.setupInteropEnvironment(environment);
@@ -68,14 +67,6 @@ public class FMLServiceProvider implements ITransformationService {
FMLLoader.onInitialLoad(environment, otherServices);
}
@Override
public void arguments(BiFunction<String, String, OptionSpecBuilder> argumentBuilder) {
}
@Override
public void argumentValues(OptionResult option) {
}
@SuppressWarnings("rawtypes")
@Override
public @NotNull List<ITransformer> transformers() {
@@ -78,9 +78,13 @@ public class LoadingModList
public void addAccessTransformers()
{
modFiles.stream()
.map(ModFileInfo::getFile)
.forEach(mod -> mod.getAccessTransformer().ifPresent(path -> FMLLoader.addAccessTransformer(path, mod)));
for (ModFileInfo modFile : modFiles) {
ModFile mod = modFile.getFile();
var at = mod.getAccessTransformer().orElse(null);
if (at != null) {
FMLLoader.addAccessTransformer(at, mod);
}
}
}
public void addForScanning(BackgroundScanHandler backgroundScanHandler)
@@ -36,7 +36,7 @@ public class MCPNamingService implements INameMappingService {
@Override
public Map.Entry<String, String> understanding() {
return Pair.of("srg", "mcp");
return Map.entry("srg", "mcp");
}
@Override
@@ -34,7 +34,7 @@ public class MavenCoordinateResolver {
(!extension.isEmpty() ? "." + extension : ".jar");
String[] groups = groupId.split("\\.");
Path result = Paths.get(groups[0]);
Path result = Path.of(groups[0]);
for (int i = 1; i < groups.length; i++) {
result = result.resolve(groups[i]);
}
@@ -57,13 +57,13 @@ public class ModSorter
// Note this will never actually throw an error because the duplicate checks are done in ModDiscovererer before we get to this phase
// So all this is really doing is wasting time.
// But i'm leaving it here until I rewrite all of cpw's mod loading code because its such a clusterfuck.
return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf->(ModInfo)mf.getModInfos().get(0)).collect(toList()), e);
return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf->(ModInfo)mf.getModInfos().get(0)).toList(), e);
}
// try and validate dependencies
final List<ExceptionData> failedList = Stream.concat(ms.verifyDependencyVersions().stream(), errors.stream()).toList();
// if we miss one or the other, we abort now
if (!failedList.isEmpty()) {
return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf->(ModInfo)mf.getModInfos().get(0)).collect(toList()), new EarlyLoadingException("failure to validate mod list", null, failedList));
return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf->(ModInfo)mf.getModInfos().get(0)).toList(), new EarlyLoadingException("failure to validate mod list", null, failedList));
} else {
// Otherwise, lets try and sort the modlist and proceed
EarlyLoadingException earlyLoadingException = null;
@@ -89,9 +89,9 @@ public class ModSorter
infos.keySet().forEach(graph::addNode);
modFiles.stream()
.map(ModFile::getModInfos)
.<IModInfo>mapMulti(Iterable::forEach)
.flatMap(List::stream)
.map(IModInfo::getDependencies)
.<IModInfo.ModVersion>mapMulti(Iterable::forEach)
.<IModInfo.ModVersion>flatMap(List::stream)
.forEach(dep -> addDependency(graph, dep));
final List<ModFileInfo> sorted;
@@ -107,7 +107,7 @@ public class ModSorter
LOGGER.error(LOADING, "Mod Sorting failed.\nDetected Cycles: {}\n", cycles);
}
var dataList = cycles.stream()
.<ModFileInfo>mapMulti(Iterable::forEach)
.flatMap(Set::stream)
.<IModInfo>mapMulti((mf,c)->mf.getMods().forEach(c))
.map(IModInfo::getModId)
.map(list -> new ExceptionData("fml.modloading.cycle", list))
@@ -116,7 +116,7 @@ public class ModSorter
}
this.sortedList = sorted.stream()
.map(ModFileInfo::getMods)
.<IModInfo>mapMulti(Iterable::forEach)
.flatMap(List::stream)
.map(ModInfo.class::cast)
.collect(toList());
this.modFiles = sorted.stream()
@@ -126,12 +126,14 @@ public class ModSorter
private void addDependency(MutableGraph<ModFileInfo> topoGraph, IModInfo.ModVersion dep)
{
final ModFileInfo self = (ModFileInfo)dep.getOwner().getOwningFile();
final IModInfo targetModInfo = modIdNameLookup.get(dep.getModId());
// soft dep that doesn't exist. Just return. No edge required.
if (targetModInfo == null || !(targetModInfo.getOwningFile() instanceof final ModFileInfo target)) return;
final ModFileInfo self = (ModFileInfo)dep.getOwner().getOwningFile();
if (self == target)
return; // in case a jar has two mods that have dependencies between
switch (dep.getOrdering()) {
case BEFORE -> topoGraph.putEdge(self, target);
case AFTER -> topoGraph.putEdge(target, self);
@@ -165,7 +167,7 @@ public class ModSorter
var container = modFilesByFirstId.get(systemMod);
if (container != null && !container.isEmpty()) {
LOGGER.debug("Found system mod: {}", systemMod);
this.systemMods.add((ModFile) container.get(0));
this.systemMods.add(container.getFirst());
} else {
throw new IllegalStateException("Failed to find system mod: " + systemMod);
}
@@ -176,23 +178,23 @@ public class ModSorter
{
final var modVersions = modFiles.stream()
.map(ModFile::getModInfos)
.<IModInfo>mapMulti(Iterable::forEach)
.flatMap(List::stream)
.collect(toMap(IModInfo::getModId, IModInfo::getVersion));
final var modVersionDependencies = modFiles.stream()
.map(ModFile::getModInfos)
.<IModInfo>mapMulti(Iterable::forEach)
.flatMap(List::stream)
.collect(groupingBy(Function.identity(), flatMapping(e -> e.getDependencies().stream(), toList())));
final var modRequirements = modVersionDependencies.values().stream()
.<IModInfo.ModVersion>mapMulti(Iterable::forEach)
.<IModInfo.ModVersion>flatMap(List::stream)
.filter(mv -> mv.getSide().isCorrectSide())
.collect(toSet());
final long mandatoryRequired = modRequirements.stream().filter(IModInfo.ModVersion::isMandatory).count();
LOGGER.debug(LOADING, "Found {} mod requirements ({} mandatory, {} optional)", modRequirements.size(), mandatoryRequired, modRequirements.size() - mandatoryRequired);
final var missingVersions = modRequirements.stream()
.filter(mv -> (mv.isMandatory() || modVersions.containsKey(mv.getModId())) && this.modVersionNotContained(mv, modVersions))
.filter(mv -> (mv.isMandatory() || modVersions.containsKey(mv.getModId())) && !modVersionContained(mv, modVersions))
.collect(toSet());
final long mandatoryMissing = missingVersions.stream().filter(IModInfo.ModVersion::isMandatory).count();
LOGGER.debug(LOADING, "Found {} mod requirements missing ({} mandatory, {} optional)", missingVersions.size(), mandatoryMissing, missingVersions.size() - mandatoryMissing);
@@ -240,9 +242,13 @@ public class ModSorter
);
}
private boolean modVersionNotContained(final IModInfo.ModVersion mv, final Map<String, ArtifactVersion> modVersions)
{
return !(VersionSupportMatrix.testVersionSupportMatrix(mv.getVersionRange(), mv.getModId(), "mod", (modId, range) -> modVersions.containsKey(modId) &&
(range.containsVersion(modVersions.get(modId)) || modVersions.get(modId).toString().equals("0.0NONE"))));
private static boolean modVersionContained(IModInfo.ModVersion mv, Map<String, ArtifactVersion> modVersions) {
var modId = mv.getModId();
var range = mv.getVersionRange();
if (modVersions.containsKey(modId)
&& (range.containsVersion(modVersions.get(modId)) || modVersions.get(modId).toString().equals("0.0NONE")))
return true;
return VersionSupportMatrix.testVersionSupportMatrix(mv.getVersionRange(), mv.getModId(), "mod");
}
}
@@ -8,14 +8,8 @@ package net.minecraftforge.fml.loading;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import com.google.common.collect.Streams;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
import net.minecraftforge.api.distmarker.Dist;
@@ -28,200 +22,177 @@ import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;
import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
public class RuntimeDistCleaner implements ILaunchPluginService
{
public class RuntimeDistCleaner implements ILaunchPluginService {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Marker DISTXFORM = MarkerFactory.getMarker("DISTXFORM");
private static String DIST;
private static final String ONLYIN = Type.getDescriptor(OnlyIn.class);
private static final String ONLYINS = Type.getDescriptor(OnlyIns.class);
private static final String MOD = "Lnet/minecraftforge/fml/common/Mod;";
@Override
public String name()
{
public String name() {
return "runtimedistcleaner";
}
@Override
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason)
{
AtomicBoolean changes = new AtomicBoolean();
if (remove(classNode.visibleAnnotations, DIST))
{
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason) {
var changed = false;
var annotations = unpack(classNode.visibleAnnotations);
if (remove(annotations, DIST)) {
LOGGER.error(DISTXFORM, "Attempted to load class {} for invalid dist {}", classNode.name, DIST);
throw new RuntimeException("Attempted to load class "+ classNode.name + " for invalid dist "+ DIST);
}
if (!FMLEnvironment.production && hasOnlyInWithModAnnotation(classNode.visibleAnnotations))
{
if (!FMLEnvironment.production && !annotations.isEmpty() && hasModAnnotation(classNode.visibleAnnotations)) {
LOGGER.error(DISTXFORM, "Attempted to load class {} with @Mod and @OnlyIn/@OnlyIns annotations", classNode.name);
throw new RuntimeException("Found @OnlyIn on @Mod class "+ classNode.name + " - this is not allowed as it causes crashes. Remove the OnlyIn and consider setting clientSideOnly=true in the root of your mods.toml instead");
}
if (classNode.interfaces != null )
{
unpack(classNode.visibleAnnotations).stream()
.filter(ann->Objects.equals(ann.desc, ONLYIN))
.filter(ann-> ann.values.contains("_interface"))
.filter(ann->!Objects.equals(((String[])ann.values.get(ann.values.indexOf("value") + 1))[1], DIST))
.map(ann -> ((Type)ann.values.get(ann.values.indexOf("_interface") + 1)).getInternalName())
.forEach(intf -> {
if (classNode.interfaces.remove(intf)) {
LOGGER.debug(DISTXFORM,"Removing Interface: {} implements {}", classNode.name, intf);
changes.compareAndSet(false, true);
}
});
if (classNode.interfaces != null && !classNode.interfaces.isEmpty()) {
for (var ann : annotations) {
if (ann.intf == null || DIST.equals(ann.side))
continue;
if (classNode.interfaces.remove(ann.intf)) {
LOGGER.debug(DISTXFORM,"Removing Interface: {} implements {}", classNode.name, ann.intf);
changed = true;
}
}
//Remove Class level @OnlyIn/@OnlyIns annotations, this is important if anyone gets ambitious and tries to reflect an annotation with _interface set.
if (classNode.visibleAnnotations != null) {
Iterator<AnnotationNode> itr = classNode.visibleAnnotations.iterator();
while (itr.hasNext()) {
AnnotationNode ann = itr.next();
if (Objects.equals(ann.desc, ONLYIN) || Objects.equals(ann.desc, ONLYINS)) {
LOGGER.debug(DISTXFORM,"Removing Class Annotation: {} @{}", classNode.name, ann.desc);
for (var itr = classNode.visibleAnnotations.iterator(); itr.hasNext(); ) {
var ann = itr.next();
if (ONLYIN.equals(ann.desc) || ONLYINS.equals(ann.desc)) {
LOGGER.debug(DISTXFORM, "Removing Class Annotation: {} @{}", classNode.name, ann.desc);
itr.remove();
changes.compareAndSet(false, true);
changed = true;
}
}
}
}
Iterator<FieldNode> fields = classNode.fields.iterator();
while(fields.hasNext())
{
FieldNode field = fields.next();
if (remove(field.visibleAnnotations, DIST))
{
LOGGER.debug(DISTXFORM,"Removing field: {}.{}", classNode.name, field.name);
fields.remove();
changes.compareAndSet(false, true);
for (var itr = classNode.fields.iterator(); itr.hasNext(); ) {
var field = itr.next();
if (remove(unpack(field.visibleAnnotations), DIST)) {
LOGGER.debug(DISTXFORM, "Removing field: {}.{}", classNode.name, field.name);
itr.remove();
changed = true;
}
}
LambdaGatherer lambdaGatherer = new LambdaGatherer();
Iterator<MethodNode> methods = classNode.methods.iterator();
while(methods.hasNext())
{
MethodNode method = methods.next();
if (remove(method.visibleAnnotations, DIST))
{
LOGGER.debug(DISTXFORM,"Removing method: {}.{}{}", classNode.name, method.name, method.desc);
methods.remove();
var lambdaGatherer = new LambdaGatherer();
for (var itr = classNode.methods.iterator(); itr.hasNext(); ) {
var method = itr.next();
if (remove(unpack(method.visibleAnnotations), DIST)) {
LOGGER.debug(DISTXFORM, "Removing method: {}.{}{}", classNode.name, method.name, method.desc);
itr.remove();
lambdaGatherer.accept(method);
changes.compareAndSet(false, true);
changed = true;
}
}
// remove dynamic synthetic lambda methods that are inside of removed methods
for (List<Handle> dynamicLambdaHandles = lambdaGatherer.getDynamicLambdaHandles();
!dynamicLambdaHandles.isEmpty(); dynamicLambdaHandles = lambdaGatherer.getDynamicLambdaHandles())
{
for (List<Handle> handles = lambdaGatherer.getDynamicLambdaHandles();
!handles.isEmpty(); handles = lambdaGatherer.getDynamicLambdaHandles()) {
lambdaGatherer = new LambdaGatherer();
methods = classNode.methods.iterator();
while (methods.hasNext())
{
MethodNode method = methods.next();
if ((method.access & Opcodes.ACC_SYNTHETIC) == 0) continue;
for (Handle dynamicLambdaHandle : dynamicLambdaHandles)
{
if (method.name.equals(dynamicLambdaHandle.getName()) && method.desc.equals(dynamicLambdaHandle.getDesc()))
{
LOGGER.debug(DISTXFORM,"Removing lambda method: {}.{}{}", classNode.name, method.name, method.desc);
methods.remove();
for (var itr = classNode.methods.iterator(); itr.hasNext(); ) {
MethodNode method = itr.next();
if ((method.access & Opcodes.ACC_SYNTHETIC) == 0)
continue;
for (var handle : handles) {
if (method.name.equals(handle.getName()) && method.desc.equals(handle.getDesc())) {
LOGGER.debug(DISTXFORM, "Removing lambda method: {}.{}{}", classNode.name, method.name, method.desc);
itr.remove();
lambdaGatherer.accept(method);
changes.compareAndSet(false, true);
changed = true;
}
}
}
}
return changes.get() ? ComputeFlags.SIMPLE_REWRITE : ComputeFlags.NO_REWRITE;
return changed ? ComputeFlags.SIMPLE_REWRITE : ComputeFlags.NO_REWRITE;
}
private static record Target(String side, String intf) {
static Target from(final AnnotationNode node) {
var idx = node.values.indexOf("value");
var value = (String[])node.values.get(idx + 1); // Enums are stored as [Type, Value]
idx = node.values.indexOf("_interface");
if (idx == -1)
return new Target(value[1], null);
var intf = (Type)node.values.get(idx + 1);
return new Target(value[1], intf.getInternalName());
}
}
/**
* Unpack any OnlyIn or OnlyIns annotations to Target objects.
*/
@SuppressWarnings("unchecked")
private static List<AnnotationNode> unpack(final List<AnnotationNode> anns)
{
if (anns == null)
{
private static List<Target> unpack(final List<AnnotationNode> anns) {
if (anns == null || anns.isEmpty())
return Collections.emptyList();
}
List<AnnotationNode> unpacked = new ArrayList<>();
var ret = new ArrayList<Target>();
for (var node : anns) {
if (ONLYIN.equals(node.desc))
ret.add(Target.from(node));
else if (ONLYINS.equals(node.desc)) {
if (node.values == null || node.values.isEmpty())
continue;
for (var annotationNode : anns)
{
if (Objects.equals(annotationNode.desc, ONLYINS))
{
unpacked.add(annotationNode);
int idx = node.values.indexOf("value");
if (idx == -1)
continue;
if (annotationNode.values != null)
{
List<AnnotationNode> subNodes = (List<AnnotationNode>) annotationNode.values.get(annotationNode.values.indexOf("value") + 1);
if (subNodes != null)
{
unpacked.addAll(subNodes);
}
var subNodes = (List<AnnotationNode>)node.values.get(idx + 1);
if (subNodes != null) {
for (var sub : subNodes)
ret.add(Target.from(sub));
}
}
}
return unpacked;
return ret;
}
private boolean remove(final List<AnnotationNode> anns, final String side)
{
var onlyIns = unpack(anns);
for (var onlyIn : onlyIns)
{
if (!Objects.equals(onlyIn.desc, ONLYINS) || onlyIn.values.contains("_interface"))
{
continue;
}
if (!Objects.equals(((String[])onlyIn.values.get(onlyIn.values.indexOf("value") + 1))[1], side))
{
private static boolean remove(final List<Target> targets, final String side) {
for (var target : targets) {
if (target.intf == null && !side.equals(target.side))
return true;
}
}
return false;
}
private boolean hasOnlyInWithModAnnotation(final List<AnnotationNode> anns)
{
if (anns == null)
{
private static boolean hasModAnnotation(final List<AnnotationNode> anns) {
if (anns == null || anns.isEmpty())
return false;
for (var ann : anns) {
if (ann.desc.equals(MOD))
return true;
}
var foundModAnnotation = false;
var foundOnlyIn = false;
for (var ann : anns)
{
if (ann.desc.equals("Lnet/minecraftforge/fml/common/Mod;"))
{
foundModAnnotation = true;
} else if (Objects.equals(ann.desc, ONLYIN) || Objects.equals(ann.desc, ONLYINS))
{
foundOnlyIn = true;
}
}
return foundModAnnotation && foundOnlyIn;
return false;
}
@SuppressWarnings("unchecked")
@Override
public Consumer<Dist> getExtension()
{
return (s)-> {
public Consumer<Dist> getExtension() {
return s -> {
DIST = s.name();
LOGGER.debug(DISTXFORM, "Configuring for Dist {}", DIST);
};
@@ -231,8 +202,7 @@ public class RuntimeDistCleaner implements ILaunchPluginService
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
@Override
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty)
{
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
return isEmpty ? NAY : YAY;
}
@@ -248,23 +218,21 @@ public class RuntimeDistCleaner implements ILaunchPluginService
}
public void accept(MethodNode method) {
Streams.stream(method.instructions.iterator()).
filter(insnNode->insnNode.getType() == AbstractInsnNode.INVOKE_DYNAMIC_INSN).
forEach(insnNode->insnNode.accept(this));
}
@Override
public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs)
{
if (META_FACTORY.equals(bsm))
{
Handle dynamicLambdaHandle = (Handle) bsmArgs[1];
dynamicLambdaHandles.add(dynamicLambdaHandle);
for (var insn : method.instructions) {
if (insn.getType() == AbstractInsnNode.INVOKE_DYNAMIC_INSN)
insn.accept(this);
}
}
public List<Handle> getDynamicLambdaHandles()
{
@Override
public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
if (META_FACTORY.equals(bsm)) {
var handle = (Handle)bsmArgs[1];
dynamicLambdaHandles.add(handle);
}
}
public List<Handle> getDynamicLambdaHandles() {
return dynamicLambdaHandles;
}
}
@@ -14,16 +14,17 @@ import org.apache.commons.lang3.text.StrSubstitutor;
@SuppressWarnings("deprecation")
public class StringSubstitutor {
private static final Map<String, String> GLOBALS = Map.of(
"mcVersion", FMLLoader.versionInfo().mcVersion(),
"forgeVersion", FMLLoader.versionInfo().forgeVersion()
);
public static String replace(final String in, final ModFile file) {
return new StrSubstitutor(getStringLookup(file)).replace(in);
}
private static StrLookup<String> getStringLookup(final ModFile file) {
var globals = Map.of(
"mcVersion", FMLLoader.versionInfo().mcVersion(),
"forgeVersion", FMLLoader.versionInfo().forgeVersion()
);
return new StrLookup<String>() {
return new StrLookup<>() {
@Override
public String lookup(String key) {
var parts = key.split("\\.");
@@ -32,7 +33,7 @@ public class StringSubstitutor {
var pfx = parts[0];
if ("global".equals(pfx))
return globals.get(parts[1]);
return GLOBALS.get(parts[1]);
else if ("file".equals(pfx) && file != null)
return String.valueOf(file.getSubstitutionMap().get().get(parts[1]));
@@ -47,12 +47,12 @@ public class UniqueModListBuilder
// Select the newest by artifact version sorting of non-unique files thus identified
uniqueModList = modFilesByFirstId.entrySet().stream()
.map(this::selectNewestModInfo)
.map(UniqueModListBuilder::selectNewestModInfo)
.toList();
// Select the newest by artifact version sorting of non-unique files thus identified
uniqueLibListWithVersion = libFilesWithVersionByModuleName.entrySet().stream()
.map(this::selectNewestModInfo)
.map(UniqueModListBuilder::selectNewestModInfo)
.toList();
// Transform to the full mod id list
@@ -75,7 +75,7 @@ public class UniqueModListBuilder
final List<String> dupedModErrors = modIds.values().stream()
.filter(modInfos -> modInfos.size() > 1)
.map(mods -> String.format("\tMod ID: '%s' from mod files: %s",
mods.get(0).getModId(),
mods.getFirst().getModId(),
mods.stream()
.map(modInfo -> modInfo.getOwningFile().getFile().getFileName()).collect(joining(", "))
)).toList();
@@ -91,9 +91,8 @@ public class UniqueModListBuilder
final List<String> dupedLibErrors = versionedLibIds.values().stream()
.filter(modFiles -> modFiles.size() > 1)
.map(mods -> String.format("\tLibrary: '%s' from files: %s",
getModId(mods.get(0)),
mods.stream()
.map(modFile -> modFile.getFileName()).collect(joining(", "))
getModId(mods.getFirst()),
mods.stream().map(ModFile::getFileName).collect(joining(", "))
)).toList();
if (!dupedLibErrors.isEmpty()) {
@@ -107,25 +106,23 @@ public class UniqueModListBuilder
final Map<String, List<ModFile>> uniqueModFilesByFirstId = uniqueModList.stream()
.collect(groupingBy(UniqueModListBuilder::getModId));
final List<ModFile> loadedList = new ArrayList<>();
loadedList.addAll(uniqueModList);
final List<ModFile> loadedList = new ArrayList<>(uniqueModList);
loadedList.addAll(uniqueLibListWithVersion);
return new UniqueModListData(loadedList, uniqueModFilesByFirstId);
}
private ModFile selectNewestModInfo(Map.Entry<String, List<ModFile>> fullList) {
private static ModFile selectNewestModInfo(Map.Entry<String, List<ModFile>> fullList) {
List<ModFile> modInfoList = fullList.getValue();
if (modInfoList.size() > 1) {
LOGGER.debug("Found {} mods for first modid {}, selecting most recent based on version data", modInfoList.size(), fullList.getKey());
modInfoList.sort(Comparator.comparing(this::getVersion).reversed());
LOGGER.debug("Selected file {} for modid {} with version {}", modInfoList.get(0).getFileName(), fullList.getKey(), this.getVersion(modInfoList.get(0)));
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);
}
private ArtifactVersion getVersion(final ModFile mf)
{
private static ArtifactVersion getVersion(final ModFile mf) {
if (mf.getModFileInfo() == null || mf.getModInfos() == null || mf.getModInfos().isEmpty()) {
return mf.getJarVersion();
}
@@ -5,33 +5,53 @@
package net.minecraftforge.fml.loading;
import net.minecraftforge.forgespi.language.MavenVersionAdapter;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.VersionRange;
import org.jetbrains.annotations.ApiStatus;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.BiPredicate;
@ApiStatus.Internal // since 1.21.1, will be made non-public in a later MC version
public class VersionSupportMatrix {
private static final HashMap<String, List<ArtifactVersion>> overrideVersions = new HashMap<>();
private static final Map<String, List<ArtifactVersion>> OVERRIDE_VERSIONS;
static {
final ArtifactVersion version = new DefaultArtifactVersion(FMLLoader.versionInfo().mcVersion());
if (MavenVersionAdapter.createFromVersionSpec("[1.19.2]").containsVersion(version)) {
// 1.19.2 is Compatible with 1.19.1
add("languageloader.javafml", "42");
add("mod.minecraft", "1.19.1");
add("mod.forge", "42.0.9");
if ("1.21.1".equals(FMLLoader.versionInfo().mcVersion())) {
OVERRIDE_VERSIONS = Map.ofEntries(
// 1.21.1 is compatible with 1.21
entry("languageloader.javafml", "51"),
entry("mod.minecraft", "1.21"),
entry("mod.forge", "51.0.33")
);
} else {
OVERRIDE_VERSIONS = Collections.emptyMap();
}
}
private static void add(String key, String value) {
overrideVersions.computeIfAbsent(key, k -> new ArrayList<>()).add(new DefaultArtifactVersion(value));
}
public static <T> boolean testVersionSupportMatrix(VersionRange declaredRange, String lookupId, String type, BiPredicate<String, VersionRange> standardLookup) {
/**
* @deprecated Use {@link #testVersionSupportMatrix(VersionRange, String, String)} instead, unwrapping your BiPredicate.
*/
@Deprecated(forRemoval = true, since = "1.21.1")
public static boolean testVersionSupportMatrix(VersionRange declaredRange, String lookupId, String type, BiPredicate<String, VersionRange> standardLookup) {
if (standardLookup.test(lookupId, declaredRange)) return true;
List<ArtifactVersion> custom = overrideVersions.get(type + "." + lookupId);
return custom == null ? false : custom.stream().anyMatch(declaredRange::containsVersion);
return testVersionSupportMatrix(declaredRange, lookupId, type);
}
public static boolean testVersionSupportMatrix(VersionRange declaredRange, String lookupId, String type) {
if (OVERRIDE_VERSIONS.isEmpty()) return false;
List<ArtifactVersion> custom = OVERRIDE_VERSIONS.get(type + "." + lookupId);
return custom != null && custom.stream().anyMatch(declaredRange::containsVersion);
}
private static Map.Entry<String, List<ArtifactVersion>> entry(String typeAndLookupId, String declaredRange) {
return Map.entry(typeAndLookupId, List.of(new DefaultArtifactVersion(declaredRange)));
}
private static Map.Entry<String, List<ArtifactVersion>> entry(String typeAndLookupId, List<String> declaredRanges) {
return Map.entry(typeAndLookupId, declaredRanges.stream().map(DefaultArtifactVersion::new).map(it -> (ArtifactVersion) it).toList());
}
}
@@ -49,10 +49,7 @@ public class ForgeHighlight {
LOGGER.warn("Failed to invoke initializeTerminal on TCA", e);
}
if (!TerminalConsoleAppender.isAnsiSupported() && Arrays.stream(options).noneMatch(s -> s.equals("disableAnsi=true"))) {
List<String> optionList = new ArrayList<>();
optionList.add(options[0]);
optionList.add("disableAnsi=true");
options = optionList.toArray(new String[0]);
options = new String[] { options[0], "disableAnsi=true" };
}
return HighlightConverter.newInstance(config, options);
}
@@ -42,6 +42,11 @@ public abstract class AbstractModProvider implements IModProvider {
@Nullable
protected IModLocator.ModFileOrException createMod(Path path, boolean ignoreUnknown) {
return createMod(path, ignoreUnknown, getDefaultJarModType());
}
@Nullable
protected IModLocator.ModFileOrException createMod(Path path, boolean ignoreUnknown, String defaultType) {
var mjm = new ModJarMetadata();
var sj = SecureJar.from(
jar -> jar.moduleDataProvider().findFile(MODS_TOML).isPresent() ? mjm : JarMetadata.from(jar, path),
@@ -51,7 +56,7 @@ public abstract class AbstractModProvider implements IModProvider {
IModFile mod;
var type = sj.moduleDataProvider().getManifest().getMainAttributes().getValue(ModFile.TYPE);
if (type == null)
type = getDefaultJarModType();
type = defaultType;
if (sj.moduleDataProvider().findFile(MODS_TOML).isPresent()) {
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MODS_TOML, type, path);
@@ -76,6 +81,11 @@ public abstract class AbstractModProvider implements IModProvider {
var mf = mod.getSecureJar().moduleDataProvider().getManifest().getMainAttributes();
var license = mf.getValue("LICENSE");
var dummy = new IConfigurable() {
@Override
public <T> Optional<T> getConfigElement(String key) {
return Optional.empty();
}
@Override
public <T> Optional<T> getConfigElement(String... key) {
return Optional.empty();
@@ -135,10 +145,11 @@ public abstract class AbstractModProvider implements IModProvider {
}
private static final class Holder<T> {
T value;
private T value;
}
private record DefaultModFileInfo(IModFile mod, String license, IConfigurable configurable) implements IModFileInfo, IConfigurable {
@Override public <T> Optional<T> getConfigElement(final String string) { return Optional.empty(); }
@Override public <T> Optional<T> getConfigElement(final String... strings) { return Optional.empty(); }
@Override public List<? extends IConfigurable> getConfigList(final String... strings) { return null; }
@Override public List<IModInfo> getMods() { return Collections.emptyList(); }
@@ -29,6 +29,7 @@ public class BackgroundScanHandler
}
private static final Logger LOGGER = LogUtils.getLogger();
private static final boolean DEBUG = LOGGER.isErrorEnabled(LogMarkers.SCAN);
private final ExecutorService modContentScanner;
private final List<ModFile> modFiles;
private ScanStatus status;
@@ -55,16 +56,16 @@ public class BackgroundScanHandler
}
status = ScanStatus.RUNNING;
ImmediateWindowHandler.updateProgress("Scanning mod candidates");
final CompletableFuture<ModFileScanData> future = CompletableFuture.supplyAsync(file::compileContent, modContentScanner)
.whenComplete(file::setScanResult)
.whenComplete((r,t)-> this.addCompletedFile(file,r,t));
CompletableFuture<ModFileScanData> future = CompletableFuture.supplyAsync(file::compileContent, modContentScanner)
.whenComplete(file::setScanResult);
if (DEBUG) future = future.whenComplete((r, t) -> addCompletedFile(file, t));
file.setFutureScanResult(future);
}
private void addCompletedFile(final ModFile file, final ModFileScanData modFileScanData, final Throwable throwable) {
private void addCompletedFile(final ModFile file, final Throwable throwable) {
if (throwable != null) {
status = ScanStatus.ERRORED;
LOGGER.error(LogMarkers.SCAN,"An error occurred scanning file {}", file, throwable);
LOGGER.error(LogMarkers.SCAN, "An error occurred scanning file {}", file, throwable);
}
}
@@ -18,9 +18,9 @@ import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.jar.Attributes;
@@ -28,7 +28,7 @@ import java.util.jar.JarFile;
import java.util.jar.Manifest;
@ApiStatus.Internal
public class ClasspathLocator extends AbstractModProvider implements IModLocator {
public final class ClasspathLocator extends AbstractModProvider implements IModLocator {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Attributes.Name MOD_TYPE = new Attributes.Name("FMLModType");
@@ -80,11 +80,11 @@ public class ClasspathLocator extends AbstractModProvider implements IModLocator
return ret;
}
private List<URL> getUrls(ClassLoader cl, String resource) {
private static List<URL> getUrls(ClassLoader cl, String resource) {
try {
var lst = Collections.list(cl.getResources(resource));
if (LOGGER.isDebugEnabled(LogMarkers.SCAN)) {
Collections.sort(lst, (a, b) -> a.toString().compareTo(b.toString()));
lst.sort(Comparator.comparing(URL::toString));
LOGGER.debug(LogMarkers.SCAN, "Scanning Classloader: {} for {}", cl, resource);
for (var url : lst)
LOGGER.debug(LogMarkers.SCAN, "\t{}", url);
@@ -104,8 +104,7 @@ public class ClasspathLocator extends AbstractModProvider implements IModLocator
len += 2;
}
str = str.substring(0, str.length() - len);
var path = Paths.get(URI.create(str));
return path;
return Path.of(URI.create(str));
}
private static Path getPathFromResource(ClassLoader cl, String resource) {
@@ -15,7 +15,7 @@ import java.nio.file.Path;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Internal
public class CoreModFile implements ICoreModFile {
public final class CoreModFile implements ICoreModFile {
private final Path internalPath;
private final ModFile file;
private final String name;
@@ -6,7 +6,6 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.EarlyLoadingException;
import net.minecraftforge.forgespi.language.IModInfo;
@@ -26,6 +25,7 @@ import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -51,7 +51,7 @@ public class JarInJarDependencyLocator extends AbstractModProvider implements ID
@Override
public List<IModFile> scanMods(Iterable<IModFile> loadedMods) {
final List<IModFile> sources = Lists.newArrayList();
final List<IModFile> sources = new ArrayList<>();
loadedMods.forEach(sources::add);
var dependenciesToLoad = JarSelector.detectAndSelect(
@@ -83,7 +83,14 @@ public class JarInJarDependencyLocator extends AbstractModProvider implements ID
final Map<String, ?> outerFsArgs = ImmutableMap.of("packagePath", pathInModFile);
final FileSystem zipFS = FileSystems.newFileSystem(filePathUri, outerFsArgs);
final Path pathInFS = zipFS.getPath("/");
return Optional.of(createMod(pathInFS).file());
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());
@@ -95,34 +102,34 @@ public class JarInJarDependencyLocator extends AbstractModProvider implements ID
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(this::buildExceptionData)
.map(JarInJarDependencyLocator::buildExceptionData)
.toList();
return new EarlyLoadingException(failedDependencies.size() + " Dependency restrictions were not met.", null, errors);
}
@NotNull
private EarlyLoadingException.ExceptionData buildExceptionData(JarSelector.ResolutionFailureInformation<IModFile> entry) {
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(this::getModWithVersionRangeStream)
.map(this::formatError)
.flatMap(JarInJarDependencyLocator::getModWithVersionRangeStream)
.map(JarInJarDependencyLocator::formatError)
.collect(Collectors.joining(", "))
);
}
@NotNull
private String getErrorTranslationKey(JarSelector.ResolutionFailureInformation<IModFile> entry) {
private static String getErrorTranslationKey(JarSelector.ResolutionFailureInformation<IModFile> entry) {
return entry.failureReason() == JarSelector.FailureReason.VERSION_RESOLUTION_FAILED ?
"fml.dependencyloading.conflictingdependencies" :
"fml.dependencyloading.mismatchedcontaineddependencies";
}
@NotNull
private Stream<ModWithVersionRange> getModWithVersionRangeStream(JarSelector.SourceWithRequestedVersionRange<IModFile> file) {
private static Stream<ModWithVersionRange> getModWithVersionRangeStream(JarSelector.SourceWithRequestedVersionRange<IModFile> file) {
return file.sources()
.stream()
.map(IModFile::getModFileInfo)
@@ -145,7 +152,7 @@ public class JarInJarDependencyLocator extends AbstractModProvider implements ID
}
@NotNull
private String formatError(ModWithVersionRange modWithVersionRange){
private static String formatError(ModWithVersionRange modWithVersionRange){
return YELLOW + modWithVersionRange.modInfo().getModId() + RESET + " - " +
RED + modWithVersionRange.versionRange().toString() + RESET + " - " +
GREEN + modWithVersionRange.artifactVersion().toString() + RESET;
@@ -18,7 +18,7 @@ import java.util.List;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Internal
public class MinecraftLocator extends AbstractModProvider implements IModLocator {
public final class MinecraftLocator extends AbstractModProvider implements IModLocator {
@Override
public List<IModLocator.ModFileOrException> scanMods() {
var minecraft = FMLLoader.getLaunchHandler().getMinecraftPaths();
@@ -29,13 +29,13 @@ public class MinecraftLocator extends AbstractModProvider implements IModLocator
jar -> meta,
minecraft.toArray(Path[]::new)
);
var mc = ModFileFactory.FACTORY.build(mcjar, this, this::buildMinecraftTOML);
var mc = ModFileFactory.FACTORY.build(mcjar, this, MinecraftLocator::buildMinecraftTOML);
meta.setModFile(mc);
return List.of(new ModFileOrException(mc, null));
}
private IModFileInfo buildMinecraftTOML(final IModFile iModFile) {
private static IModFileInfo buildMinecraftTOML(final IModFile iModFile) {
// We haven't changed this in years, and I can't be asked right now to special case this one file in the path.
final var conf = Config.inMemory();
conf.set("modLoader", "minecraft");
@@ -52,7 +52,7 @@ public class MinecraftLocator extends AbstractModProvider implements IModLocator
conf.set("mods", List.of(mods));
var configWrapper = new NightConfigWrapper(conf);
return new ModFileInfo((ModFile)iModFile, configWrapper, configWrapper::setFile, List.of());
return new ModFileInfo((ModFile) iModFile, configWrapper, configWrapper::setFile);
}
@Override
@@ -6,7 +6,6 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.Launcher;
import cpw.mods.modlauncher.api.IModuleLayerManager;
@@ -28,6 +27,7 @@ import org.jetbrains.annotations.ApiStatus;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -49,17 +49,19 @@ public class ModDiscoverer {
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());
modLocatorList.forEach(l->l.initArguments(arguments));
for (IModLocator iModLocator : modLocatorList) {
iModLocator.initArguments(arguments);
}
dependencyLocatorList = ServiceLoaderUtils.streamServiceLoader(()-> dependencyLocators, sce->LOGGER.error("Failed to load dependency locator list", sce)).collect(Collectors.toList());
dependencyLocatorList.forEach(l->l.initArguments(arguments));
for (IDependencyLocator l : dependencyLocatorList) {
l.initArguments(arguments);
}
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(",")));
}
if (LOGGER.isDebugEnabled(LogMarkers.CORE))
{
LOGGER.debug(LogMarkers.CORE, "Found Dependency Locators : {}", dependencyLocatorList.stream()
.map(dependencyLocator -> "(%s:%s)".formatted(dependencyLocator.name(),
dependencyLocator.getClass().getPackage().getImplementationVersion())).collect(Collectors.joining(",")));
@@ -126,7 +128,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 = Maps.newHashMap();
Map<IModFile.Type, List<ModFile>> modFilesMap = new EnumMap<>(IModFile.Type.class);
try {
final UniqueModListBuilder modsUniqueListBuilder = new UniqueModListBuilder(loadedFiles);
final UniqueModListBuilder.UniqueModListData uniqueModsData = modsUniqueListBuilder.buildUniqueList();
@@ -134,7 +136,7 @@ public class ModDiscoverer {
//Grab the temporary results.
//This allows loading to continue to a base state, in case dependency loading fails.
modFilesMap = uniqueModsData.modFiles().stream()
.collect(Collectors.groupingBy(IModFile::getType));
.collect(Collectors.groupingBy(IModFile::getType, () -> new EnumMap<>(IModFile.Type.class), Collectors.toList()));
loadedFiles = uniqueModsData.modFiles();
}
catch (EarlyLoadingException exception) {
@@ -171,11 +173,11 @@ public class ModDiscoverer {
//We now only need the mod files map, not the list.
modFilesMap = uniqueModsAndDependenciesData.modFiles().stream()
.collect(Collectors.groupingBy(IModFile::getType));
.collect(Collectors.groupingBy(IModFile::getType, () -> new EnumMap<>(IModFile.Type.class), Collectors.toList()));
} catch (EarlyLoadingException exception) {
LOGGER.error(LogMarkers.SCAN, "Failed to build unique mod list after dependency discovery.", exception);
discoveryErrorData.addAll(exception.getAllData());
modFilesMap = loadedFiles.stream().collect(Collectors.groupingBy(IModFile::getType));
modFilesMap = loadedFiles.stream().collect(Collectors.groupingBy(IModFile::getType, () -> new EnumMap<>(IModFile.Type.class), Collectors.toList()));
}
}
else {
@@ -190,8 +192,7 @@ public class ModDiscoverer {
return validator;
}
private void handleLocatedFiles(final List<ModFile> loadedFiles, final List<IModFile> locatedFiles)
{
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());
@@ -45,7 +45,6 @@ public class ModFile implements IModFile {
private Throwable scanError;
private final SecureJar jar;
private final Type modFileType;
private final Manifest manifest;
private final IModProvider provider;
private IModFileInfo modFileInfo;
private ModFileScanData fileModFileScanData;
@@ -65,7 +64,7 @@ public class ModFile implements IModFile {
this.jar = jar;
this.parser = parser;
manifest = this.jar.moduleDataProvider().getManifest();
var manifest = this.jar.moduleDataProvider().getManifest();
modFileType = Type.valueOf(type);
jarVersion = Optional.ofNullable(manifest.getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION)).orElse("0.0NONE");
this.modFileInfo = ModFileParser.readModList(this, this.parser);
@@ -7,7 +7,6 @@ package net.minecraftforge.fml.loading.moddiscovery;
import com.google.common.base.Strings;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.fml.loading.StringUtils;
import net.minecraftforge.forgespi.language.IConfigurable;
@@ -18,17 +17,15 @@ import org.jetbrains.annotations.ApiStatus;
import org.slf4j.Logger;
import javax.security.auth.x500.X500Principal;
import java.net.URL;
import java.security.CodeSigner;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
@@ -37,10 +34,8 @@ import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ModFileInfo implements IModFileInfo, IConfigurable
{
public class ModFileInfo implements IModFileInfo, IConfigurable {
private static final Logger LOGGER = LogUtils.getLogger();
private static final String MAYBE_NOT_JAVAFML_VER = "[2,)";
private static final String JAVAFML = "javafml";
@@ -60,8 +55,7 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
private final String license;
private final List<String> usesServices;
ModFileInfo(final ModFile modFile, final IConfigurable config, Consumer<IModFileInfo> configFileConsumer)
{
ModFileInfo(final ModFile modFile, final IConfigurable config, Consumer<IModFileInfo> configFileConsumer) {
this.modFile = modFile;
this.config = config;
configFileConsumer.accept(this);
@@ -86,10 +80,6 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
.orElse(false);
this.clientSideOnly = config.<Boolean>getConfigElement("clientSideOnly")
.orElse(false);
if (this.clientSideOnly) {
// if clientSideOnly, then this is definitely a Forge mod
maybeNotAForgeMod = false;
}
this.showAsDataPack = config.<Boolean>getConfigElement("showAsDataPack")
.orElse(false);
this.usesServices = config.<List<String>>getConfigElement("services")
@@ -98,6 +88,10 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
.map(StringUtils::toURL)
.orElse(null);
// if clientSideOnly, then this is definitely a Forge mod
if (this.clientSideOnly)
maybeNotAForgeMod = false;
if (maybeNotAForgeMod) {
// mark this file as not a Forge mod. Note: this marker may be removed later based on the mod dependencies
this.properties = config.<Map<String, Object>>getConfigElement("properties")
@@ -110,15 +104,14 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
this.modFile.setFileProperties(this.properties);
final List<? extends IConfigurable> modConfigs = config.getConfigList("mods");
if (modConfigs.isEmpty())
{
if (modConfigs == null || modConfigs.isEmpty())
throw new InvalidModFileException("Missing mods list", this);
}
this.mods = modConfigs.stream()
.map(mi-> (IModInfo)new ModInfo(this, mi))
.map(mi -> (IModInfo) ModInfo.of(this, mi))
.toList();
if (LOGGER.isDebugEnabled(LogMarkers.LOADING))
{
if (LOGGER.isDebugEnabled(LogMarkers.LOADING)) {
LOGGER.debug(LogMarkers.LOADING, "Found valid mod file {} with {} mods - versions {}",
this.modFile.getFileName(),
this.mods.stream().map(IModInfo::getModId).collect(Collectors.joining(",", "{", "}")),
@@ -132,13 +125,11 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
}
@Override
public List<IModInfo> getMods()
{
public List<IModInfo> getMods() {
return mods;
}
public ModFile getFile()
{
public ModFile getFile() {
return this.modFile;
}
@@ -148,44 +139,42 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
}
@Override
public Map<String, Object> getFileProperties()
{
public Map<String, Object> getFileProperties() {
return this.properties;
}
@Override
public boolean showAsResourcePack()
{
public boolean showAsResourcePack() {
return this.showAsResourcePack;
}
@Override
public boolean isClientSideOnly()
{
public boolean isClientSideOnly() {
return this.clientSideOnly;
}
@Override
public boolean showAsDataPack()
{
public boolean showAsDataPack() {
return this.showAsDataPack;
}
@Override
public <T> Optional<T> getConfigElement(final String... key)
{
public <T> Optional<T> getConfigElement(final String key) {
return this.config.getConfigElement(key);
}
@Override
public List<? extends IConfigurable> getConfigList(final String... key)
{
public <T> Optional<T> getConfigElement(final String... key) {
return this.config.getConfigElement(key);
}
@Override
public List<? extends IConfigurable> getConfigList(final String... key) {
return this.config.getConfigList(key);
}
@Override
public String getLicense()
{
public String getLicense() {
return license;
}
@@ -194,49 +183,78 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
return this;
}
public URL getIssueURL()
{
public URL getIssueURL() {
return issueURL;
}
public boolean missingLicense()
{
public boolean missingLicense() {
return Strings.isNullOrEmpty(license);
}
private static final char[] HEX = "0123456789ABCDEF".toCharArray();
public Optional<String> getCodeSigningFingerprint() {
var signers = this.modFile.getSecureJar().getManifestSigners();
return (signers == null ? Stream.<CodeSigner>of() : Arrays.stream(signers))
.flatMap(csa->csa.getSignerCertPath().getCertificates().stream())
.findFirst()
.map(LamdbaExceptionUtils.rethrowFunction(Certificate::getEncoded))
.map(bytes->LamdbaExceptionUtils.uncheck(()->MessageDigest.getInstance("SHA-256")).digest(bytes))
.map(StringUtils::binToHex)
.map(str-> String.join(":", str.split("(?<=\\G.{2})")));
if (signers == null)
return Optional.empty();
for (var signer : signers) {
for (var cert : signer.getSignerCertPath().getCertificates()) {
try {
var encoded = cert.getEncoded();
var sha256 = MessageDigest.getInstance("SHA-256");
var digest = sha256.digest(encoded);
var buf = new StringBuilder(digest.length * 3);
for (int x = 0; x < digest.length - 1; x++) {
buf.append(HEX[(digest[x] & 0xF0) >> 4]);
buf.append(HEX[(digest[x] & 0x0F) ]);
buf.append(':');
}
buf.append(HEX[(digest[digest.length - 1] & 0xF0) >> 4]);
buf.append(HEX[(digest[digest.length - 1] & 0x0F) ]);
// We only care about the first. Should we list them all?
return Optional.of(buf.toString());
} catch (CertificateEncodingException | NoSuchAlgorithmException e) {
sneak(e);
}
}
}
return Optional.empty();
}
public Optional<String> getTrustData() {
return Arrays.stream(this.modFile.getSecureJar().getManifestSigners())
.flatMap(csa->csa.getSignerCertPath().getCertificates().stream())
.findFirst()
.map(X509Certificate.class::cast)
.map(c->{
StringBuffer sb = new StringBuffer();
sb.append(c.getSubjectX500Principal().getName(X500Principal.RFC2253).split(",")[0]);
boolean selfSigned = false;
try {
c.verify(c.getPublicKey());
selfSigned = true;
} catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException | SignatureException | NoSuchProviderException e) {
// not self signed
}
if (selfSigned) {
var signers = this.modFile.getSecureJar().getManifestSigners();
if (signers == null)
return Optional.empty();
for (var signer : signers) {
for (var cert : signer.getSignerCertPath().getCertificates()) {
if (!(cert instanceof X509Certificate x509))
continue;
var sb = new StringBuilder();
sb.append(x509.getSubjectX500Principal().getName(X500Principal.RFC2253).split(",")[0]);
var selfSigned = false;
try {
x509.verify(x509.getPublicKey());
selfSigned = true;
} catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException | SignatureException | NoSuchProviderException e) {
// not self signed
}
if (selfSigned)
sb.append(" self-signed");
} else {
sb.append(" signed by ").append(c.getIssuerX500Principal().getName(X500Principal.RFC2253).split(",")[0]);
};
return sb.toString();
});
else
sb.append(" signed by ").append(x509.getIssuerX500Principal().getName(X500Principal.RFC2253).split(",")[0]);
return Optional.of(sb.toString());
}
}
return Optional.empty();
}
@Override
@@ -253,4 +271,9 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
public List<String> usesServices() {
return usesServices;
}
@SuppressWarnings("unchecked")
private static <E extends Throwable, R> R sneak(Throwable e) throws E {
throw (E)e;
}
}
@@ -19,202 +19,189 @@ import org.jetbrains.annotations.ApiStatus;
import org.slf4j.Logger;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.regex.Pattern;
@ApiStatus.Internal
public class ModInfo implements IModInfo, IConfigurable {
public record ModInfo(
ModFileInfo getOwningFile,
IConfigurable getConfig,
String getModId,
String getNamespace,
ArtifactVersion getVersion,
String getDisplayName,
String getDescription,
Optional<String> getLogoFile,
boolean getLogoBlur,
Optional<URL> getUpdateURL,
Optional<URL> getModURL,
Holder<List<? extends ModVersion>> dependencies,
Holder<List<ForgeFeature.Bound>> forgeFeatures,
Map<String, Object> getModProperties
) implements IModInfo, IConfigurable {
private static final Logger LOGGER = LogUtils.getLogger();
private static final DefaultArtifactVersion DEFAULT_VERSION = new DefaultArtifactVersion("1");
private static final Pattern VALID_MODID = Pattern.compile("^[a-z][a-z0-9_]{1,63}$");
private static final Pattern VALID_NAMESPACE = Pattern.compile("^[a-z][a-z0-9_.-]{1,63}$");
private static final Pattern VALID_VERSION = Pattern.compile("^\\d+.*");
private final ModFileInfo owningFile;
private final String modId;
private final String namespace;
private final ArtifactVersion version;
private final String displayName;
private final String description;
private final Optional<String> logoFile;
private final boolean logoBlur;
private final Optional<URL> updateJSONURL;
private final List<? extends IModInfo.ModVersion> dependencies;
private final List<ForgeFeature.Bound> features;
private final Map<String,Object> properties;
private final IConfigurable config;
private final Optional<URL> modUrl;
public ModInfo(final ModFileInfo owningFile, final IConfigurable config)
{
Optional<ModFileInfo> ownFile = Optional.ofNullable(owningFile);
this.owningFile = owningFile;
this.config = config;
public static ModInfo of(ModFileInfo owningFile, IConfigurable config) {
// These are sourced from the mod specific [[mod]] entry
this.modId = config.<String>getConfigElement("modId")
.orElseThrow(() -> new InvalidModFileException("Missing modId", owningFile));
String modId = config.<String>getConfigElement("modId").orElse(null);
if (modId == null)
throw new InvalidModFileException("Missing modId", owningFile);
// verify we have a valid modid
if (!VALID_MODID.matcher(this.modId).matches()) {
LOGGER.error(LogUtils.FATAL_MARKER, "Invalid modId found in file {} - {} does not match the standard: {}", this.owningFile.getFile().getFilePath(), this.modId, VALID_MODID.pattern());
throw new InvalidModFileException("Invalid modId found : " + this.modId, owningFile);
if (!VALID_MODID.matcher(modId).matches()) {
LOGGER.error(LogUtils.FATAL_MARKER, "Invalid modId found in file {} - {} does not match the standard: {}", owningFile.getFile().getFilePath(), modId, VALID_MODID.pattern());
throw new InvalidModFileException("Invalid modId found : " + modId, owningFile);
}
this.namespace = config.<String>getConfigElement("namespace")
.orElse(this.modId);
String namespace = config.<String>getConfigElement("namespace")
.orElse(modId);
// verify our namespace is valid
if (!VALID_NAMESPACE.matcher(this.namespace).matches()) {
LOGGER.error(LogUtils.FATAL_MARKER, "Invalid override namespace found in file {} - {} does not match the standard: {}", this.owningFile.getFile().getFilePath(), this.namespace, VALID_NAMESPACE.pattern());
throw new InvalidModFileException("Invalid override namespace found : " + this.namespace, owningFile);
if (!VALID_NAMESPACE.matcher(namespace).matches()) {
LOGGER.error(LogUtils.FATAL_MARKER, "Invalid override namespace found in file {} - {} does not match the standard: {}", owningFile.getFile().getFilePath(), namespace, VALID_NAMESPACE.pattern());
throw new InvalidModFileException("Invalid override namespace found : " + namespace, owningFile);
}
this.version = config.<String>getConfigElement("version")
.map(s -> StringSubstitutor.replace(s, ownFile.map(ModFileInfo::getFile).orElse(null)))
ArtifactVersion version = config.<String>getConfigElement("version")
.map(s -> StringSubstitutor.replace(s, owningFile.getFile()))
.map(DefaultArtifactVersion::new)
.orElse(DEFAULT_VERSION);
// verify we have a valid mod version
if (!VALID_VERSION.matcher(this.version.toString()).matches()) {
throw new InvalidModFileException("Illegal version number specified "+this.version, this.getOwningFile());
}
if (!VALID_VERSION.matcher(version.toString()).matches())
throw new InvalidModFileException("Illegal version number specified " + version, owningFile);
// The remaining properties all default to sensible values and are not essential
this.displayName = config.<String>getConfigElement("displayName")
.orElse(this.modId);
this.description = config.<String>getConfigElement("description")
String displayName = config.<String>getConfigElement("displayName")
.orElse(modId);
String description = config.<String>getConfigElement("description")
.orElse("MISSING DESCRIPTION")
.replace("\r\n", "\n").stripIndent();
this.logoFile = Optional.ofNullable(config.<String>getConfigElement("logoFile")
.orElseGet(() -> ownFile.flatMap(mf -> mf.<String>getConfigElement("logoFile"))
.orElse(null)));
this.logoBlur = config.<Boolean>getConfigElement("logoBlur")
.orElseGet(() -> ownFile.flatMap(f -> f.<Boolean>getConfigElement("logoBlur"))
.orElse(true));
this.updateJSONURL = config.<String>getConfigElement("updateJSONURL")
Optional<String> logoFile = Optional.ofNullable(
config.<String>getConfigElement("logoFile")
.orElseGet(() -> owningFile.<String>getConfigElement("logoFile").orElse(null))
);
Boolean logoBlur = config.<Boolean>getConfigElement("logoBlur").orElse(null);
if (logoBlur == null)
logoBlur = owningFile.<Boolean>getConfigElement("logoBlur").orElse(true);
Optional<URL> updateJSONURL = config.<String>getConfigElement("updateJSONURL")
.map(StringUtils::toURL);
this.modUrl = config.<String>getConfigElement("modUrl")
Optional<URL> modUrl = config.<String>getConfigElement("modUrl")
.map(StringUtils::toURL);
// These are sourced from the file rather than the mod-specific block, but with a modid tag
this.dependencies = ownFile.map(mfi -> mfi.getConfigList("dependencies", this.modId))
.orElse(Collections.emptyList())
.stream()
.map(dep -> new ModVersion(this, dep))
.toList();
this.features = ownFile.flatMap(mfi -> mfi.<Map<String, Object>>getConfigElement("features", this.modId))
.stream()
.flatMap(m->m.entrySet().stream())
.map(this::makeBound)
.toList();
this.properties = ownFile.flatMap(mfi -> mfi.<Map<String, Object>>getConfigElement("modproperties", this.modId))
// dependencies and features are done after the constructor as they need to reference the ModInfo we are creating
List<? extends ModVersion> dependencies = Collections.emptyList();
List<ForgeFeature.Bound> forgeFeatures = Collections.emptyList();
Map<String, Object> modProperties = owningFile.<Map<String, Object>>getConfigElement("modproperties", modId)
.map(Collections::unmodifiableMap)
.orElse(Collections.emptyMap());
return new ModInfo(
owningFile, config,
modId, namespace, version,
displayName, description, logoFile, logoBlur, updateJSONURL, modUrl,
new Holder<>(dependencies), new Holder<>(forgeFeatures), modProperties
).setupDependencies().setupForgeFeatures();
}
@Override
public ModFileInfo getOwningFile() {
return owningFile;
private ModInfo setupDependencies() {
var deps = getOwningFile.getConfigList("dependencies", getModId);
if (deps == null || deps.isEmpty()) {
dependencies.value = Collections.emptyList();
return this;
}
var tmp = new ModVersion[deps.size()];
for (int i = 0; i < deps.size(); i++) {
tmp[i] = ModVersion.of(this, deps.get(i));
}
dependencies.value = List.of(tmp);
return this;
}
@Override
public String getModId() {
return modId;
}
private ModInfo setupForgeFeatures() {
var feats = getOwningFile.<Map<String, Object>>getConfigElement("features", getModId).orElse(null);
if (feats == null) {
forgeFeatures.value = Collections.emptyList();
return this;
}
@Override
public String getDisplayName()
{
return this.displayName;
}
@Override
public String getDescription()
{
return this.description;
}
@Override
public ArtifactVersion getVersion() {
return version;
}
@Override
public List<? extends IModInfo.ModVersion> getDependencies() {
return this.dependencies;
}
@Override
public String getNamespace() {
return this.namespace;
}
@Override
public Map<String, Object> getModProperties() {
return this.properties;
}
@Override
public Optional<URL> getUpdateURL() {
return this.updateJSONURL;
}
@Override
public Optional<String> getLogoFile()
{
return this.logoFile;
}
@Override
public boolean getLogoBlur()
{
return this.logoBlur;
}
@Override
public IConfigurable getConfig() {
var tmp = new ArrayList<ForgeFeature.Bound>();
for (var entry : feats.entrySet()) {
if (!(entry.getValue() instanceof String val))
throw new InvalidModFileException("Invalid feature bound {" + entry.getValue() + "} for key {" + entry.getKey() + "} only strings are accepted", getOwningFile);
tmp.add(new ForgeFeature.Bound(entry.getKey(), val, this));
}
forgeFeatures.value = List.copyOf(tmp);
return this;
}
@Override
public List<? extends ForgeFeature.Bound> getForgeFeatures() {
return this.features;
public <T> Optional<T> getConfigElement(String key) {
return getConfig.getConfigElement(key);
}
@Override
public <T> Optional<T> getConfigElement(final String... key) {
return this.config.getConfigElement(key);
public <T> Optional<T> getConfigElement(String... key) {
return getConfig.getConfigElement(key);
}
@Override
public List<? extends IConfigurable> getConfigList(final String... key) {
public List<? extends IConfigurable> getConfigList(String... key) {
return null;
}
@Override
public Optional<URL> getModURL() {
return modUrl;
public List<? extends IModInfo.ModVersion> getDependencies() {
return dependencies.value;
}
private ForgeFeature.Bound makeBound(Map.Entry<String, Object> e) {
if (e.getValue() instanceof String val) {
return new ForgeFeature.Bound(e.getKey(), val, this);
} else {
throw new InvalidModFileException("Invalid feature bound {" + e.getValue() + "} for key {" + e.getKey() + "} only strings are accepted", this.owningFile);
@Override
public List<? extends ForgeFeature.Bound> getForgeFeatures() {
return forgeFeatures.value;
}
private static final class Holder<T> {
private T value;
public Holder(T value) {
this.value = value;
}
}
class ModVersion implements net.minecraftforge.forgespi.language.IModInfo.ModVersion {
private IModInfo owner;
private final String modId;
private final VersionRange versionRange;
private final boolean mandatory;
private final Ordering ordering;
private final DependencySide side;
private final Optional<URL> referralUrl;
private record ModVersion(
Holder<IModInfo> owner,
public ModVersion(final IModInfo owner, final IConfigurable config) {
this.owner = owner;
this.modId = config.<String>getConfigElement("modId")
.orElseThrow(()->new InvalidModFileException("Missing required field modid in dependency", getOwningFile()));
if (this.modId.equals("forge")) {
String getModId,
VersionRange getVersionRange,
boolean isMandatory,
Ordering getOrdering,
DependencySide getSide,
Optional<URL> getReferralURL
) implements IModInfo.ModVersion {
public static ModVersion of(IModInfo owner, IConfigurable config) {
var modId = config.<String>getConfigElement("modId").orElse(null);
if (modId == null)
throw new InvalidModFileException("Missing required field modid in dependency", owner.getOwningFile());
if (modId.equals("forge")) {
var fileProps = owner.getOwningFile().getFileProperties();
// Checking containsKey to avoid a possible exception if the property is not present (due to Collections.emptyMap())
if (!fileProps.isEmpty() && fileProps.containsKey(ModFileInfo.NOT_A_FORGE_MOD_PROP)) {
@@ -223,74 +210,42 @@ public class ModInfo implements IModInfo, IConfigurable {
fileProps.remove(ModFileInfo.NOT_A_FORGE_MOD_PROP);
}
}
var mandatory = config.<Boolean>getConfigElement("mandatory");
if (mandatory.isPresent()) {
this.mandatory = mandatory.get();
} else if (owner.getOwningFile().getFileProperties().containsKey(ModFileInfo.NOT_A_FORGE_MOD_PROP)) {
this.mandatory = true;
} else {
throw new InvalidModFileException("Missing required field mandatory in dependency", getOwningFile());
}
this.versionRange = config.<String>getConfigElement("versionRange")
boolean mandatory;
var mandatoryValue = config.<Boolean>getConfigElement("mandatory");
if (mandatoryValue.isPresent())
mandatory = mandatoryValue.get();
else if (owner.getOwningFile().getFileProperties().containsKey(ModFileInfo.NOT_A_FORGE_MOD_PROP))
mandatory = true;
else
throw new InvalidModFileException("Missing required field mandatory in dependency", owner.getOwningFile());
var versionRange = config.<String>getConfigElement("versionRange")
.map(MavenVersionAdapter::createFromVersionSpec)
.orElse(UNBOUNDED);
this.ordering = config.<String>getConfigElement("ordering")
var ordering = config.<String>getConfigElement("ordering")
.map(Ordering::valueOf)
.orElse(Ordering.NONE);
this.side = config.<String>getConfigElement("side")
var side = config.<String>getConfigElement("side")
.map(DependencySide::valueOf)
.orElse(DependencySide.BOTH);
this.referralUrl = config.<String>getConfigElement("referralUrl")
var referralUrl = config.<String>getConfigElement("referralUrl")
.map(StringUtils::toURL);
}
@Override
public String getModId()
{
return modId;
return new ModVersion(
new Holder<>(owner),
modId, versionRange, mandatory, ordering, side, referralUrl
);
}
@Override
public VersionRange getVersionRange()
{
return versionRange;
public IModInfo getOwner() {
return owner.value;
}
@Override
public boolean isMandatory()
{
return mandatory;
}
@Override
public Ordering getOrdering()
{
return ordering;
}
@Override
public DependencySide getSide()
{
return side;
}
@Override
public void setOwner(final IModInfo owner)
{
this.owner = owner;
}
@Override
public IModInfo getOwner()
{
return owner;
}
@Override
public Optional<URL> getReferralURL() {
return referralUrl;
public void setOwner(IModInfo newOwner) {
owner.value = newOwner;
}
}
}
@@ -64,7 +64,7 @@ public class ModValidator {
}
@NotNull
private List<ModFile> validateFiles(final List<ModFile> mods) {
private static List<ModFile> validateFiles(final List<ModFile> mods) {
final List<ModFile> brokenFiles = new ArrayList<>();
for (Iterator<ModFile> iterator = mods.iterator(); iterator.hasNext();) {
ModFile modFile = iterator.next();
@@ -106,8 +106,8 @@ public class ModValidator {
private List<EarlyLoadingException.ExceptionData> validateLanguages() {
List<EarlyLoadingException.ExceptionData> errorData = new ArrayList<>();
for (Iterator<ModFile> iterator = this.candidateMods.iterator(); iterator.hasNext(); ) {
final ModFile modFile = iterator.next();
for (Iterator<ModFile> iterator = this.candidateMods.iterator(); iterator.hasNext();) {
var modFile = iterator.next();
try {
modFile.identifyLanguage();
} catch (EarlyLoadingException e) {
@@ -6,6 +6,8 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.electronwill.nightconfig.core.UnmodifiableConfig;
import com.google.common.collect.ImmutableMap;
import net.minecraftforge.forgespi.language.IConfigurable;
import net.minecraftforge.forgespi.language.IModFileInfo;
@@ -16,11 +18,12 @@ import java.util.Optional;
import java.util.stream.Collectors;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import static java.util.Arrays.asList;
@ApiStatus.Internal
class NightConfigWrapper implements IConfigurable {
final class NightConfigWrapper implements IConfigurable {
private final UnmodifiableConfig config;
private IModFileInfo file;
@@ -28,35 +31,51 @@ class NightConfigWrapper implements IConfigurable {
this.config = config;
}
NightConfigWrapper setFile(IModFileInfo file) {
private NightConfigWrapper(UnmodifiableConfig config, IModFileInfo file) {
this.config = config;
this.file = file;
}
void setFile(IModFileInfo file) {
this.file = file;
return this;
}
@Override
@SuppressWarnings("unchecked")
public <T> Optional<T> getConfigElement(String key) {
var path = List.of(key);
return Optional.ofNullable(validate(this.config.get(path), path));
}
@Override
public <T> Optional<T> getConfigElement(final String... key) {
var path = asList(key);
return this.config.getOptional(path).map(value -> {
if (value instanceof UnmodifiableConfig) {
return (T) ((UnmodifiableConfig) value).valueMap();
} else if (value instanceof ArrayList<?> al && al.size() > 0 && al.get(0) instanceof UnmodifiableConfig) {
throw new InvalidModFileException("The configuration path " + path + " is invalid. I wasn't expecting a multi-object list - remove one of the [[ ]]", file);
}
return (T) value;
});
return Optional.ofNullable(validate(this.config.get(path), path));
}
@SuppressWarnings("unchecked")
private <T> T validate(@Nullable T value, List<String> path) {
if (value instanceof UnmodifiableConfig cfg) {
// New Night config doesn't implement valueMap(), so do a copy.
var entries = cfg.entrySet();
var builder = ImmutableMap.builderWithExpectedSize(entries.size());
for (var e : entries)
builder.put(e.getKey(), e.getValue());
return (T) builder.build();
} else if (value instanceof ArrayList<?> al && !al.isEmpty() && al.getFirst() instanceof UnmodifiableConfig) {
throw new InvalidModFileException("The configuration path " + path + " is invalid. I wasn't expecting a multi-object list - remove one of the [[ ]]", file);
}
return value;
}
@Override
public List<? extends IConfigurable> getConfigList(final String... key) {
final List<String> path = asList(key);
if (this.config.contains(path) && !(this.config.get(path) instanceof Collection)) {
throw new InvalidModFileException("The configuration path "+path+" is invalid. Expecting a collection!", file);
throw new InvalidModFileException("The configuration path " + path + " is invalid. Expecting a collection!", file);
}
final Collection<UnmodifiableConfig> nestedConfigs = this.config.getOrElse(path, ArrayList::new);
return nestedConfigs.stream()
.map(NightConfigWrapper::new)
.map(cw->cw.setFile(file))
.map(conf -> new NightConfigWrapper(conf, file))
.collect(Collectors.toList());
}
}
@@ -21,37 +21,36 @@ import java.nio.file.Path;
import java.util.List;
@ApiStatus.Internal
class Scanner {
record Scanner(ModFile fileToScan, ModFileScanData result) {
private static final Logger LOGGER = LogUtils.getLogger();
private final ModFile fileToScan;
private static final boolean DEBUG = LOGGER.isDebugEnabled(LogMarkers.SCAN);
public Scanner(final ModFile fileToScan) {
this.fileToScan = fileToScan;
public Scanner(ModFile fileToScan) {
this(fileToScan, new ModFileScanData());
}
public ModFileScanData scan() {
ModFileScanData result = new ModFileScanData();
result.addModFileInfo(fileToScan.getModFileInfo());
fileToScan.scanFile(p -> fileVisitor(p, result));
fileToScan.scanFile(this::fileVisitor);
final List<IModLanguageProvider> loaders = fileToScan.getLoaders();
if (loaders != null) {
loaders.forEach(loader -> {
LOGGER.debug(LogMarkers.SCAN, "Scanning {} with language loader {}", fileToScan.getFilePath(), loader.name());
for (IModLanguageProvider loader : loaders) {
if (DEBUG) LOGGER.debug("Scanning {} with language loader {}", fileToScan.getFilePath(), loader.name());
loader.getFileVisitor().accept(result);
});
}
}
return result;
}
private void fileVisitor(final Path path, final ModFileScanData result) {
LOGGER.debug(LogMarkers.SCAN,"Scanning {} path {}", fileToScan, path);
try (InputStream in = Files.newInputStream(path)){
private void fileVisitor(final Path path) {
try (InputStream in = Files.newInputStream(path)) {
ModClassVisitor mcv = new ModClassVisitor();
ClassReader cr = new ClassReader(in);
cr.accept(mcv, 0);
mcv.buildData(result.getClasses(), result.getAnnotations());
} catch (IOException | IllegalArgumentException e) {
// mark path bad
if (DEBUG) LOGGER.warn("Failed scanning {} path {}", fileToScan, path);
}
}
}
@@ -24,8 +24,8 @@ import java.util.function.Supplier;
@ApiStatus.Internal
class ArgumentList {
private static final Logger LOGGER = LogUtils.getLogger();
private List<Supplier<String[]>> entries = new ArrayList<>();
private Map<String, EntryValue> values = new HashMap<>();
private final List<Supplier<String[]>> entries = new ArrayList<>();
private final Map<String, EntryValue> values = new HashMap<>();
public static ArgumentList from(String... args) {
ArgumentList ret = new ArgumentList();
@@ -77,8 +77,8 @@ class ArgumentList {
public String[] getArguments() {
return entries.stream()
.flatMap(e -> Arrays.asList(e.get()).stream())
.toArray(size -> new String[size]);
.flatMap(e -> Arrays.stream(e.get()))
.toArray(String[]::new);
}
public boolean hasValue(String key) {
@@ -122,7 +122,7 @@ class ArgumentList {
return ent.getValue();
}
private class EntryValue implements Supplier<String[]> {
private static final class EntryValue implements Supplier<String[]> {
private final String prefix;
private final String key;
private final boolean split;
@@ -40,7 +40,7 @@ abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
String username = args.get("username");
if (username != null) { // Replace '#' placeholders with random numbers
Matcher m = Pattern.compile("#+").matcher(username);
StringBuffer replaced = new StringBuffer();
StringBuilder replaced = new StringBuilder();
while (m.find()) {
m.appendReplacement(replaced, getRandomNumbers(m.group().length()));
}
@@ -7,7 +7,6 @@ package net.minecraftforge.fml.loading.targets;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.api.ILaunchHandlerService;
import cpw.mods.modlauncher.api.ITransformingClassLoaderBuilder;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.api.distmarker.Dist;
@@ -19,7 +18,6 @@ import org.slf4j.Logger;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
/**
@@ -47,10 +45,6 @@ public abstract class CommonLaunchHandler implements ILaunchHandlerService {
public abstract List<Path> getMinecraftPaths();
@Override
public void configureTransformationClassLoader(final ITransformingClassLoaderBuilder builder) {
}
protected String[] preLaunch(String[] arguments, ModuleLayer layer) {
URI uri;
try (var reader = layer.configuration().findModule("net.minecraftforge.fmlloader").orElseThrow().reference().open()) {
@@ -107,7 +101,7 @@ public abstract class CommonLaunchHandler implements ILaunchHandlerService {
len += 2;
}
str = str.substring(0, str.length() - len);
var path = Paths.get(URI.create(str));
var path = Path.of(URI.create(str));
return path;
}
}
@@ -9,7 +9,7 @@ import java.util.List;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Internal
abstract class ForgeDevLaunchHandler extends CommonDevLaunchHandler {
sealed abstract class ForgeDevLaunchHandler extends CommonDevLaunchHandler {
private ForgeDevLaunchHandler(LaunchType type) {
super(type, "forge_dev_");
}
@@ -33,25 +33,25 @@ abstract class ForgeDevLaunchHandler extends CommonDevLaunchHandler {
return List.of(filtered);
}
public static class Client extends ForgeDevLaunchHandler {
public static final class Client extends ForgeDevLaunchHandler {
public Client() {
super(CLIENT);
}
}
public static class Data extends ForgeDevLaunchHandler {
public static final class Data extends ForgeDevLaunchHandler {
public Data() {
super(DATA);
}
}
public static class Server extends ForgeDevLaunchHandler {
public static final class Server extends ForgeDevLaunchHandler {
public Server() {
super(SERVER);
}
}
public static class ServerGameTest extends ForgeDevLaunchHandler {
public static final class ServerGameTest extends ForgeDevLaunchHandler {
public ServerGameTest() {
super(SERVER_GAMETEST);
}
@@ -35,7 +35,7 @@ import net.minecraftforge.fml.loading.moddiscovery.AbstractModProvider;
import net.minecraftforge.forgespi.locating.IModLocator;
@ApiStatus.Internal
public class ForgeDevLocator extends AbstractModProvider implements IModLocator {
public final class ForgeDevLocator extends AbstractModProvider implements IModLocator {
private static final String PACK_META = "pack.mcmeta";
@Override
@@ -60,7 +60,7 @@ public class ForgeDevLocator extends AbstractModProvider implements IModLocator
return ret;
}
private List<Path> getMods() {
private static List<Path> getMods() {
// Forge is an exploded directory as well
var minecraft = ForgeDevLaunchHandler.getPathFromResource("net/minecraft/client/Minecraft.class");
var forge = ForgeDevLaunchHandler.getPathFromResource("net/minecraftforge/common/MinecraftForge.class");
@@ -84,7 +84,7 @@ public class ForgeDevLocator extends AbstractModProvider implements IModLocator
return ret;
}
private List<Path> explodeTestMods(Path path) {
private static List<Path> explodeTestMods(Path path) {
var mod = new ArrayList<Path>();
var memory = Jimfs.newFileSystem();
@@ -137,7 +137,7 @@ public class ForgeDevLocator extends AbstractModProvider implements IModLocator
// Find all the @Mods so we can generate tomls, and so we can pick packages. Right now it doesn't allow mods in parent directories,
// I could make it merge all the way up, but I think this would be fine.
private Map<String, Set<String>> findTestModPackages(Path path) {
private static Map<String, Set<String>> findTestModPackages(Path path) {
var mods = new HashMap<String, Set<String>>();
try (var files = Files.walk(path)) {
var classes = files
@@ -190,7 +190,7 @@ public class ForgeDevLocator extends AbstractModProvider implements IModLocator
}
// Builds or update the mods.toml file for all @Mods in this package
private void buildModsToml(Set<Path> resources, Set<String> modids, Path root) {
private static void buildModsToml(Set<Path> resources, Set<String> modids, Path root) {
var toml = resources.stream()
.map(p -> p.resolve(MODS_TOML))
.filter(Files::exists)
@@ -226,7 +226,7 @@ public class ForgeDevLocator extends AbstractModProvider implements IModLocator
}
for (var modid : modids) {
if (!modlist.stream().anyMatch(c -> modid.equals(c.get("modId")))) {
if (modlist.stream().noneMatch(c -> modid.equals(c.get("modId")))) {
modified = true;
var tmp = Config.inMemory();
tmp.set("modId", modid);
@@ -248,7 +248,7 @@ public class ForgeDevLocator extends AbstractModProvider implements IModLocator
}
// This is optional, it just hides a warning screen when starting up. I should probably remove this once I restructure how data gen for test mods work and make it generate there.
private void buildPackMeta(Set<Path> paths, Path root) {
private static void buildPackMeta(Set<Path> paths, Path root) {
var existing = paths.stream()
.map(p -> p.resolve(PACK_META))
.filter(Files::exists)
@@ -10,7 +10,7 @@ import java.util.List;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Internal
abstract class ForgeProdLaunchHandler extends CommonLaunchHandler {
sealed abstract class ForgeProdLaunchHandler extends CommonLaunchHandler {
protected ForgeProdLaunchHandler(LaunchType type) {
super(type, "forge_");
}
@@ -18,7 +18,7 @@ abstract class ForgeProdLaunchHandler extends CommonLaunchHandler {
@Override public String getNaming() { return "mcp"; }
@Override public boolean isProduction() { return true; }
public static class Client extends ForgeProdLaunchHandler {
public static final class Client extends ForgeProdLaunchHandler {
public Client() {
super(CLIENT);
}
@@ -29,7 +29,7 @@ abstract class ForgeProdLaunchHandler extends CommonLaunchHandler {
}
}
public static class Server extends ForgeProdLaunchHandler {
public static final class Server extends ForgeProdLaunchHandler {
public Server() {
super(SERVER);
}
@@ -14,7 +14,7 @@ import org.jetbrains.annotations.ApiStatus;
import cpw.mods.jarhandling.SecureJar;
@ApiStatus.Internal
abstract class ForgeUserdevLaunchHandler extends CommonDevLaunchHandler {
sealed abstract class ForgeUserdevLaunchHandler extends CommonDevLaunchHandler {
private ForgeUserdevLaunchHandler(LaunchType type) {
super(type, "forge_userdev_");
}
@@ -33,25 +33,25 @@ abstract class ForgeUserdevLaunchHandler extends CommonDevLaunchHandler {
return List.of(minecraft);
}
public static class Client extends ForgeUserdevLaunchHandler {
public static final class Client extends ForgeUserdevLaunchHandler {
public Client() {
super(CLIENT);
}
}
public static class Data extends ForgeUserdevLaunchHandler {
public static final class Data extends ForgeUserdevLaunchHandler {
public Data() {
super(DATA);
}
}
public static class Server extends ForgeUserdevLaunchHandler {
public static final class Server extends ForgeUserdevLaunchHandler {
public Server() {
super(SERVER);
}
}
public static class ServerGameTest extends ForgeUserdevLaunchHandler {
public static final class ServerGameTest extends ForgeUserdevLaunchHandler {
public ServerGameTest() {
super(SERVER_GAMETEST);
}
@@ -12,7 +12,7 @@ import net.minecraftforge.fml.loading.moddiscovery.AbstractModProvider;
import net.minecraftforge.forgespi.locating.IModLocator;
@ApiStatus.Internal
public class ForgeUserdevLocator extends AbstractModProvider implements IModLocator {
public final class ForgeUserdevLocator extends AbstractModProvider implements IModLocator {
@Override
public String name() {
return "forge_userdev_locator";
@@ -25,7 +25,7 @@ import java.util.Set;
*/
public class StronglyConnectedComponentDetector<T> {
private final Graph<T> graph;
private Map<T, Integer> ids;
private final Map<T, Integer> ids = new HashMap<>();
private T[] elements;
private int[] dfn;
private int[] low;
@@ -49,7 +49,6 @@ public class StronglyConnectedComponentDetector<T> {
private void calculate() {
components = new HashSet<>();
int t = 0;
ids = new HashMap<>();
Set<T> nodes = graph.nodes();
elements = (T[]) new Object[nodes.size()];
for (T node : nodes) {
+4 -4
View File
@@ -5,11 +5,11 @@ org.gradle.daemon=false
org.gradle.parallel=true
JAVA_VERSION=21
MC_VERSION=1.21
MC_NEXT_VERSION=1.21
MCP_VERSION=20240613.102039
MC_VERSION=1.21.3
MC_NEXT_VERSION=1.22
MCP_VERSION=20241025.112443
MAPPING_CHANNEL=official
MAPPING_VERSION=1.21
MAPPING_VERSION=1.21.3
// Set to true before the first build of a new MC version, so we don't do compatibility checks
CHECK_COMPATIBILITY=false
@@ -27,7 +27,7 @@ import java.util.stream.Collectors;
/**
* Automatic eventbus subscriber - reads {@link EventBusSubscriber}
* annotations and passes the class instances to the {@link nBus}
* annotations and passes the class instances to the {@link Bus}
* defined by the annotation. Defaults to {@code MinecraftForge#EVENT_BUS}
*/
public class AutomaticEventSubscriber {
@@ -31,21 +31,8 @@ public class FMLJavaModLanguageProvider implements IModLanguageProvider
{
private static final Logger LOGGER = LogManager.getLogger();
private static class FMLModTarget implements IModLanguageProvider.IModLanguageLoader {
private record FMLModTarget(String className, String modId) implements IModLanguageProvider.IModLanguageLoader {
private static final Logger LOGGER = FMLJavaModLanguageProvider.LOGGER;
private final String className;
private final String modId;
private FMLModTarget(String className, String modId)
{
this.className = className;
this.modId = modId;
}
public String getModId()
{
return modId;
}
@SuppressWarnings("unchecked")
@Override
@@ -94,7 +81,7 @@ public class FMLJavaModLanguageProvider implements IModLanguageProvider
.filter(ad -> ad.annotationType().equals(MODANNOTATION))
.peek(ad -> LOGGER.debug(SCAN, "Found @Mod class {} with id {}", ad.clazz().getClassName(), ad.annotationData().get("value")))
.map(ad -> new FMLModTarget(ad.clazz().getClassName(), (String)ad.annotationData().get("value")))
.collect(Collectors.toMap(FMLModTarget::getModId, Function.identity(), (a,b)->a));
.collect(Collectors.toMap(FMLModTarget::modId, Function.identity(), (a,b)->a));
scanResult.addLanguageLoader(modTargetMap);
};
}
@@ -8,7 +8,10 @@ package net.minecraftforge.fml.javafmlmod;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.ModLoadingContext;
public class FMLJavaModLoadingContext
/**
* Use the context provided by your language loader in your mod's constructor
*/
public class FMLJavaModLoadingContext extends ModLoadingContext
{
private final FMLModContainer container;
@@ -24,11 +27,21 @@ public class FMLJavaModLoadingContext
return container.getEventBus();
}
/**
* @return {@link FMLModContainer} by default.
*/
@Override
public FMLModContainer getContainer() {
return container;
}
/**
* Helper to get the right instance from the {@link ModLoadingContext} correctly.
* @return The FMLJavaMod language specific extension from the ModLoadingContext
*
* @deprecated use {@link FMLJavaModLoadingContext} in your mod constructor
*/
@Deprecated(forRemoval = true, since="1.21.1")
public static FMLJavaModLoadingContext get() {
return ModLoadingContext.get().extension();
}
@@ -21,6 +21,7 @@ import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Objects;
import java.util.Optional;
@@ -32,18 +33,16 @@ public class FMLModContainer extends ModContainer {
private final IEventBus eventBus;
private Object modInstance;
private final Class<?> modClass;
private final FMLJavaModLoadingContext context = new FMLJavaModLoadingContext(this);
public FMLModContainer(IModInfo info, String className, ModFileScanData modFileScanResults, ModuleLayer gameLayer) {
super(info);
LOGGER.debug(LOADING,"Creating FMLModContainer instance for {}", className);
this.scanResults = modFileScanResults;
activityMap.put(ModLoadingStage.CONSTRUCT, this::constructMod);
this.eventBus = BusBuilder.builder().setExceptionHandler(this::onEventFailed).setTrackPhases(false).markerType(IModBusEvent.class).useModLauncher().build();
this.eventBus = BusBuilder.builder().setExceptionHandler(FMLModContainer::onEventFailed).setTrackPhases(false).markerType(IModBusEvent.class).useModLauncher().build();
this.configHandler = Optional.of(ce->this.eventBus.post(ce.self()));
final FMLJavaModLoadingContext contextExtension = new FMLJavaModLoadingContext(this);
this.contextExtension = () -> contextExtension;
this.contextExtension = () -> context;
try {
var moduleName = info.getOwningFile().moduleName();
var module = gameLayer.findModule(moduleName)
@@ -56,14 +55,20 @@ public class FMLModContainer extends ModContainer {
}
}
private void onEventFailed(IEventBus iEventBus, Event event, IEventListener[] iEventListeners, int i, Throwable throwable) {
private static void onEventFailed(IEventBus iEventBus, Event event, IEventListener[] iEventListeners, int i, Throwable throwable) {
LOGGER.error(new EventBusErrorMessage(event, i, iEventListeners, throwable));
}
private void constructMod() {
try {
LOGGER.trace(LOADING, "Loading mod instance {} of type {}", getModId(), modClass.getName());
this.modInstance = modClass.getDeclaredConstructor().newInstance();
Constructor<?> constructor;
try {
constructor = modClass.getDeclaredConstructor(context.getClass());
} catch (NoSuchMethodException | SecurityException exception) {
constructor = modClass.getDeclaredConstructor();
}
this.modInstance = constructor.getParameterCount() == 0 ? constructor.newInstance() : constructor.newInstance(context);
LOGGER.trace(LOADING, "Loaded mod instance {} of type {}", getModId(), modClass.getName());
} catch (Throwable e) {
// When a mod constructor throws an exception, it's wrapped in an InvocationTargetException which hides the
@@ -21,14 +21,12 @@ import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
public class LowCodeModContainer extends ModContainer
{
private static final Logger LOGGER = LogUtils.getLogger();
private final ModFileScanData scanResults;
private Object modInstance;
private final Object modInstance;
public LowCodeModContainer(IModInfo info, ModFileScanData modFileScanResults, ModuleLayer gameLayer)
{
super(info);
LOGGER.debug(LOADING, "Creating LowCodeModContainer for {}", info.getModId());
this.scanResults = modFileScanResults;
this.modInstance = new Object();
this.contextExtension = () -> null;
this.extensionPoints.remove(IExtensionPoint.DisplayTest.class);
@@ -45,9 +43,4 @@ public class LowCodeModContainer extends ModContainer
{
return modInstance;
}
@Override
protected <T extends Event & IModBusEvent> void acceptEvent(final T e)
{
}
}
@@ -5,7 +5,6 @@
package net.minecraftforge.fml.mclanguageprovider;
import net.minecraftforge.fml.ModContainer;
import net.minecraftforge.forgespi.language.ILifecycleEvent;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.language.IModLanguageProvider;
@@ -15,14 +14,11 @@ import org.apache.logging.log4j.Logger;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Supplier;
import static net.minecraftforge.fml.Logging.LOADING;
import net.minecraftforge.forgespi.language.IModLanguageProvider.IModLanguageLoader;
public class MinecraftModLanguageProvider implements IModLanguageProvider {
private static final Logger LOGGER = LogManager.getLogger();
@Override
@@ -46,7 +46,7 @@ public class Config
private static boolean validateItemName(final Object obj)
{
return obj instanceof final String itemName && ForgeRegistries.ITEMS.containsKey(new ResourceLocation(itemName));
return obj instanceof final String itemName && ForgeRegistries.ITEMS.containsKey(ResourceLocation.tryParse(itemName));
}
@SubscribeEvent
@@ -58,7 +58,7 @@ public class Config
// convert the list of strings into a set of items
items = ITEM_STRINGS.get().stream()
.map(itemName -> ForgeRegistries.ITEMS.getValue(new ResourceLocation(itemName)))
.map(itemName -> ForgeRegistries.ITEMS.getValue(ResourceLocation.tryParse(itemName)))
.collect(Collectors.toSet());
}
}
@@ -18,7 +18,6 @@ import net.minecraftforge.event.BuildCreativeModeTabContentsEvent;
import net.minecraftforge.event.server.ServerStartingEvent;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
@@ -61,9 +60,9 @@ public class ExampleMod
output.accept(EXAMPLE_ITEM.get()); // Add the example item to the tab. For your own tabs, this method is preferred over the event
}).build());
public ExampleMod()
public ExampleMod(FMLJavaModLoadingContext context)
{
IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
IEventBus modEventBus = context.getModEventBus();
// Register the commonSetup method for modloading
modEventBus.addListener(this::commonSetup);
@@ -82,7 +81,7 @@ public class ExampleMod
modEventBus.addListener(this::addCreative);
// Register our mod's ForgeConfigSpec so that Forge can create and load the config file for us
ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, Config.SPEC);
context.registerConfig(ModConfig.Type.COMMON, Config.SPEC);
}
private void commonSetup(final FMLCommonSetupEvent event)
@@ -1,6 +1,6 @@
--- a/com/mojang/blaze3d/pipeline/RenderTarget.java
+++ b/com/mojang/blaze3d/pipeline/RenderTarget.java
@@ -104,7 +_,10 @@
@@ -96,7 +_,10 @@
GlStateManager._texParameter(3553, 34892, 0);
GlStateManager._texParameter(3553, 10242, 33071);
GlStateManager._texParameter(3553, 10243, 33071);
@@ -11,7 +11,7 @@
}
this.setFilterMode(9728, true);
@@ -114,8 +_,13 @@
@@ -106,8 +_,13 @@
GlStateManager._texImage2D(3553, 0, 32856, this.width, this.height, 0, 6408, 5121, null);
GlStateManager._glBindFramebuffer(36160, this.frameBufferId);
GlStateManager._glFramebufferTexture2D(36160, 36064, 3553, this.colorTextureId, 0);
@@ -26,7 +26,7 @@
}
this.checkStatus();
@@ -257,5 +_,26 @@
@@ -229,5 +_,26 @@
public int getDepthTextureId() {
return this.depthBufferId;
@@ -42,7 +42,7 @@
+ public void enableStencil() {
+ if (stencilEnabled) return;
+ stencilEnabled = true;
+ this.resize(viewWidth, viewHeight, net.minecraft.client.Minecraft.ON_OSX);
+ this.resize(viewWidth, viewHeight);
+ }
+
+ /**
@@ -1,6 +1,6 @@
--- a/com/mojang/blaze3d/platform/GlStateManager.java
+++ b/com/mojang/blaze3d/platform/GlStateManager.java
@@ -493,9 +_,17 @@
@@ -519,9 +_,17 @@
}
}
@@ -1,6 +1,6 @@
--- a/com/mojang/blaze3d/platform/Window.java
+++ b/com/mojang/blaze3d/platform/Window.java
@@ -84,7 +_,8 @@
@@ -88,7 +_,8 @@
GLFW.glfwWindowHint(139267, 2);
GLFW.glfwWindowHint(139272, 204801);
GLFW.glfwWindowHint(139270, 1);
@@ -10,7 +10,7 @@
if (monitor != null) {
VideoMode videomode = monitor.getPreferredVidMode(this.fullscreen ? this.preferredFullscreenVideoMode : Optional.empty());
this.windowedX = this.x = monitor.getX() + videomode.getWidth() / 2 - this.width / 2;
@@ -96,6 +_,7 @@
@@ -100,6 +_,7 @@
this.windowedX = this.x = aint1[0];
this.windowedY = this.y = aint[0];
}
@@ -18,7 +18,7 @@
GLFW.glfwMakeContextCurrent(this.window);
GL.createCapabilities();
@@ -254,6 +_,9 @@
@@ -267,6 +_,9 @@
GLFW.glfwGetFramebufferSize(this.window, aint, aint1);
this.framebufferWidth = aint[0] > 0 ? aint[0] : 1;
this.framebufferHeight = aint1[0] > 0 ? aint1[0] : 1;
@@ -1,6 +1,6 @@
--- a/com/mojang/blaze3d/vertex/PoseStack.java
+++ b/com/mojang/blaze3d/vertex/PoseStack.java
@@ -13,7 +_,7 @@
@@ -14,7 +_,7 @@
import org.joml.Vector3f;
@OnlyIn(Dist.CLIENT)
@@ -1,11 +0,0 @@
--- a/com/mojang/blaze3d/vertex/SheetedDecalTextureGenerator.java
+++ b/com/mojang/blaze3d/vertex/SheetedDecalTextureGenerator.java
@@ -62,7 +_,7 @@
public VertexConsumer setNormal(float p_344306_, float p_342091_, float p_344579_) {
this.delegate.setNormal(p_344306_, p_342091_, p_344579_);
Vector3f vector3f = this.normalInversePose.transform(p_344306_, p_342091_, p_344579_, this.normal);
- Direction direction = Direction.getNearest(vector3f.x(), vector3f.y(), vector3f.z());
+ Direction direction = net.minecraftforge.client.ForgeHooksClient.getNearestStable(vector3f.x(), vector3f.y(), vector3f.z());
Vector3f vector3f1 = this.cameraInversePose.transformPosition(this.x, this.y, this.z, this.worldPos);
vector3f1.rotateY((float) Math.PI);
vector3f1.rotateX((float) (-Math.PI / 2));
@@ -1,6 +1,6 @@
--- a/com/mojang/blaze3d/vertex/VertexConsumer.java
+++ b/com/mojang/blaze3d/vertex/VertexConsumer.java
@@ -12,7 +_,7 @@
@@ -13,7 +_,7 @@
import org.lwjgl.system.MemoryStack;
@OnlyIn(Dist.CLIENT)
@@ -9,16 +9,15 @@
VertexConsumer addVertex(float p_344294_, float p_342213_, float p_344859_);
VertexConsumer setColor(int p_342749_, int p_344324_, int p_343336_, int p_342831_);
@@ -135,10 +_,11 @@
}
@@ -133,9 +_,11 @@
int i1 = FastColor.ARGB32.color(k, (int)f3, (int)f4, (int)f5);
- int j1 = p_331444_[l];
+ int j1 = applyBakedLighting(p_331444_[l], bytebuffer);
int j1 = ARGB.color(k, (int)f3, (int)f4, (int)f5);
int k1 = LightTexture.lightCoordsWithEmission(p_331444_[i1], l);
+ k1 = applyBakedLighting(k1, bytebuffer);
float f10 = bytebuffer.getFloat(16);
float f9 = bytebuffer.getFloat(20);
Vector3f vector3f1 = matrix4f.transformPosition(f, f1, f2, new Vector3f());
+ applyBakedNormals(vector3f, bytebuffer, p_85988_.normal());
this.addVertex(vector3f1.x(), vector3f1.y(), vector3f1.z(), i1, f10, f9, p_85993_, j1, vector3f.x(), vector3f.y(), vector3f.z());
this.addVertex(vector3f1.x(), vector3f1.y(), vector3f1.z(), j1, f10, f9, p_85993_, k1, vector3f.x(), vector3f.y(), vector3f.z());
}
}
@@ -1,6 +1,6 @@
--- a/com/mojang/blaze3d/vertex/VertexFormat.java
+++ b/com/mojang/blaze3d/vertex/VertexFormat.java
@@ -13,7 +_,7 @@
@@ -14,7 +_,7 @@
import net.minecraftforge.api.distmarker.OnlyIn;
@OnlyIn(Dist.CLIENT)
@@ -9,7 +9,7 @@
public static final int UNKNOWN_ELEMENT = -1;
private final List<VertexFormatElement> elements;
private final List<String> names;
@@ -22,6 +_,7 @@
@@ -23,6 +_,7 @@
private final int[] offsetsByElement = new int[32];
@Nullable
private VertexBuffer immediateDrawVertexBuffer;
@@ -17,7 +17,7 @@
VertexFormat(List<VertexFormatElement> p_343616_, List<String> p_345241_, IntList p_345522_, int p_344162_) {
this.elements = p_343616_;
@@ -34,6 +_,11 @@
@@ -35,6 +_,11 @@
int j = vertexformatelement != null ? p_343616_.indexOf(vertexformatelement) : -1;
this.offsetsByElement[i] = j != -1 ? p_345522_.getInt(j) : -1;
}
@@ -29,7 +29,7 @@
}
public static VertexFormat.Builder builder() {
@@ -158,6 +_,9 @@
@@ -141,6 +_,9 @@
return vertexbuffer;
}
@@ -1,6 +1,6 @@
--- a/net/minecraft/SharedConstants.java
+++ b/net/minecraft/SharedConstants.java
@@ -171,6 +_,7 @@
@@ -170,6 +_,7 @@
}
static {
@@ -1,6 +1,6 @@
--- a/net/minecraft/Util.java
+++ b/net/minecraft/Util.java
@@ -267,7 +_,7 @@
@@ -264,7 +_,7 @@
.getSchema(DataFixUtils.makeKey(SharedConstants.getCurrentVersion().getDataVersion().getVersion()))
.getChoiceType(p_137552_, p_137553_);
} catch (IllegalArgumentException illegalargumentexception) {
@@ -1,6 +1,6 @@
--- a/net/minecraft/advancements/AdvancementRewards.java
+++ b/net/minecraft/advancements/AdvancementRewards.java
@@ -40,6 +_,7 @@
@@ -41,6 +_,7 @@
LootParams lootparams = new LootParams.Builder(p_9990_.serverLevel())
.withParameter(LootContextParams.THIS_ENTITY, p_9990_)
.withParameter(LootContextParams.ORIGIN, p_9990_.position())
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/Camera.java
+++ b/net/minecraft/client/Camera.java
@@ -105,9 +_,13 @@
@@ -120,9 +_,13 @@
}
protected void setRotation(float p_90573_, float p_90574_) {
@@ -15,7 +15,7 @@
FORWARDS.rotate(this.rotation, this.forwards);
UP.rotate(this.rotation, this.up);
LEFT.rotate(this.rotation, this.left);
@@ -223,6 +_,13 @@
@@ -238,6 +_,13 @@
public float getPartialTickTime() {
return this.partialTickTime;
@@ -5,6 +5,6 @@
@DontObfuscate
public static String getClientModName() {
- return "vanilla";
+ return net.minecraftforge.internal.BrandingControl.getClientBranding();
+ return net.minecraftforge.internal.BrandingControl.getBranding();
}
}
@@ -1,20 +0,0 @@
--- a/net/minecraft/client/ClientRecipeBook.java
+++ b/net/minecraft/client/ClientRecipeBook.java
@@ -65,7 +_,7 @@
Recipe<?> recipe = recipeholder.value();
if (!recipe.isSpecial() && !recipe.isIncomplete()) {
RecipeBookCategories recipebookcategories = getCategory(recipeholder);
- String s = recipe.getGroup();
+ String s = recipe.getGroup().isEmpty() ? recipeholder.id().toString() : recipe.getGroup(); // FORGE: Group value defaults to the recipe's ID if the recipe's explicit group is empty.
if (s.isEmpty()) {
map.computeIfAbsent(recipebookcategories, p_90645_ -> Lists.newArrayList()).add(ImmutableList.of(recipeholder));
} else {
@@ -125,6 +_,8 @@
} else if (recipetype == RecipeType.SMITHING) {
return RecipeBookCategories.SMITHING;
} else {
+ var categories = net.minecraftforge.client.RecipeBookManager.findCategories((RecipeType)recipetype, p_299041_);
+ if (categories != null) return categories;
LOGGER.warn(
"Unknown recipe category: {}/{}",
LogUtils.defer(() -> BuiltInRegistries.RECIPE_TYPE.getKey(recipe.getType())),
@@ -1,18 +1,21 @@
--- a/net/minecraft/client/KeyboardHandler.java
+++ b/net/minecraft/client/KeyboardHandler.java
@@ -405,9 +_,9 @@
Screen.wrapScreenError(() -> {
if (p_90897_ == 1 || p_90897_ == 2) {
@@ -424,12 +_,12 @@
if (screen != null) {
try {
if (p_90897_ != 1 && p_90897_ != 2) {
- if (p_90897_ == 0 && screen.keyReleased(p_90895_, p_90896_, p_90898_)) {
+ if (p_90897_ == 0 && net.minecraftforge.client.ForgeHooksClient.onScreenKeyReleased(screen, p_90895_, p_90896_, p_90898_)) {
return;
}
} else {
screen.afterKeyboardAction();
- aboolean[0] = screen.keyPressed(p_90895_, p_90896_, p_90898_);
+ net.minecraftforge.client.ForgeHooksClient.onScreenKeyPressed(aboolean, screen, p_90895_, p_90896_, p_90898_);
} else if (p_90897_ == 0) {
- aboolean[0] = screen.keyReleased(p_90895_, p_90896_, p_90898_);
+ net.minecraftforge.client.ForgeHooksClient.onScreenKeyReleased(aboolean, screen, p_90895_, p_90896_, p_90898_);
- if (screen.keyPressed(p_90895_, p_90896_, p_90898_)) {
+ if (net.minecraftforge.client.ForgeHooksClient.onScreenKeyPressed(screen, p_90895_, p_90896_, p_90898_)) {
return;
}
}
}, "keyPressed event handler", screen.getClass().getCanonicalName());
if (aboolean[0]) {
@@ -476,20 +_,21 @@
@@ -505,6 +_,7 @@
}
}
}
@@ -20,20 +23,17 @@
}
}
private void charTyped(long p_90890_, int p_90891_, int p_90892_) {
if (p_90890_ == this.minecraft.getWindow().getWindow()) {
- GuiEventListener guieventlistener = this.minecraft.screen;
+ Screen guieventlistener = this.minecraft.screen;
if (guieventlistener != null && this.minecraft.getOverlay() == null) {
if (Character.charCount(p_90891_) == 1) {
Screen.wrapScreenError(
- () -> guieventlistener.charTyped((char)p_90891_, p_90892_), "charTyped event handler", guieventlistener.getClass().getCanonicalName()
+ () -> net.minecraftforge.client.ForgeHooksClient.onScreenCharTyped(guieventlistener, (char)p_90891_, p_90892_), "charTyped event handler", guieventlistener.getClass().getCanonicalName()
);
} else {
for (char c0 : Character.toChars(p_90891_)) {
- Screen.wrapScreenError(() -> guieventlistener.charTyped(c0, p_90892_), "charTyped event handler", guieventlistener.getClass().getCanonicalName());
+ Screen.wrapScreenError(() -> net.minecraftforge.client.ForgeHooksClient.onScreenCharTyped(guieventlistener, c0, p_90892_), "charTyped event handler", guieventlistener.getClass().getCanonicalName());
@@ -514,10 +_,10 @@
if (screen != null && this.minecraft.getOverlay() == null) {
try {
if (Character.isBmpCodePoint(p_90891_)) {
- screen.charTyped((char)p_90891_, p_90892_);
+ net.minecraftforge.client.ForgeHooksClient.onScreenCharTyped(screen, (char)p_90891_, p_90892_);
} else if (Character.isValidCodePoint(p_90891_)) {
- screen.charTyped(Character.highSurrogate(p_90891_), p_90892_);
- screen.charTyped(Character.lowSurrogate(p_90891_), p_90892_);
+ net.minecraftforge.client.ForgeHooksClient.onScreenCharTyped(screen, Character.highSurrogate(p_90891_), p_90892_);
+ net.minecraftforge.client.ForgeHooksClient.onScreenCharTyped(screen, Character.lowSurrogate(p_90891_), p_90892_);
}
}
}
} catch (Throwable throwable) {
CrashReport crashreport = CrashReport.forThrowable(throwable, "charTyped event handler");
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/Minecraft.java
+++ b/net/minecraft/client/Minecraft.java
@@ -254,7 +_,7 @@
@@ -253,7 +_,7 @@
import org.slf4j.Logger;
@OnlyIn(Dist.CLIENT)
@@ -9,7 +9,7 @@
static Minecraft instance;
private static final Logger LOGGER = LogUtils.getLogger();
public static final boolean ON_OSX = Util.getPlatform() == Util.OS.OSX;
@@ -431,7 +_,6 @@
@@ -433,7 +_,6 @@
}
}, Util.nonCriticalIoPool());
LOGGER.info("Setting user: {}", this.user.getName());
@@ -17,10 +17,10 @@
this.demo = p_91084_.game.demo;
this.allowsMultiplayer = !p_91084_.game.disableMultiplayer;
this.allowsChat = !p_91084_.game.disableChat;
@@ -468,15 +_,15 @@
@@ -480,15 +_,15 @@
LOGGER.error("Couldn't set icon", (Throwable)ioexception);
}
this.window.setFramerateLimit(this.options.framerateLimit().get());
+ // FORGE: Move mouse and keyboard handler setup further below
this.mouseHandler = new MouseHandler(this);
- this.mouseHandler.setup(this.window.getWindow());
@@ -29,27 +29,21 @@
RenderSystem.initRenderer(this.options.glDebugVerbosity, false);
this.mainRenderTarget = new MainTarget(this.window.getWidth(), this.window.getHeight());
this.mainRenderTarget.setClearColor(0.0F, 0.0F, 0.0F, 0.0F);
this.mainRenderTarget.clear(ON_OSX);
this.mainRenderTarget.clear();
this.resourceManager = new ReloadableResourceManager(PackType.CLIENT_RESOURCES);
+ net.minecraftforge.client.loading.ClientModLoader.begin(this, this.resourcePackRepository, this.resourceManager);
this.resourcePackRepository.reload();
this.options.loadSelectedResourcePacks(this.resourcePackRepository);
this.languageManager = new LanguageManager(this.options.languageCode, p_340763_ -> {
@@ -540,6 +_,7 @@
this.entityRenderDispatcher = new EntityRenderDispatcher(this, this.textureManager, this.itemRenderer, this.blockRenderer, this.font, this.options, this.entityModels);
@@ -562,6 +_,7 @@
);
this.resourceManager.registerReloadListener(this.entityRenderDispatcher);
this.particleEngine = new ParticleEngine(this.level, this.textureManager);
+ net.minecraftforge.client.ForgeHooksClient.onRegisterParticleProviders(this.particleEngine);
this.resourceManager.registerReloadListener(this.particleEngine);
this.paintingTextures = new PaintingTextureManager(this.textureManager);
this.resourceManager.registerReloadListener(this.paintingTextures);
@@ -552,11 +_,15 @@
this.gameRenderer = new GameRenderer(this, this.entityRenderDispatcher.getItemInHandRenderer(), this.resourceManager, this.renderBuffers);
this.resourceManager.registerReloadListener(this.gameRenderer.createReloadListener());
this.levelRenderer = new LevelRenderer(this, this.entityRenderDispatcher, this.blockEntityRenderDispatcher, this.renderBuffers);
+ net.minecraftforge.fml.ModLoader.get().postEvent(new net.minecraftforge.client.event.RenderLevelStageEvent.RegisterStageEvent());
this.resourceManager.registerReloadListener(this.levelRenderer);
this.gpuWarnlistManager = new GpuWarnlistManager();
@@ -577,6 +_,9 @@
this.resourceManager.registerReloadListener(this.gpuWarnlistManager);
this.resourceManager.registerReloadListener(this.regionalCompliancies);
this.gui = new Gui(this);
@@ -59,7 +53,7 @@
this.debugRenderer = new DebugRenderer(this);
RealmsClient realmsclient = RealmsClient.create(this);
this.realmsDataFetcher = new RealmsDataFetcher(realmsclient);
@@ -580,6 +_,7 @@
@@ -600,6 +_,7 @@
this.options.fullscreen().set(this.window.isFullscreen());
}
@@ -67,7 +61,7 @@
this.window.updateVsync(this.options.enableVsync().get());
this.window.updateRawMouseInput(this.options.rawMouseInput().get());
this.window.setDefaultErrorCallback();
@@ -600,16 +_,17 @@
@@ -620,16 +_,17 @@
GameLoadTimesEvent.INSTANCE.beginStep(TelemetryProperty.LOAD_TIME_LOADING_OVERLAY_MS);
Minecraft.GameLoadCookie minecraft$gameloadcookie = new Minecraft.GameLoadCookie(realmsclient, p_91084_.quickPlay);
this.setOverlay(
@@ -87,8 +81,8 @@
+ ).get()
);
this.quickPlayLog = QuickPlayLog.of(p_91084_.quickPlay.path());
}
@@ -698,6 +_,7 @@
this.framerateLimitTracker = new FramerateLimitTracker(this.options, this);
@@ -724,6 +_,7 @@
StringBuilder stringbuilder = new StringBuilder("Minecraft");
if (checkModStatus().shouldReportAsModified()) {
stringbuilder.append("*");
@@ -96,7 +90,7 @@
}
stringbuilder.append(" ");
@@ -721,6 +_,8 @@
@@ -747,6 +_,8 @@
}
private UserApiService createUserApiService(YggdrasilAuthenticationService p_193586_, GameConfig p_193587_) {
@@ -105,7 +99,7 @@
return p_193587_.user.user.getType() != User.Type.MSA
? UserApiService.OFFLINE
: p_193586_.createUserApiService(p_193587_.user.user.getAccessToken());
@@ -731,7 +_,7 @@
@@ -757,7 +_,7 @@
}
private void rollbackResourcePacks(Throwable p_91240_, @Nullable Minecraft.GameLoadCookie p_299515_) {
@@ -114,7 +108,7 @@
this.clearResourcePacksOnError(p_91240_, null, p_299515_);
} else {
Util.throwAsRuntime(p_91240_);
@@ -991,12 +_,6 @@
@@ -1043,12 +_,6 @@
LOGGER.error("setScreen called from non-game thread");
}
@@ -127,7 +121,7 @@
if (p_91153_ == null && this.clientLevelTeardownInProgress) {
throw new IllegalStateException("Trying to return to in-game GUI during disconnection");
} else {
@@ -1010,6 +_,23 @@
@@ -1062,6 +_,23 @@
}
}
@@ -151,19 +145,19 @@
this.screen = p_91153_;
if (this.screen != null) {
this.screen.added();
@@ -1155,9 +_,11 @@
@@ -1215,9 +_,11 @@
this.mouseHandler.handleAccumulatedMovement();
this.profiler.pop();
profilerfiller.pop();
if (!this.noRender) {
+ net.minecraftforge.client.event.ForgeEventFactoryClient.onRenderTickStart(this.timer);
this.profiler.popPush("gameRenderer");
this.gameRenderer.render(this.timer, p_91384_);
this.profiler.pop();
+ net.minecraftforge.client.event.ForgeEventFactoryClient.onRenderTickEnd(this.timer);
+ net.minecraftforge.client.event.ForgeEventFactoryClient.onRenderTickStart(this.deltaTracker);
profilerfiller.popPush("gameRenderer");
this.gameRenderer.render(this.deltaTracker, p_91384_);
profilerfiller.pop();
+ net.minecraftforge.client.event.ForgeEventFactoryClient.onRenderTickEnd(this.deltaTracker);
}
if (this.fpsPieResults != null) {
@@ -1283,10 +_,12 @@
profilerfiller.push("blit");
@@ -1339,10 +_,12 @@
this.window.setGuiScale((double)i);
if (this.screen != null) {
this.screen.resize(this, this.window.getGuiScaledWidth(), this.window.getGuiScaledHeight());
@@ -171,12 +165,12 @@
}
RenderTarget rendertarget = this.getMainRenderTarget();
rendertarget.resize(this.window.getWidth(), this.window.getHeight(), ON_OSX);
rendertarget.resize(this.window.getWidth(), this.window.getHeight());
+ if (this.gameRenderer != null)
this.gameRenderer.resize(this.window.getWidth(), this.window.getHeight());
this.mouseHandler.setIgnoreFirstMove();
}
@@ -1554,6 +_,7 @@
@@ -1483,6 +_,7 @@
}
public void stop() {
@@ -184,7 +178,7 @@
this.running = false;
}
@@ -1582,10 +_,18 @@
@@ -1511,10 +_,18 @@
if (p_91387_ && this.hitResult != null && this.hitResult.getType() == HitResult.Type.BLOCK) {
BlockHitResult blockhitresult = (BlockHitResult)this.hitResult;
BlockPos blockpos = blockhitresult.getBlockPos();
@@ -206,7 +200,7 @@
this.player.swing(InteractionHand.MAIN_HAND);
}
}
@@ -1613,6 +_,8 @@
@@ -1542,6 +_,8 @@
return false;
} else {
boolean flag = false;
@@ -215,7 +209,7 @@
switch (this.hitResult.getType()) {
case ENTITY:
this.gameMode.attack(this.player, ((EntityHitResult)this.hitResult).getEntity());
@@ -1620,7 +_,7 @@
@@ -1549,7 +_,7 @@
case BLOCK:
BlockHitResult blockhitresult = (BlockHitResult)this.hitResult;
BlockPos blockpos = blockhitresult.getBlockPos();
@@ -224,7 +218,7 @@
this.gameMode.startDestroyBlock(blockpos, blockhitresult.getDirection());
if (this.level.getBlockState(blockpos).isAir()) {
flag = true;
@@ -1633,8 +_,10 @@
@@ -1562,8 +_,10 @@
}
this.player.resetAttackStrengthTicker();
@@ -235,7 +229,7 @@
this.player.swing(InteractionHand.MAIN_HAND);
return flag;
}
@@ -1650,6 +_,12 @@
@@ -1579,6 +_,12 @@
}
for (InteractionHand interactionhand : InteractionHand.values()) {
@@ -248,68 +242,68 @@
ItemStack itemstack = this.player.getItemInHand(interactionhand);
if (!itemstack.isItemEnabled(this.level.enabledFeatures())) {
return;
@@ -1670,7 +_,7 @@
@@ -1599,7 +_,7 @@
}
if (interactionresult.consumesAction()) {
- if (interactionresult.shouldSwing()) {
+ if (interactionresult.shouldSwing() && inputEvent.shouldSwingHand()) {
if (interactionresult instanceof InteractionResult.Success interactionresult$success2) {
- if (interactionresult$success2.swingSource() == InteractionResult.SwingSource.CLIENT) {
+ if (interactionresult$success2.swingSource() == InteractionResult.SwingSource.CLIENT && inputEvent.shouldSwingHand()) {
this.player.swing(interactionhand);
}
@@ -1682,7 +_,7 @@
@@ -1611,7 +_,7 @@
int i = itemstack.getCount();
InteractionResult interactionresult1 = this.gameMode.useItemOn(this.player, interactionhand, blockhitresult);
if (interactionresult1.consumesAction()) {
- if (interactionresult1.shouldSwing()) {
+ if (interactionresult1.shouldSwing() && inputEvent.shouldSwingHand()) {
if (interactionresult1 instanceof InteractionResult.Success interactionresult$success) {
- if (interactionresult$success.swingSource() == InteractionResult.SwingSource.CLIENT) {
+ if (interactionresult$success.swingSource() == InteractionResult.SwingSource.CLIENT && inputEvent.shouldSwingHand()) {
this.player.swing(interactionhand);
if (!itemstack.isEmpty() && (itemstack.getCount() != i || this.gameMode.hasInfiniteItems())) {
this.gameRenderer.itemInHandRenderer.itemUsed(interactionhand);
@@ -1698,6 +_,9 @@
@@ -1627,6 +_,9 @@
}
}
+ if (itemstack.isEmpty() && (this.hitResult == null || this.hitResult.getType() == HitResult.Type.MISS))
+ net.minecraftforge.event.ForgeEventFactory.onRightClickEmpty(this.player, interactionhand);
+
if (!itemstack.isEmpty()) {
InteractionResult interactionresult2 = this.gameMode.useItem(this.player, interactionhand);
if (interactionresult2.consumesAction()) {
@@ -1728,6 +_,8 @@
this.rightClickDelay--;
if (!itemstack.isEmpty()
&& this.gameMode.useItem(this.player, interactionhand) instanceof InteractionResult.Success interactionresult$success1) {
if (interactionresult$success1.swingSource() == InteractionResult.SwingSource.CLIENT) {
@@ -1656,6 +_,8 @@
}
ProfilerFiller profilerfiller = Profiler.get();
+ net.minecraftforge.event.ForgeEventFactory.onPreClientTick();
+
this.profiler.push("gui");
profilerfiller.push("gui");
this.chatListener.tick();
this.gui.tick(this.pause);
@@ -1811,6 +_,7 @@
@@ -1745,6 +_,7 @@
this.tutorial.tick();
+ net.minecraftforge.event.ForgeEventFactory.onPreLevelTick(this.level, () -> true);
try {
this.level.tick(() -> true);
} catch (Throwable throwable) {
@@ -1824,6 +_,7 @@
} catch (Throwable throwable1) {
@@ -1758,6 +_,7 @@
throw new ReportedException(crashreport);
throw new ReportedException(crashreport1);
}
+ net.minecraftforge.event.ForgeEventFactory.onPostLevelTick(this.level, () -> true);
}
this.profiler.popPush("animateTick");
@@ -1843,6 +_,7 @@
this.profiler.popPush("keyboard");
profilerfiller.popPush("animateTick");
@@ -1782,6 +_,7 @@
profilerfiller.popPush("keyboard");
this.keyboardHandler.tick();
this.profiler.pop();
profilerfiller.pop();
+ net.minecraftforge.event.ForgeEventFactory.onPostClientTick();
}
private boolean isLevelRunningNormally() {
@@ -2043,6 +_,7 @@
@@ -1983,6 +_,7 @@
}
public void setLevel(ClientLevel p_91157_, ReceivingLevelScreen.Reason p_335893_) {
@@ -317,7 +311,7 @@
this.updateScreenAndTick(new ReceivingLevelScreen(() -> false, p_335893_));
this.level = p_91157_;
this.updateLevelInEngines(p_91157_);
@@ -2080,6 +_,7 @@
@@ -2020,6 +_,7 @@
IntegratedServer integratedserver = this.singleplayerServer;
this.singleplayerServer = null;
this.gameRenderer.resetData();
@@ -325,15 +319,15 @@
this.gameMode = null;
this.narrator.clear();
this.clientLevelTeardownInProgress = true;
@@ -2087,6 +_,7 @@
@@ -2027,6 +_,7 @@
try {
this.updateScreenAndTick(p_335030_);
if (this.level != null) {
+ net.minecraftforge.event.ForgeEventFactory.onLevelUnload(this.level);
if (integratedserver != null) {
this.profiler.push("waitForServer");
@@ -2099,6 +_,7 @@
ProfilerFiller profilerfiller = Profiler.get();
profilerfiller.push("waitForServer");
@@ -2040,6 +_,7 @@
this.gui.onDisconnected();
this.isLocalServer = false;
@@ -341,7 +335,7 @@
}
this.level = null;
@@ -2245,6 +_,7 @@
@@ -2187,6 +_,7 @@
private void pickBlock() {
if (this.hitResult != null && this.hitResult.getType() != HitResult.Type.MISS) {
@@ -349,7 +343,7 @@
boolean flag = this.player.getAbilities().instabuild;
BlockEntity blockentity = null;
HitResult.Type hitresult$type = this.hitResult.getType();
@@ -2257,7 +_,7 @@
@@ -2199,7 +_,7 @@
}
Block block = blockstate.getBlock();
@@ -358,7 +352,7 @@
if (itemstack.isEmpty()) {
return;
}
@@ -2271,7 +_,7 @@
@@ -2213,7 +_,7 @@
}
Entity entity = ((EntityHitResult)this.hitResult).getEntity();
@@ -367,7 +361,7 @@
if (itemstack == null) {
return;
}
@@ -2855,6 +_,10 @@
@@ -2818,6 +_,10 @@
@Nullable
public static String getLauncherBrand() {
return System.getProperty("minecraft.launcher.brand");
@@ -1,34 +1,40 @@
--- a/net/minecraft/client/MouseHandler.java
+++ b/net/minecraft/client/MouseHandler.java
@@ -79,6 +_,7 @@
@@ -85,6 +_,7 @@
this.activeButton = -1;
}
+ if (net.minecraftforge.client.event.ForgeEventFactoryClient.onMouseButtonPre(p_91532_, p_91533_, p_91534_)) return;
boolean[] aboolean = new boolean[]{false};
if (this.minecraft.getOverlay() == null) {
if (this.minecraft.screen == null) {
@@ -91,9 +_,9 @@
Screen screen = this.minecraft.screen;
if (flag) {
if (!this.mouseGrabbed && flag) {
@@ -98,7 +_,7 @@
screen.afterMouseAction();
- Screen.wrapScreenError(() -> aboolean[0] = screen.mouseClicked(d0, d1, i), "mouseClicked event handler", screen.getClass().getCanonicalName());
+ Screen.wrapScreenError(() -> net.minecraftforge.client.event.ForgeEventFactoryClient.onScreenMouseClicked(aboolean, screen, d0, d1, i), "mouseClicked event handler", screen.getClass().getCanonicalName());
try {
- if (screen.mouseClicked(d0, d1, i)) {
+ if (net.minecraftforge.client.event.ForgeEventFactoryClient.onScreenMouseClicked(screen, d0, d1, i)) {
return;
}
} catch (Throwable throwable1) {
@@ -112,7 +_,7 @@
}
} else {
- Screen.wrapScreenError(() -> aboolean[0] = screen.mouseReleased(d0, d1, i), "mouseReleased event handler", screen.getClass().getCanonicalName());
+ Screen.wrapScreenError(() -> net.minecraftforge.client.event.ForgeEventFactoryClient.onScreenMouseReleased(aboolean, screen, d0, d1, i), "mouseReleased event handler", screen.getClass().getCanonicalName());
}
try {
- if (screen.mouseReleased(d0, d1, i)) {
+ if (net.minecraftforge.client.event.ForgeEventFactoryClient.onScreenMouseReleased(screen, d0, d1, i)) {
return;
}
} catch (Throwable throwable) {
@@ -147,6 +_,7 @@
}
}
@@ -116,6 +_,7 @@
}
}
}
+ net.minecraftforge.client.event.ForgeEventFactoryClient.onMouseButtonPost(p_91532_, p_91533_, p_91534_);
}
+ net.minecraftforge.client.event.ForgeEventFactoryClient.onMouseButtonPost(p_91532_, p_91533_, p_91534_);
}
@@ -129,7 +_,9 @@
private void onScroll(long p_91527_, double p_91528_, double p_91529_) {
@@ -160,7 +_,9 @@
if (this.minecraft.screen != null) {
double d3 = this.xpos * (double)this.minecraft.getWindow().getGuiScaledWidth() / (double)this.minecraft.getWindow().getScreenWidth();
double d4 = this.ypos * (double)this.minecraft.getWindow().getGuiScaledHeight() / (double)this.minecraft.getWindow().getScreenHeight();
@@ -38,25 +44,25 @@
+ net.minecraftforge.client.event.ForgeEventFactoryClient.onScreenMouseScrollPost(this.minecraft.screen, d3, d4, d1, d2);
this.minecraft.screen.afterMouseAction();
} else if (this.minecraft.player != null) {
if (this.accumulatedScrollX != 0.0 && Math.signum(d1) != Math.signum(this.accumulatedScrollX)) {
@@ -151,6 +_,7 @@
this.accumulatedScrollX -= (double)j;
this.accumulatedScrollY -= (double)i;
int k = i == 0 ? -j : i;
Vector2i vector2i = this.scrollWheelHandler.onMouseScroll(d1, d2);
@@ -169,6 +_,7 @@
}
int i = vector2i.y == 0 ? -vector2i.x : vector2i.y;
+ if (net.minecraftforge.client.event.ForgeEventFactoryClient.onMouseScroll(this, d1, d2)) return;
if (this.minecraft.player.isSpectator()) {
if (this.minecraft.gui.getSpectatorGui().isMenuActive()) {
this.minecraft.gui.getSpectatorGui().onMouseScrolled(-k);
@@ -236,7 +_,7 @@
if (this.activeButton != -1 && this.mousePressedTime > 0.0) {
double d4 = this.accumulatedDX * (double)this.minecraft.getWindow().getGuiScaledWidth() / (double)this.minecraft.getWindow().getScreenWidth();
this.minecraft.gui.getSpectatorGui().onMouseScrolled(-i);
@@ -274,7 +_,7 @@
double d5 = this.accumulatedDY * (double)this.minecraft.getWindow().getGuiScaledHeight() / (double)this.minecraft.getWindow().getScreenHeight();
- Screen.wrapScreenError(() -> screen.mouseDragged(d2, d3, this.activeButton, d4, d5), "mouseDragged event handler", screen.getClass().getCanonicalName());
+ Screen.wrapScreenError(() -> net.minecraftforge.client.ForgeHooksClient.onScreenMouseDrag(screen, d2, d3, this.activeButton, d4, d5), "mouseDragged event handler", screen.getClass().getCanonicalName());
}
screen.afterMouseMove();
@@ -303,6 +_,14 @@
try {
- screen.mouseDragged(d2, d3, this.activeButton, d4, d5);
+ net.minecraftforge.client.ForgeHooksClient.onScreenMouseDrag(screen, d2, d3, this.activeButton, d4, d5);
} catch (Throwable throwable) {
CrashReport crashreport1 = CrashReport.forThrowable(throwable, "mouseDragged event handler");
screen.fillCrashDetails(crashreport1);
@@ -349,6 +_,14 @@
public double ypos() {
return this.ypos;
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/Options.java
+++ b/net/minecraft/client/Options.java
@@ -681,7 +_,8 @@
@@ -698,7 +_,8 @@
},
new OptionInstance.LazyEnum<>(
() -> Stream.concat(Stream.of(""), Minecraft.getInstance().getSoundManager().getAvailableSoundDevices().stream()).toList(),
@@ -10,7 +10,7 @@
? Optional.empty()
: Optional.of(p_232011_),
Codec.STRING
@@ -1050,6 +_,7 @@
@@ -1079,6 +_,7 @@
}
public Options(Minecraft p_92138_, File p_92139_) {
@@ -18,7 +18,7 @@
this.minecraft = p_92138_;
this.optionsFile = new File(p_92139_, "options.txt");
boolean flag = Runtime.getRuntime().maxMemory() >= 1000000000L;
@@ -1184,12 +_,22 @@
@@ -1210,12 +_,22 @@
p_168428_.process("telemetryOptInExtra", this.telemetryOptInExtra);
this.onboardAccessibility = p_168428_.process("onboardAccessibility", this.onboardAccessibility);
p_168428_.process("menuBackgroundBlurriness", this.menuBackgroundBlurriness);
@@ -42,7 +42,7 @@
}
}
@@ -1207,6 +_,10 @@
@@ -1233,6 +_,10 @@
}
public void load() {
@@ -53,7 +53,7 @@
try {
if (!this.optionsFile.exists()) {
return;
@@ -1234,7 +_,8 @@
@@ -1260,7 +_,8 @@
}
}
@@ -63,15 +63,15 @@
new Options.FieldAccess() {
@Nullable
private String getValueOrNull(String p_168459_) {
@@ -1428,6 +_,7 @@
@@ -1451,6 +_,7 @@
}
public void broadcastOptions() {
+ if (net.minecraftforge.client.loading.ClientModLoader.isLoading()) return; //Don't save settings before mods add keybindigns and the like to prevent them from being deleted.
if (this.minecraft.player != null) {
this.minecraft.player.connection.send(new ServerboundClientInformationPacket(this.buildPlayerInformation()));
this.minecraft.player.connection.broadcastClientInformation(this.buildPlayerInformation());
}
@@ -1546,6 +_,23 @@
@@ -1564,6 +_,23 @@
public static Component genericValueLabel(Component p_231901_, int p_231902_) {
return genericValueLabel(p_231901_, Component.literal(Integer.toString(p_231902_)));
@@ -1,45 +0,0 @@
--- a/net/minecraft/client/RecipeBookCategories.java
+++ b/net/minecraft/client/RecipeBookCategories.java
@@ -12,7 +_,7 @@
import net.minecraftforge.api.distmarker.OnlyIn;
@OnlyIn(Dist.CLIENT)
-public enum RecipeBookCategories {
+public enum RecipeBookCategories implements net.minecraftforge.common.IExtensibleEnum {
CRAFTING_SEARCH(new ItemStack(Items.COMPASS)),
CRAFTING_BUILDING_BLOCKS(new ItemStack(Blocks.BRICKS)),
CRAFTING_REDSTONE(new ItemStack(Items.REDSTONE)),
@@ -38,7 +_,7 @@
public static final List<RecipeBookCategories> CRAFTING_CATEGORIES = ImmutableList.of(
CRAFTING_SEARCH, CRAFTING_EQUIPMENT, CRAFTING_BUILDING_BLOCKS, CRAFTING_MISC, CRAFTING_REDSTONE
);
- public static final Map<RecipeBookCategories, List<RecipeBookCategories>> AGGREGATE_CATEGORIES = ImmutableMap.of(
+ public static final Map<RecipeBookCategories, List<RecipeBookCategories>> AGGREGATE_CATEGORIES = net.minecraftforge.client.RecipeBookManager.getAggregateCategories(ImmutableMap.of(
CRAFTING_SEARCH,
ImmutableList.of(CRAFTING_EQUIPMENT, CRAFTING_BUILDING_BLOCKS, CRAFTING_MISC, CRAFTING_REDSTONE),
FURNACE_SEARCH,
@@ -47,7 +_,7 @@
ImmutableList.of(BLAST_FURNACE_BLOCKS, BLAST_FURNACE_MISC),
SMOKER_SEARCH,
ImmutableList.of(SMOKER_FOOD)
- );
+ ));
private final List<ItemStack> itemIcons;
private RecipeBookCategories(final ItemStack... p_92267_) {
@@ -60,10 +_,15 @@
case FURNACE -> FURNACE_CATEGORIES;
case BLAST_FURNACE -> BLAST_FURNACE_CATEGORIES;
case SMOKER -> SMOKER_CATEGORIES;
+ default -> net.minecraftforge.client.RecipeBookManager.getCustomCategoriesOrEmpty(p_92270_);
};
}
public List<ItemStack> getIconItems() {
return this.itemIcons;
+ }
+
+ public static RecipeBookCategories create(String name, ItemStack... icons) {
+ throw new IllegalStateException("Enum not extended");
}
}
@@ -10,8 +10,8 @@
public static ItemColors createDefault(BlockColors p_92684_) {
ItemColors itemcolors = new ItemColors();
@@ -105,17 +_,20 @@
: FastColor.ARGB32.opaque(p_325310_.getOrDefault(DataComponents.MAP_COLOR, MapItemColor.DEFAULT).rgb()),
@@ -101,17 +_,20 @@
(p_357654_, p_357655_) -> p_357655_ == 0 ? -1 : ARGB.opaque(p_357654_.getOrDefault(DataComponents.MAP_COLOR, MapItemColor.DEFAULT).rgb()),
Items.FILLED_MAP
);
+ net.minecraftforge.client.ForgeHooksClient.onItemColorsInit(itemcolors, p_92684_);
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/gui/Font.java
+++ b/net/minecraft/client/gui/Font.java
@@ -31,7 +_,7 @@
@@ -33,7 +_,7 @@
import org.joml.Vector3f;
@OnlyIn(Dist.CLIENT)
@@ -9,12 +9,3 @@
private static final float EFFECT_DEPTH = 0.01F;
private static final Vector3f SHADOW_OFFSET = new Vector3f(0.0F, 0.0F, 0.03F);
public static final int ALPHA_CUTOFF = 8;
@@ -322,6 +_,8 @@
public StringSplitter getSplitter() {
return this.splitter;
}
+
+ @Override public Font self() { return this; }
@OnlyIn(Dist.CLIENT)
public static enum DisplayMode {
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/gui/Gui.java
+++ b/net/minecraft/client/gui/Gui.java
@@ -131,7 +_,7 @@
@@ -147,7 +_,7 @@
public float vignetteBrightness = 1.0F;
private int toolHighlightTimer;
private ItemStack lastToolHighlight = ItemStack.EMPTY;
@@ -9,7 +9,7 @@
private final SubtitleOverlay subtitleOverlay;
private final SpectatorGui spectatorGui;
private final PlayerTabOverlay tabList;
@@ -318,7 +_,7 @@
@@ -345,7 +_,7 @@
Window window = this.minecraft.getWindow();
int i = Mth.floor(this.minecraft.mouseHandler.xpos() * (double)window.getGuiScaledWidth() / (double)window.getScreenWidth());
int j = Mth.floor(this.minecraft.mouseHandler.ypos() * (double)window.getGuiScaledHeight() / (double)window.getScreenHeight());
@@ -18,24 +18,24 @@
}
}
@@ -434,6 +_,8 @@
@@ -443,6 +_,8 @@
for (MobEffectInstance mobeffectinstance : Ordering.natural().reverse().sortedCopy(collection)) {
Holder<MobEffect> holder = mobeffectinstance.getEffect();
+ var renderer = net.minecraftforge.client.extensions.common.IClientMobEffectExtensions.of(mobeffectinstance);
+ if (!renderer.isVisibleInGui(mobeffectinstance)) continue;
if (mobeffectinstance.showIcon()) {
int i = p_282812_.guiWidth();
int j = 1;
@@ -463,6 +_,7 @@
int k = p_282812_.guiWidth();
int l = 1;
@@ -473,6 +_,7 @@
}
}
+ if (renderer.renderGuiIcon(mobeffectinstance, this, p_282812_, i, j, 0, f)) continue;
+ if (renderer.renderGuiIcon(mobeffectinstance, this, p_282812_, k, l, 0, f)) continue;
TextureAtlasSprite textureatlassprite = mobeffecttexturemanager.get(holder);
int l1 = i;
int i1 = j;
@@ -625,6 +_,10 @@
int l1 = k;
int k1 = l;
@@ -623,6 +_,10 @@
}
private void renderSelectedItemName(GuiGraphics p_283501_) {
@@ -43,37 +43,35 @@
+ }
+
+ public void renderSelectedItemName(GuiGraphics p_283501_, int yShift) {
this.minecraft.getProfiler().push("selectedItemName");
Profiler.get().push("selectedItemName");
if (this.toolHighlightTimer > 0 && !this.lastToolHighlight.isEmpty()) {
MutableComponent mutablecomponent = Component.empty().append(this.lastToolHighlight.getHoverName()).withStyle(this.lastToolHighlight.getRarity().color());
@@ -632,9 +_,10 @@
@@ -630,9 +_,13 @@
mutablecomponent.withStyle(ChatFormatting.ITALIC);
}
- int i = this.getFont().width(mutablecomponent);
+ Component highlightTip = this.lastToolHighlight.getHighlightTip(mutablecomponent);
+ int i = this.getFont().width(highlightTip);
+ Font font = net.minecraftforge.client.extensions.common.IClientItemExtensions.of(lastToolHighlight).getFont(lastToolHighlight, net.minecraftforge.client.extensions.common.IClientItemExtensions.FontContext.SELECTED_ITEM_NAME);
+ if (font == null)
+ font = this.getFont();
+ int i = font.width(highlightTip);
int j = (p_283501_.guiWidth() - i) / 2;
- int k = p_283501_.guiHeight() - 59;
+ int k = p_283501_.guiHeight() - Math.max(yShift, 59);
if (!this.minecraft.gameMode.canHurtPlayer()) {
k += 14;
}
@@ -646,6 +_,13 @@
@@ -643,7 +_,7 @@
}
if (l > 0) {
p_283501_.drawStringWithBackdrop(this.getFont(), mutablecomponent, j, k, i, FastColor.ARGB32.color(l, -1));
+ Font font = net.minecraftforge.client.extensions.common.IClientItemExtensions.of(lastToolHighlight).getFont(lastToolHighlight, net.minecraftforge.client.extensions.common.IClientItemExtensions.FontContext.SELECTED_ITEM_NAME);
+ if (font == null) {
+ p_283501_.drawString(this.getFont(), highlightTip, j, k, 16777215 + (l << 24));
+ } else {
+ j = (p_283501_.guiWidth() - font.width(highlightTip)) / 2;
+ p_283501_.drawString(font, highlightTip, j, k, 16777215 + (l << 24));
+ }
- p_283501_.drawStringWithBackdrop(this.getFont(), mutablecomponent, j, k, i, ARGB.color(l, -1));
+ p_283501_.drawStringWithBackdrop(font, highlightTip, j, k, i, ARGB.color(l, -1));
}
}
@@ -1146,7 +_,7 @@
@@ -1152,7 +_,7 @@
ItemStack itemstack = this.minecraft.player.getInventory().getSelected();
if (itemstack.isEmpty()) {
this.toolHighlightTimer = 0;
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/gui/GuiGraphics.java
+++ b/net/minecraft/client/gui/GuiGraphics.java
@@ -56,7 +_,7 @@
@@ -53,7 +_,7 @@
import org.joml.Vector2ic;
@OnlyIn(Dist.CLIENT)
@@ -9,7 +9,7 @@
public static final float MAX_GUI_Z = 10000.0F;
public static final float MIN_GUI_Z = -10000.0F;
private static final int EXTRA_SPACE_AFTER_FIRST_TOOLTIP_LINE = 2;
@@ -268,6 +_,11 @@
@@ -227,6 +_,11 @@
}
public int drawString(Font p_283343_, @Nullable String p_281896_, int p_283569_, int p_283418_, int p_281560_, boolean p_282130_) {
@@ -18,10 +18,10 @@
+
+ // Forge: Add float variant for x,y coordinates
+ public int drawString(Font p_283343_, @Nullable String p_281896_, float p_283569_, float p_283418_, int p_281560_, boolean p_282130_) {
if (p_281896_ == null) {
return 0;
} else {
@@ -294,6 +_,11 @@
return p_281896_ == null
? 0
: p_283343_.drawInBatch(
@@ -248,6 +_,11 @@
}
public int drawString(Font p_282636_, FormattedCharSequence p_281596_, int p_281586_, int p_282816_, int p_281743_, boolean p_282394_) {
@@ -30,10 +30,10 @@
+
+ // Forge: Add float variant for x,y coordinates
+ public int drawString(Font p_282636_, FormattedCharSequence p_281596_, float p_281586_, float p_282816_, int p_281743_, boolean p_282394_) {
int i = p_282636_.drawInBatch(
return p_282636_.drawInBatch(
p_281596_,
(float)p_281586_,
@@ -864,6 +_,7 @@
@@ -872,6 +_,7 @@
CrashReport crashreport = CrashReport.forThrowable(throwable, "Rendering item");
CrashReportCategory crashreportcategory = crashreport.addCategory("Item being rendered");
crashreportcategory.setDetail("Item Type", () -> String.valueOf(p_281675_.getItem()));
@@ -41,9 +41,9 @@
crashreportcategory.setDetail("Item Components", () -> String.valueOf(p_281675_.getComponents()));
crashreportcategory.setDetail("Item Foil", () -> String.valueOf(p_281675_.hasFoil()));
throw new ReportedException(crashreport);
@@ -904,16 +_,26 @@
}
@@ -892,13 +_,24 @@
this.renderItemCount(p_282005_, p_283349_, p_282641_, p_282146_, p_282803_);
this.renderItemCooldown(p_283349_, p_282641_, p_282146_);
this.pose.popPose();
+ net.minecraftforge.client.ItemDecoratorHandler.of(p_283349_).render(this, p_282005_, p_283349_, p_282641_, p_282146_);
}
@@ -53,43 +53,56 @@
public void renderTooltip(Font p_282308_, ItemStack p_282781_, int p_282687_, int p_282292_) {
+ this.tooltipStack = p_282781_;
this.renderTooltip(p_282308_, Screen.getTooltipFromItem(this.minecraft, p_282781_), p_282781_.getTooltipImage(), p_282687_, p_282292_);
this.renderTooltip(
p_282308_, Screen.getTooltipFromItem(this.minecraft, p_282781_), p_282781_.getTooltipImage(), p_282687_, p_282292_, p_282781_.get(DataComponents.TOOLTIP_STYLE)
);
+ this.tooltipStack = ItemStack.EMPTY;
+ }
+
+ public void renderTooltip(Font font, List<Component> textComponents, Optional<TooltipComponent> tooltipComponent, ItemStack stack, int mouseX, int mouseY) {
+ this.tooltipStack = stack;
+ this.renderTooltip(font, textComponents, tooltipComponent, mouseX, mouseY);
+ this.renderTooltip(font, textComponents, tooltipComponent, mouseX, mouseY, stack.get(DataComponents.TOOLTIP_STYLE));
+ this.tooltipStack = ItemStack.EMPTY;
}
public void renderTooltip(Font p_283128_, List<Component> p_282716_, Optional<TooltipComponent> p_281682_, int p_283678_, int p_281696_) {
- List<ClientTooltipComponent> list = p_282716_.stream().map(Component::getVisualOrderText).map(ClientTooltipComponent::create).collect(Util.toMutableList());
- p_281682_.ifPresent(p_325321_ -> list.add(list.isEmpty() ? 0 : 1, ClientTooltipComponent.create(p_325321_)));
+ List<ClientTooltipComponent> list = net.minecraftforge.client.ForgeHooksClient.gatherTooltipComponents(this.tooltipStack, p_282716_, p_281682_, p_283678_, guiWidth(), guiHeight(), p_283128_);
this.renderTooltipInternal(p_283128_, list, p_283678_, p_281696_, DefaultTooltipPositioner.INSTANCE);
@@ -908,8 +_,7 @@
public void renderTooltip(
Font p_362491_, List<Component> p_368544_, Optional<TooltipComponent> p_362815_, int p_366300_, int p_368952_, @Nullable ResourceLocation p_368469_
) {
- List<ClientTooltipComponent> list = p_368544_.stream().map(Component::getVisualOrderText).map(ClientTooltipComponent::create).collect(Util.toMutableList());
- p_362815_.ifPresent(p_325321_ -> list.add(list.isEmpty() ? 0 : 1, ClientTooltipComponent.create(p_325321_)));
+ List<ClientTooltipComponent> list = net.minecraftforge.client.ForgeHooksClient.gatherTooltipComponents(this.tooltipStack, p_368544_, p_362815_, p_366300_, guiWidth(), guiHeight(), p_362491_);
this.renderTooltipInternal(p_362491_, list, p_366300_, p_368952_, DefaultTooltipPositioner.INSTANCE, p_368469_);
}
@@ -922,7 +_,15 @@
@@ -922,7 +_,22 @@
}
public void renderComponentTooltip(Font p_282739_, List<Component> p_281832_, int p_282191_, int p_282446_) {
- this.renderTooltip(p_282739_, Lists.transform(p_281832_, Component::getVisualOrderText), p_282191_, p_282446_);
- this.renderComponentTooltip(p_282739_, p_281832_, p_282191_, p_282446_, null);
+ List<ClientTooltipComponent> components = net.minecraftforge.client.ForgeHooksClient.gatherTooltipComponents(this.tooltipStack, p_281832_, p_282191_, guiWidth(), guiHeight(), p_282739_);
+ this.renderTooltipInternal(p_282739_, components, p_282191_, p_282446_, DefaultTooltipPositioner.INSTANCE);
+ this.renderTooltipInternal(p_282739_, components, p_282191_, p_282446_, DefaultTooltipPositioner.INSTANCE, null);
+ }
+
+ public void renderComponentTooltip(Font font, List<? extends net.minecraft.network.chat.FormattedText> tooltips, int mouseX, int mouseY, ItemStack stack) {
+ this.tooltipStack = stack;
+ List<ClientTooltipComponent> components = net.minecraftforge.client.ForgeHooksClient.gatherTooltipComponents(stack, tooltips, mouseX, guiWidth(), guiHeight(), font);
+ this.renderTooltipInternal(font, components, mouseX, mouseY, DefaultTooltipPositioner.INSTANCE);
+ this.renderTooltipInternal(font, components, mouseX, mouseY, DefaultTooltipPositioner.INSTANCE, null);
+ this.tooltipStack = ItemStack.EMPTY;
+ }
+
+ public void renderComponentTooltipFromElements(Font font, List<com.mojang.datafixers.util.Either<FormattedText, TooltipComponent>> elements, int mouseX, int mouseY, ItemStack stack) {
+ this.tooltipStack = stack;
+ List<ClientTooltipComponent> components = net.minecraftforge.client.ForgeHooksClient.gatherTooltipComponentsFromElements(stack, elements, mouseX, guiWidth(), guiHeight(), font);
+ this.renderTooltipInternal(font, components, mouseX, mouseY, DefaultTooltipPositioner.INSTANCE, null);
+ this.tooltipStack = ItemStack.EMPTY;
}
public void renderTooltip(Font p_282192_, List<? extends FormattedCharSequence> p_282297_, int p_281680_, int p_283325_) {
@@ -941,11 +_,13 @@
private void renderTooltipInternal(Font p_282675_, List<ClientTooltipComponent> p_282615_, int p_283230_, int p_283417_, ClientTooltipPositioner p_282442_) {
public void renderComponentTooltip(Font p_369090_, List<Component> p_365405_, int p_368143_, int p_366244_, @Nullable ResourceLocation p_364763_) {
@@ -964,11 +_,13 @@
@Nullable ResourceLocation p_368234_
) {
if (!p_282615_.isEmpty()) {
+ var preEvent = net.minecraftforge.client.ForgeHooksClient.onRenderTooltipPre(this.tooltipStack, this, p_283230_, p_283417_, guiWidth(), guiHeight(), p_282615_, p_282675_, p_282442_);
+ if (preEvent.isCanceled()) return;
@@ -102,21 +115,19 @@
if (k > i) {
i = k;
}
@@ -955,18 +_,21 @@
@@ -978,18 +_,19 @@
int i2 = i;
int j2 = j;
- Vector2ic vector2ic = p_282442_.positionTooltip(this.guiWidth(), this.guiHeight(), p_283230_, p_283417_, i2, j2);
- Vector2ic vector2ic = p_282442_.positionTooltip(this.guiWidth(), this.guiHeight(), p_283230_, p_283417_, i, j);
+ Vector2ic vector2ic = p_282442_.positionTooltip(this.guiWidth(), this.guiHeight(), preEvent.getX(), preEvent.getY(), i2, j2);
int l = vector2ic.x();
int i1 = vector2ic.y();
this.pose.pushPose();
int j1 = 400;
- this.drawManaged(() -> TooltipRenderUtil.renderTooltipBackground(this, l, i1, i2, j2, 400));
+ this.drawManaged(() -> {
+ var colorEvent = net.minecraftforge.client.ForgeHooksClient.onRenderTooltipColor(this.tooltipStack, this, l, i1, preEvent.getFont(), p_282615_);
+ TooltipRenderUtil.renderTooltipBackground(this, l, i1, i2, j2, 400, colorEvent.getBackgroundStart(), colorEvent.getBackgroundEnd(), colorEvent.getBorderStart(), colorEvent.getBorderEnd());
+ });
- TooltipRenderUtil.renderTooltipBackground(this, l, i1, i, j, 400, p_368234_);
+ var background_event = net.minecraftforge.client.event.ForgeEventFactoryClient.onRenderTooltipBackground(this.tooltipStack, this, l, i1, preEvent.getFont(), p_282615_, p_368234_);
+ TooltipRenderUtil.renderTooltipBackground(this, l, i1, i, j, 400, background_event.getBackground());
this.pose.translate(0.0F, 0.0F, 400.0F);
int k1 = i1;
@@ -124,15 +135,26 @@
ClientTooltipComponent clienttooltipcomponent1 = p_282615_.get(l1);
- clienttooltipcomponent1.renderText(p_282675_, l, k1, this.pose.last().pose(), this.bufferSource);
+ clienttooltipcomponent1.renderText(preEvent.getFont(), l, k1, this.pose.last().pose(), this.bufferSource);
k1 += clienttooltipcomponent1.getHeight() + (l1 == 0 ? 2 : 0);
k1 += clienttooltipcomponent1.getHeight(p_282675_) + (l1 == 0 ? 2 : 0);
}
@@ -974,7 +_,7 @@
@@ -997,7 +_,7 @@
for (int k2 = 0; k2 < p_282615_.size(); k2++) {
ClientTooltipComponent clienttooltipcomponent2 = p_282615_.get(k2);
- clienttooltipcomponent2.renderImage(p_282675_, l, k1, this);
+ clienttooltipcomponent2.renderImage(preEvent.getFont(), l, k1, this);
k1 += clienttooltipcomponent2.getHeight() + (k2 == 0 ? 2 : 0);
- clienttooltipcomponent2.renderImage(p_282675_, l, k1, i2, j2, this);
+ clienttooltipcomponent2.renderImage(preEvent.getFont(), l, k1, i2, j2, this);
k1 += clienttooltipcomponent2.getHeight(p_282675_) + (k2 == 0 ? 2 : 0);
}
@@ -1054,6 +_,10 @@
}
}
}
+ }
+
+ public MultiBufferSource.BufferSource getBufferSource() {
+ return this.bufferSource;
}
public void drawSpecial(Consumer<MultiBufferSource> p_367429_) {
@@ -1,9 +1,9 @@
--- a/net/minecraft/client/gui/components/AbstractButton.java
+++ b/net/minecraft/client/gui/components/AbstractButton.java
@@ -34,7 +_,7 @@
RenderSystem.enableDepthTest();
p_281670_.blitSprite(SPRITES.get(this.active, this.isHoveredOrFocused()), this.getX(), this.getY(), this.getWidth(), this.getHeight());
p_281670_.setColor(1.0F, 1.0F, 1.0F, 1.0F);
@@ -39,7 +_,7 @@
this.getHeight(),
ARGB.white(this.alpha)
);
- int i = this.active ? 16777215 : 10526880;
+ int i = getFGColor();
this.renderString(p_281670_, minecraft.font, i | Mth.ceil(this.alpha * 255.0F) << 24);
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/gui/components/AbstractWidget.java
+++ b/net/minecraft/client/gui/components/AbstractWidget.java
@@ -255,6 +_,25 @@
@@ -259,6 +_,25 @@
this.focused = p_93693_;
}
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/gui/components/BossHealthOverlay.java
+++ b/net/minecraft/client/gui/components/BossHealthOverlay.java
@@ -63,13 +_,16 @@
@@ -66,13 +_,16 @@
for (LerpingBossEvent lerpingbossevent : this.events.values()) {
int k = i / 2 - 91;
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/gui/components/DebugScreenOverlay.java
+++ b/net/minecraft/client/gui/components/DebugScreenOverlay.java
@@ -101,9 +_,9 @@
@@ -105,9 +_,9 @@
@Nullable
private CompletableFuture<LevelChunk> serverChunk;
private boolean renderDebug;
@@ -13,53 +13,54 @@
private final LocalSampleLogger frameTimeLogger = new LocalSampleLogger(1);
private final LocalSampleLogger tickTimeLogger = new LocalSampleLogger(TpsDebugDimensions.values().length);
private final LocalSampleLogger pingLogger = new LocalSampleLogger(1);
@@ -129,14 +_,18 @@
@@ -135,14 +_,18 @@
this.clientChunk = null;
}
- public void render(GuiGraphics p_281427_) {
- this.minecraft.getProfiler().push("debug");
- ProfilerFiller profilerfiller = Profiler.get();
- profilerfiller.push("debug");
+ protected void update() {
Entity entity = this.minecraft.getCameraEntity();
this.block = entity.pick(20.0, 0.0F, false);
this.liquid = entity.pick(20.0, 0.0F, true);
- p_281427_.drawManaged(() -> {
+ }
+
+ protected void drawText(GuiGraphics p_281427_) {
this.drawGameInformation(p_281427_);
this.drawSystemInformation(p_281427_);
+ protected void drawText(ProfilerFiller profilerfiller, GuiGraphics p_281427_) {
this.drawGameInformation(p_281427_);
this.drawSystemInformation(p_281427_);
+ }
+
+ protected void drawFPSCharts(GuiGraphics p_281427_) {
if (this.renderFpsCharts) {
int i = p_281427_.guiWidth();
int j = i / 2;
@@ -146,7 +_,9 @@
this.tpsChart.drawChart(p_281427_, i - k, k);
}
}
+ protected void drawFPSCharts(ProfilerFiller profilerfiller, GuiGraphics p_281427_) {
this.profilerPieChart.setBottomOffset(10);
if (this.renderFpsCharts) {
int i = p_281427_.guiWidth();
@@ -155,7 +_,9 @@
this.profilerPieChart.setBottomOffset(this.tpsChart.getFullHeight());
}
+ }
+ protected void drawNetworkCharts(GuiGraphics p_281427_) {
if (this.renderNetworkCharts) {
int l = p_281427_.guiWidth();
int i1 = l / 2;
@@ -157,12 +_,21 @@
int j1 = this.pingChart.getWidth(i1);
this.pingChart.drawChart(p_281427_, l - j1, j1);
}
+ protected void drawNetworkCharts(ProfilerFiller profilerfiller, GuiGraphics p_281427_) {
if (this.renderNetworkCharts) {
int l = p_281427_.guiWidth();
int i1 = l / 2;
@@ -171,12 +_,22 @@
try (Zone zone = profilerfiller.zone("profilerPie")) {
this.profilerPieChart.render(p_281427_);
}
+ }
+
+ public void render(GuiGraphics p_281427_) {
+ this.minecraft.getProfiler().push("debug");
+ ProfilerFiller profilerfiller = Profiler.get();
+ profilerfiller.push("debug");
+ this.update();
+ p_281427_.drawManaged(() -> {
+ this.drawText(p_281427_);
+ this.drawFPSCharts(p_281427_);
+ this.drawNetworkCharts(p_281427_);
});
this.minecraft.getProfiler().pop();
+
+ this.drawText(profilerfiller, p_281427_);
+ this.drawFPSCharts(profilerfiller, p_281427_);
+ this.drawNetworkCharts(profilerfiller, p_281427_);
profilerfiller.pop();
}
- protected void drawGameInformation(GuiGraphics p_281525_) {
@@ -69,7 +70,7 @@
list.add("");
boolean flag = this.minecraft.getSingleplayerServer() != null;
list.add(
@@ -176,11 +_,18 @@
@@ -190,11 +_,18 @@
+ (this.renderNetworkCharts ? " visible" : " hidden")
);
list.add("For help: press F3 + Q");
@@ -79,17 +80,17 @@
+ protected void drawGameInformation(GuiGraphics p_281525_) {
+ List<String> list = this.getGameInformation();
+ list.addAll(this.getOverlayHelp());
+ net.minecraftforge.client.ForgeHooksClient.onCustomizeDebugEvent(p_281525_, minecraft.getWindow(), minecraft.getTimer().getRealtimeDeltaTicks(), list, true);
+ net.minecraftforge.client.ForgeHooksClient.onCustomizeDebugEvent(p_281525_, minecraft.getWindow(), minecraft.getDeltaTracker().getRealtimeDeltaTicks(), list, true);
+ this.renderLines(p_281525_, list, true);
}
protected void drawSystemInformation(GuiGraphics p_281261_) {
List<String> list = this.getSystemInformation();
+ net.minecraftforge.client.ForgeHooksClient.onCustomizeDebugEvent(p_281261_, minecraft.getWindow(), minecraft.getTimer().getRealtimeDeltaTicks(), list, false);
+ net.minecraftforge.client.ForgeHooksClient.onCustomizeDebugEvent(p_281261_, minecraft.getWindow(), minecraft.getDeltaTracker().getRealtimeDeltaTicks(), list, false);
this.renderLines(p_281261_, list, false);
}
@@ -529,6 +_,7 @@
@@ -543,6 +_,7 @@
list.add("");
list.add(ChatFormatting.UNDERLINE + "Targeted Entity");
list.add(String.valueOf(BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType())));
@@ -1,10 +0,0 @@
--- a/net/minecraft/client/gui/components/toasts/ToastComponent.java
+++ b/net/minecraft/client/gui/components/toasts/ToastComponent.java
@@ -97,6 +_,7 @@
}
public void addToast(Toast p_94923_) {
+ if (net.minecraftforge.client.ForgeHooksClient.onToastAdd(p_94923_)) return;
this.queued.add(p_94923_);
}
@@ -0,0 +1,10 @@
--- a/net/minecraft/client/gui/components/toasts/ToastManager.java
+++ b/net/minecraft/client/gui/components/toasts/ToastManager.java
@@ -107,6 +_,7 @@
}
public void addToast(Toast p_360768_) {
+ if (net.minecraftforge.client.event.ForgeEventFactoryClient.onToastAdd(p_360768_)) return;
this.queued.add(p_360768_);
}
@@ -1,6 +1,30 @@
--- a/net/minecraft/client/gui/screens/LoadingOverlay.java
+++ b/net/minecraft/client/gui/screens/LoadingOverlay.java
@@ -131,6 +_,7 @@
@@ -62,6 +_,8 @@
return p_169325_ & 16777215 | p_169326_ << 24;
}
+ protected boolean renderContents(GuiGraphics gui, float alpha) { return true; }
+
@Override
public void render(GuiGraphics p_281839_, int p_282704_, int p_283650_, float p_283394_) {
int i = p_281839_.guiWidth();
@@ -100,6 +_,7 @@
f2 = 1.0F;
}
+ if (renderContents(p_281839_, f2)) {
int k2 = (int)((double)p_281839_.guiWidth() * 0.5);
int l2 = (int)((double)p_281839_.guiHeight() * 0.5);
double d1 = Math.min((double)p_281839_.guiWidth() * 0.75, (double)p_281839_.guiHeight()) * 0.25;
@@ -115,12 +_,14 @@
if (f < 1.0F) {
this.drawProgressBar(p_281839_, i / 2 - j1, l1 - 5, i / 2 + j1, l1 + 5, 1.0F - Mth.clamp(f, 0.0F, 1.0F));
}
+ }
if (f >= 2.0F) {
this.minecraft.setOverlay(null);
}
if (this.fadeOutStart == -1L && this.reload.isDone() && (!this.fadeIn || f1 >= 2.0F)) {
@@ -8,7 +32,7 @@
try {
this.reload.checkExceptions();
this.onFinish.accept(Optional.empty());
@@ -138,7 +_,6 @@
@@ -128,7 +_,6 @@
this.onFinish.accept(Optional.of(throwable));
}
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/gui/screens/PauseScreen.java
+++ b/net/minecraft/client/gui/screens/PauseScreen.java
@@ -94,6 +_,7 @@
@@ -95,6 +_,7 @@
} else {
gridlayout$rowhelper.addChild(this.openScreenButton(PLAYER_REPORTING, () -> new SocialInteractionsScreen(this)));
}
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/gui/screens/Screen.java
+++ b/net/minecraft/client/gui/screens/Screen.java
@@ -201,7 +_,7 @@
@@ -200,7 +_,7 @@
}
public void onClose() {
@@ -9,7 +9,7 @@
}
protected <T extends GuiEventListener & Renderable & NarratableEntry> T addRenderableWidget(T p_169406_) {
@@ -312,8 +_,10 @@
@@ -311,8 +_,10 @@
this.width = p_96608_;
this.height = p_96609_;
if (!this.initialized) {
@@ -20,7 +20,7 @@
} else {
this.repositionElements();
}
@@ -326,8 +_,10 @@
@@ -325,8 +_,10 @@
protected void rebuildWidgets() {
this.clearWidgets();
this.clearFocus();
@@ -31,15 +31,15 @@
}
@Override
@@ -354,6 +_,7 @@
@@ -353,6 +_,7 @@
this.renderBlurredBackground(p_297268_);
this.renderBlurredBackground();
this.renderMenuBackground(p_283688_);
+ net.minecraftforge.client.event.ForgeEventFactoryClient.onRenderScreenBackground(this, p_283688_);
}
protected void renderBlurredBackground(float p_336041_) {
@@ -463,6 +_,19 @@
protected void renderBlurredBackground() {
@@ -454,6 +_,19 @@
}
public void onFilesDrop(List<Path> p_96591_) {
@@ -14,26 +14,26 @@
int l = this.height / 4 + 48;
+ Button modButton = null;
if (this.minecraft.isDemo()) {
this.createDemoMenuOptions(l, 24);
l = this.createDemoMenuOptions(l, 24);
} else {
this.createNormalMenuOptions(l, 24);
l = this.createNormalMenuOptions(l, 24);
+ modButton = this.addRenderableWidget(Button.builder(Component.translatable("fml.menu.mods"), button -> this.minecraft.setScreen(new net.minecraftforge.client.gui.ModListScreen(this)))
+ .pos(this.width / 2 - 100, l + 24 * 2).size(98, 20).build());
+ .pos(this.width / 2 - 100, l).size(98, 20).build());
}
+ modUpdateNotification = net.minecraftforge.client.gui.TitleScreenModUpdateIndicator.init(this, modButton);
l = this.createTestWorldButton(l, 24);
SpriteIconButton spriteiconbutton = this.addRenderableWidget(
CommonButtons.language(
@@ -170,7 +_,7 @@
}).bounds(this.width / 2 - 100, p_96764_ + p_96765_ * 1, 200, 20).tooltip(tooltip).build()).active = flag;
@@ -184,7 +_,7 @@
}).bounds(this.width / 2 - 100, i = p_96764_ + p_96765_, 200, 20).tooltip(tooltip).build()).active = flag;
this.addRenderableWidget(
Button.builder(Component.translatable("menu.online"), p_325369_ -> this.minecraft.setScreen(new RealmsMainScreen(this)))
- .bounds(this.width / 2 - 100, p_96764_ + p_96765_ * 2, 200, 20)
+ .bounds(this.width / 2 + 2, p_96764_ + p_96765_ * 2, 98, 20)
- .bounds(this.width / 2 - 100, p_96764_ = i + p_96765_, 200, 20)
+ .bounds(this.width / 2 + 2, p_96764_ = i + p_96765_, 98, 20)
.tooltip(tooltip)
.build()
)
@@ -276,6 +_,7 @@
@@ -293,6 +_,7 @@
if ((i & -67108864) != 0) {
super.render(p_282860_, p_281753_, p_283539_, p_282628_);
this.logoRenderer.renderLogo(p_282860_, this.width, f);
@@ -41,21 +41,20 @@
if (this.splash != null && !this.minecraft.options.hideSplashTexts().get()) {
this.splash.render(p_282860_, this.width, this.font, i);
}
@@ -291,11 +_,19 @@
@@ -308,10 +_,18 @@
s = s + I18n.get("menu.modded");
}
- p_282860_.drawString(this.font, s, 2, this.height - 10, 16777215 | i);
+ net.minecraftforge.internal.BrandingControl.forEachLine(true, true, (brdline, brd) ->
+ net.minecraftforge.internal.BrandingControl.forEachLine(true, true, (brd, brdline) ->
+ p_282860_.drawString(this.font, brd, 2, this.height - ( 10 + brdline * (this.font.lineHeight + 1)), 16777215 | i)
+ );
+
+ net.minecraftforge.internal.BrandingControl.forEachAboveCopyrightLine((brdline, brd) ->
+ net.minecraftforge.internal.BrandingControl.forEachAboveCopyrightLine((brd, brdline) ->
+ p_282860_.drawString(this.font, brd, this.width - font.width(brd), this.height - (10 + (brdline + 1) * ( this.font.lineHeight + 1)), 16777215 | i)
+ );
+
if (this.realmsNotificationsEnabled() && f >= 1.0F) {
RenderSystem.enableDepthTest();
this.realmsNotificationsScreen.render(p_282860_, p_281753_, p_283539_, p_282628_);
}
+ if (f >= 1.0f) this.modUpdateNotification.render(p_282860_, p_281753_, p_283539_, p_282628_);
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/gui/screens/advancements/AdvancementTab.java
+++ b/net/minecraft/client/gui/screens/advancements/AdvancementTab.java
@@ -37,6 +_,7 @@
@@ -38,6 +_,7 @@
private int maxY = Integer.MIN_VALUE;
private float fade;
private boolean centered;
@@ -8,7 +8,7 @@
public AdvancementTab(
Minecraft p_97145_, AdvancementsScreen p_97146_, AdvancementTabType p_97147_, int p_97148_, AdvancementNode p_297568_, DisplayInfo p_97150_
@@ -53,6 +_,15 @@
@@ -54,6 +_,15 @@
this.addWidget(this.root, p_297568_.holder());
}
@@ -24,7 +24,7 @@
public AdvancementTabType getType() {
return this.type;
}
@@ -146,8 +_,8 @@
@@ -147,8 +_,8 @@
return null;
} else {
for (AdvancementTabType advancementtabtype : AdvancementTabType.values()) {
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/gui/screens/advancements/AdvancementTabType.java
+++ b/net/minecraft/client/gui/screens/advancements/AdvancementTabType.java
@@ -69,6 +_,7 @@
@@ -70,6 +_,7 @@
5
);
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/gui/screens/advancements/AdvancementsScreen.java
+++ b/net/minecraft/client/gui/screens/advancements/AdvancementsScreen.java
@@ -49,6 +_,7 @@
@@ -51,6 +_,7 @@
@Nullable
private AdvancementTab selectedTab;
private boolean isScrolling;
@@ -8,7 +8,7 @@
public AdvancementsScreen(ClientAdvancements p_97340_) {
this(p_97340_, null);
@@ -62,6 +_,19 @@
@@ -64,6 +_,19 @@
@Override
protected void init() {
@@ -28,7 +28,7 @@
this.layout.addTitleHeader(TITLE, this.font);
this.tabs.clear();
this.selectedTab = null;
@@ -106,7 +_,7 @@
@@ -108,7 +_,7 @@
int j = (this.height - 140) / 2;
for (AdvancementTab advancementtab : this.tabs.values()) {
@@ -37,8 +37,8 @@
this.advancements.setSelectedTab(advancementtab.getRootNode().holder(), true);
break;
}
@@ -180,10 +_,12 @@
p_283395_.blit(WINDOW_LOCATION, p_281890_, p_282532_, 0, 0, 252, 140);
@@ -181,10 +_,12 @@
p_283395_.blit(RenderType::guiTextured, WINDOW_LOCATION, p_281890_, p_282532_, 0.0F, 0.0F, 252, 140, 256, 256);
if (this.tabs.size() > 1) {
for (AdvancementTab advancementtab : this.tabs.values()) {
+ if (advancementtab.getPage() == tabPage)
@@ -50,7 +50,7 @@
advancementtab1.drawIcon(p_283395_, p_281890_, p_282532_);
}
}
@@ -203,6 +_,7 @@
@@ -202,6 +_,7 @@
if (this.tabs.size() > 1) {
for (AdvancementTab advancementtab : this.tabs.values()) {
Loaded 100 of 1321 files, more files were not shown because too many files have changed in this diff. Show more