Compare commits

...
56 Commits
Author SHA1 Message Date
Jonathing c047cf136c 1.21.3 RB 1
https://forums.minecraftforge.net/topic/154393-forge-531-minecraft-1213/
2025-03-01 16:56:32 -05:00
Jonathing f4287ecc81 Add missed license headers (#10475) 2025-03-01 21:35:03 +00:00
JonathingandLexManos 364a1e737f Speed up mod annotation scanning by ~30% (#10466)
Co-authored-by: LexManos <[email protected]>
2025-03-01 21:34:40 +00:00
Jonathing 45a977bb33 [1.21.3] Bump dependencies, most notably EventBus (#10385)
- CoreMods 5.2.4 -> 5.2.6
- EventBus 6.2.15 -> 6.2.27
    - Reduced memory usage
    - Further performance improvements
    - Fixed EventBus#39, unregistered listeners are now immediately reclaimable by the GC
- Apache MavenArtifact 3.8.5 -> 3.8.8
- NightConfig 3.7.3 -> 3.7.4
2025-02-12 17:51:46 -08:00
Jonathing ab00c907e7 Add fast graphics render type to block model jsons (#10395)
Make modded leaves behave like vanilla leaves by default (Fixes #10389)
2025-02-12 17:39:27 -08:00
979abb2b93 Re-introduce ItemStack Capabilities (#10362)
Co-authored-by: RealMangoRage <[email protected]>
Co-authored-by: LexManos <[email protected]>
2025-02-12 17:30:29 -08:00
Jonathing 056360b050 Re-introduce IForgeItem.damageItem when an item takes damage, Fixes #10344 (#10372) 2025-02-12 17:29:44 -08:00
Jonathing 960fce505c Add updated tag context to AddReloadListenerEvent (#10416)
Fixes using custom tags in GlobalLootModifiers.
2025-02-12 17:21:30 -08:00
JonathingandLexManos ea597ff45a Fix redstone update order not matching vanilla. (#10392)
Fix redstone updates for comparators going up/down incorrectly. Related: #9973
Fix game test structure rotations being inconsistant (#10391)

Co-authored-by: LexManos <[email protected]>
2025-02-12 17:11:56 -08:00
Jonathing f35dce7eb3 Fix corrupted and invalidly symlinked worlds crashing on level select (#10415) 2025-02-12 17:03:06 -08:00
Jonathing ef224bd2b8 Fix ModelDiscovery using Item components early (#10418) 2025-02-12 17:02:52 -08:00
Jonathing 0749f49b6c Fix forced chunks never being ticked by the server (#10365) 2025-02-12 13:37:21 -08:00
Jonathing 5c508efaa9 Fix shield disabling being completely ignored (#10317) 2025-01-27 15:28:07 -08:00
JonathingandJoseph T. McQuigg dbafe039d8 Fix issues in VillagerTrades.EmeraldsForVillagerTypeItem related to custom Villager Types (#10357)
Co-authored-by: Joseph T. McQuigg <[email protected]>
2025-01-27 15:27:42 -08:00
JonathingandLexManos b36ee22925 GameTestHelper additions (#10339)
Co-authored-by: LexManos <[email protected]>
2025-01-27 09:44:26 -08:00
Jonathing 287ee7b0b5 Update Gradle to 8.12.1 (#10353)
IntelliJ download sources in MDK
2025-01-27 09:44:11 -08:00
Jonathing 5f67621477 Fix JOpt Simple by bumping ModLaucnher (#10348) 2025-01-27 09:43:49 -08:00
Jonathing f237e7a8c3 Fix incorrect method reference in TntBlock.explode() (#10327) 2025-01-25 18:15:54 +00:00
Jonathing a54420de36 Fix JOpt Simple needing a strict version requirement declaration (#10312) 2025-01-25 18:15:36 +00:00
Jonathing 65dbdc8eb0 Bump dependencies (#10275)
- EventBus 6.2.8 -> 6.2.15
- Bootstrap 2.1.3 -> 2.1.8
- SecureModules 2.2.20 -> 2.2.21
- AccessTransformers 8.2.0 -> 8.2.1
- ModLauncher 10.2.2 -> 10.2.3
These bumps include further cleanup and optimisations.
2025-01-04 16:17:38 +00:00
Jonathing d42979f620 Simplify Title Screen Brandings (#10288) 2025-01-02 21:34:03 -08:00
Jonathing ac56afc99c Clean and fix Forge's internal coremods, fixes finalizeSpawn hook. 2024-12-30 14:36:16 -08:00
Gabriel Harris-Rouquette 81b7bfe4ea Update jline for better darwin/arm64 support (#10281) 2024-12-30 09:46:25 -08:00
Jonathing 4cbcf64ce1 Bump CoreMods to 5.2.4 (#10267) 2024-12-23 15:36:20 -08:00
JonathingandPaint_Ninja 29b763a0c3 Simplify memory usage display on loading screen (#10237)
Co-authored-by: Paint_Ninja <[email protected]>
2024-12-23 12:52:39 -08:00
JonathingandPaint_Ninja 52f41a4448 Deprecate @ObjectHolder, add a couple of fast-paths (#10232)
Co-authored-by: Paint_Ninja <[email protected]>
2024-12-23 12:51:51 -08:00
JonathingandPaint_Ninja 597409e2f2 Skip Vanilla classes for the CapabilityTokenSubclass transformer (#10225)
Co-authored-by: Paint_Ninja <[email protected]>
2024-12-23 12:42:07 -08:00
JonathingandPaint_Ninja 21fbd72585 Skip Forge classes in the RuntimeEnumExtender transformer (#10220)
Mod classes are still transformed as usual

Co-authored-by: Paint_Ninja <[email protected]>
2024-12-23 12:30:09 -08:00
JonathingandPaint_Ninja 83a09f7584 Skip processing Forge classes in RuntimeDistCleaner (#10212)
Co-authored-by: Paint_Ninja <[email protected]>
2024-12-23 12:19:40 -08:00
Jonathing 0e864fbe6e Fix NightConfig FileWatcher causing dedicated server to hang when exiting (#10214) 2024-12-23 12:09:22 -08:00
Jonathing 2fd8900c01 Allow mipmap lowering to be disabled (#10256) 2024-12-23 11:57:00 -08:00
LexManos e643d0a11f Remove debug code. Closes #10182 2024-11-21 11:57:00 -08:00
LexManos 5efb8d3f12 Pass the Holder<Enchantment> to Item.canApplyAtEnchantingTable. Closes #10181 2024-11-21 09:52:32 -08:00
LexManos 70537542d1 Fix incorrect patch 2024-11-20 21:04:24 -08:00
LexManos bd4b059c2b Change vanilla registry order to last-seen. Closes #10179 2024-11-20 19:32:28 -08:00
LexManos e0d2c68c5b Call IUnbakedGeometry.bake from BlockModel Closes #10178 2024-11-18 12:30:22 -08:00
LexManos 86d4bda32e Cleanup some warnings 2024-11-18 12:27:16 -08:00
LexManos 98b90f26bc Fix not being able to swim in lava 2024-11-14 23:12:26 -08:00
LexManos 4e7c471fa3 Move mdk back to com/example/examplemod to align with what people expect. 2024-11-14 23:12:11 -08:00
LexManos ec6f9fc6e7 Move MDK example to test sourceset and fix Item/Block registration example. Closes #10139 2024-11-14 14:24:21 -08:00
LexManos 427fef1d4b Added support for using optional tags on Forge registries
Add DeferredRegister.key(name) helper function to make ResourceKey creation easier
Fix TagManager not binding tags
Fix Unbound Tag Exception when modders register tags during Register events Closes #10154
2024-11-14 14:23:00 -08:00
LexManos 83594b0b0a Fix empty tag conditional recipe test 2024-11-14 14:08:00 -08:00
LexManos b76064e57a Fixed missed call to getFriction hook in AbstractBoat 2024-11-12 09:33:05 -08:00
Martijn van den Brand b2bf70c7f1 Pass player argument when firing OnDatapackSyncEvent (#10170) 2024-11-11 20:39:55 -08:00
LexManos 384286a453 Cleanup lifecycle transitions
Fix mod ordering for multi-mod jars
Make dependency resolution issues easier to debug/diagnose
2024-11-11 03:04:23 -08:00
Jonathing d93b7db69a Bump CoreMods to 5.2 (#10156)
Full Changelog:
https://gist.github.com/Jonathing/c3ad28b2a048ac839a7baba5417ee870

The key features are:
- ES6 language support
- Thoroughly updated ASMAPI, with full documentation
- Bug fixes (some optional for backwards-compatibility)
- Partial internal code cleanup
2024-11-08 12:28:39 -08:00
StavWasPlayZ bdbde4bb0f Fix PlayerRenderer#render invoking RenderLivingEvent and not RenderPlayerEvent (#10167) 2024-11-07 20:03:37 -08:00
LexManos e2a68ad903 Fix Powered Rails acting like Activator Rails due to misaligned patch 2024-11-06 23:38:09 -08:00
LexManos 46cfe50108 Properly allow normal mods.toml mods to specify the module names and use a full module-info.
Make FMLModContainer read and apply Add-Opens and Add-Exports manifest entries from mod files.
Forge is now the `net.minecraftforge.forge` module instead of `forge`

https://forums.minecraftforge.net/topic/153333-proper-java-module-support-in-forge-mods/
2024-11-04 16:55:10 -08:00
LexManos 4b423a3087 Fix LootTableLoadEvent not being fired. Closes #10144 2024-10-28 16:08:03 -07:00
LexManos 7d0b07e858 Fix items with use durations of 0 being repeatedly used when use button is held down. Fixed #10150 2024-10-28 14:54:39 -07:00
yurisuika 40220600b0 Add AT for BlockEntityType constructor. (#10149) 2024-10-27 18:05:19 -07:00
Jonathing 0c7a0e927c Add c:stripped_logs and c:stripped_woods tags (#10146) 2024-10-27 02:36:36 +00:00
Jonathing 60e8157b9a Bump CoreMods and ASM (#10145)
CoreMods 5.1.13 changelog: https://gist.github.com/Jonathing/905d142447ecef670526ae27243adbba
2024-10-27 02:34:06 +00:00
Paint_Ninja b8f5e4553b Remove redundant rendering options (#10140) 2024-10-26 20:24:18 +01:00
Ven de587eb50a Fix pause menu having a black background, reverts PR #10115 2024-10-26 12:22:19 -07:00
187 changed files with 4785 additions and 1304 deletions

No files matched your search

@@ -3,6 +3,7 @@ package net.minecraftforge.forge.tasks
import groovy.transform.CompileStatic
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Internal
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.MethodNode
@@ -12,13 +13,19 @@ abstract class BytecodePredicateFinder extends BytecodeFinder {
@Internal
abstract Property<Closure<Boolean>> getPredicate()
private final Set<String> matches = new HashSet<>()
private final Map<String, List<String>> matches = new TreeMap<>(Comparator.naturalOrder())
@Override
protected process(ClassNode parent, MethodNode node) {
for (final current : node.instructions) {
if (predicate.get().call(parent, node, current)) {
matches.add(parent.name)
var methods = matches.compute(parent.name, { k, v -> v ?: new ArrayList<>() })
// only add non-synthetic methods
if ((node.access & Opcodes.ACC_SYNTHETIC) == 0) {
methods.add(node.name + node.desc)
}
return
}
}
@@ -27,6 +34,15 @@ abstract class BytecodePredicateFinder extends BytecodeFinder {
@Internal
@Override
protected Object getData() {
return matches.toSorted(Comparator.naturalOrder()) ?: {throw new RuntimeException('Failed to find any targets, please ensure that method names and descriptors are correct.')}()
var array = new ArrayList<Object>()
matches.forEach { c, m ->
{
array.add(
'class': c,
'methods': m
)
}
}
return array ?: { throw new RuntimeException('Failed to find any targets, please ensure that method names and descriptors are correct.') }()
}
}
+6 -3
View File
@@ -118,7 +118,7 @@ dependencies {
bootstrap(libs.unsafe) // Needed by securemodules
bootstrap(libs.bundles.asm) // Needed by securemodules
implementation(libs.jopt.simple) { version { strictly '5.0.4' } }
implementation(libs.jopt.simple)
installer(libs.bootstrap)
installer(libs.bootstrap.api) // Needed by securemodules
@@ -381,13 +381,13 @@ tasks.register('extractInheritance', ExtractInheritance) {
}
}
tasks.register("findFinalizeSpawnTargets", BytecodePredicateFinder) {
jar = createJoinedSRG.output
jar = createClientOfficial.output
output = rootProject.file('src/main/resources/coremods/finalize_spawn_targets.json')
predicate = {
parent, node, insn ->
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.name == 'finalizeSpawn'
&& 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;';
}
}
@@ -923,6 +923,9 @@ tasks.register('mdkZip', Zip) {
rename 'gitignore\\.txt', '.gitignore'
rename 'gitattributes\\.txt', '.gitattributes'
}
from(rootProject.file('src/test/java/com/example/examplemod/')) {
into('src/main/java/com/example/examplemod/')
}
from(rootProject.file('src/test/generated/mdk_datagen/')) {
into('src/main/resources/')
exclude '**/.cache/'
+8
View File
@@ -75,3 +75,11 @@ eclipse {
// Run when importing the project
synchronizationTasks generateResources, copyEclipseSettings, eclipseClasspath, eclipseProject
}
idea {
module {
// IntelliJ IDEA does not do this by itself anymore...
downloadJavadoc = true
downloadSources = true
}
}
+25 -6
View File
@@ -2,16 +2,15 @@
MinecraftForge
=============
[![Stable Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Stable&prefix=1.20.6-&query=$.promos["1.20.6-recommended"]&color=brightgreen&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc3RhciBmYS13LTE4IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1pY29uPSJzdGFyIiBkYXRhLXByZWZpeD0iZmFzIiBmb2N1c2FibGU9ImZhbHNlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjU5LjMgMTcuOEwxOTQgMTUwLjIgNDcuOSAxNzEuNWMtMjYuMiAzLjgtMzYuNyAzNi4xLTE3LjcgNTQuNmwxMDUuNyAxMDMtMjUgMTQ1LjVjLTQuNSAyNi4zIDIzLjIgNDYgNDYuNCAzMy43TDI4OCA0MzkuNmwxMzAuNyA2OC43YzIzLjIgMTIuMiA1MC45LTcuNCA0Ni40LTMzLjdsLTI1LTE0NS41IDEwNS43LTEwM2MxOS0xOC41IDguNS01MC44LTE3LjctNTQuNkwzODIgMTUwLjIgMzE2LjcgMTcuOGMtMTEuNy0yMy42LTQ1LjYtMjMuOS01Ny40IDB6IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)][Download]
[![Latest Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Latest&prefix=1.20.6-&query=$.promos["1.20.6-latest"]&color=blue&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtYnVnIGZhLXctMTYiIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLWljb249ImJ1ZyIgZGF0YS1wcmVmaXg9ImZhcyIgZm9jdXNhYmxlPSJmYWxzZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUxMS45ODggMjg4LjljLS40NzggMTcuNDMtMTUuMjE3IDMxLjEtMzIuNjUzIDMxLjFINDI0djE2YzAgMjEuODY0LTQuODgyIDQyLjU4NC0xMy42IDYxLjE0NWw2MC4yMjggNjAuMjI4YzEyLjQ5NiAxMi40OTcgMTIuNDk2IDMyLjc1OCAwIDQ1LjI1NS0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDBsLTU0LjczNi01NC43MzZDMzQ1Ljg4NiA0NjcuOTY1IDMxNC4zNTEgNDgwIDI4MCA0ODBWMjM2YzAtNi42MjctNS4zNzMtMTItMTItMTJoLTI0Yy02LjYyNyAwLTEyIDUuMzczLTEyIDEydjI0NGMtMzQuMzUxIDAtNjUuODg2LTEyLjAzNS05MC42MzYtMzIuMTA4bC01NC43MzYgNTQuNzM2Yy0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDAtMTIuNDk2LTEyLjQ5Ny0xMi40OTYtMzIuNzU4IDAtNDUuMjU1bDYwLjIyOC02MC4yMjhDOTIuODgyIDM3OC41ODQgODggMzU3Ljg2NCA4OCAzMzZ2LTE2SDMyLjY2NkMxNS4yMyAzMjAgLjQ5MSAzMDYuMzMuMDEzIDI4OC45LS40ODQgMjcwLjgxNiAxNC4wMjggMjU2IDMyIDI1Nmg1NnYtNTguNzQ1bC00Ni42MjgtNDYuNjI4Yy0xMi40OTYtMTIuNDk3LTEyLjQ5Ni0zMi43NTggMC00NS4yNTUgMTIuNDk4LTEyLjQ5NyAzMi43NTgtMTIuNDk3IDQ1LjI1NiAwTDE0MS4yNTUgMTYwaDIyOS40ODlsNTQuNjI3LTU0LjYyN2MxMi40OTgtMTIuNDk3IDMyLjc1OC0xMi40OTcgNDUuMjU2IDAgMTIuNDk2IDEyLjQ5NyAxMi40OTYgMzIuNzU4IDAgNDUuMjU1TDQyNCAxOTcuMjU1VjI1Nmg1NmMxNy45NzIgMCAzMi40ODQgMTQuODE2IDMxLjk4OCAzMi45ek0yNTcgMGMtNjEuODU2IDAtMTEyIDUwLjE0NC0xMTIgMTEyaDIyNEMzNjkgNTAuMTQ0IDMxOC44NTYgMCAyNTcgMHoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=
[![Stable Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Stable&prefix=1.21.3-&query=$.promos["1.21.3-recommended"]&color=brightgreen&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc3RhciBmYS13LTE4IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1pY29uPSJzdGFyIiBkYXRhLXByZWZpeD0iZmFzIiBmb2N1c2FibGU9ImZhbHNlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjU5LjMgMTcuOEwxOTQgMTUwLjIgNDcuOSAxNzEuNWMtMjYuMiAzLjgtMzYuNyAzNi4xLTE3LjcgNTQuNmwxMDUuNyAxMDMtMjUgMTQ1LjVjLTQuNSAyNi4zIDIzLjIgNDYgNDYuNCAzMy43TDI4OCA0MzkuNmwxMzAuNyA2OC43YzIzLjIgMTIuMiA1MC45LTcuNCA0Ni40LTMzLjdsLTI1LTE0NS41IDEwNS43LTEwM2MxOS0xOC41IDguNS01MC44LTE3LjctNTQuNkwzODIgMTUwLjIgMzE2LjcgMTcuOGMtMTEuNy0yMy42LTQ1LjYtMjMuOS01Ny40IDB6IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)][Download]
[![Latest Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Latest&prefix=1.21.3-&query=$.promos["1.21.3-latest"]&color=blue&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtYnVnIGZhLXctMTYiIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLWljb249ImJ1ZyIgZGF0YS1wcmVmaXg9ImZhcyIgZm9jdXNhYmxlPSJmYWxzZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUxMS45ODggMjg4LjljLS40NzggMTcuNDMtMTUuMjE3IDMxLjEtMzIuNjUzIDMxLjFINDI0djE2YzAgMjEuODY0LTQuODgyIDQyLjU4NC0xMy42IDYxLjE0NWw2MC4yMjggNjAuMjI4YzEyLjQ5NiAxMi40OTcgMTIuNDk2IDMyLjc1OCAwIDQ1LjI1NS0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDBsLTU0LjczNi01NC43MzZDMzQ1Ljg4NiA0NjcuOTY1IDMxNC4zNTEgNDgwIDI4MCA0ODBWMjM2YzAtNi42MjctNS4zNzMtMTItMTItMTJoLTI0Yy02LjYyNyAwLTEyIDUuMzczLTEyIDEydjI0NGMtMzQuMzUxIDAtNjUuODg2LTEyLjAzNS05MC42MzYtMzIuMTA4bC01NC43MzYgNTQuNzM2Yy0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDAtMTIuNDk2LTEyLjQ5Ny0xMi40OTYtMzIuNzU4IDAtNDUuMjU1bDYwLjIyOC02MC4yMjhDOTIuODgyIDM3OC41ODQgODggMzU3Ljg2NCA4OCAzMzZ2LTE2SDMyLjY2NkMxNS4yMyAzMjAgLjQ5MSAzMDYuMzMuMDEzIDI4OC45LS40ODQgMjcwLjgxNiAxNC4wMjggMjU2IDMyIDI1Nmg1NnYtNTguNzQ1bC00Ni42MjgtNDYuNjI4Yy0xMi40OTYtMTIuNDk3LTEyLjQ5Ni0zMi43NTggMC00NS4yNTUgMTIuNDk4LTEyLjQ5NyAzMi43NTgtMTIuNDk3IDQ1LjI1NiAwTDE0MS4yNTUgMTYwaDIyOS40ODlsNTQuNjI3LTU0LjYyN2MxMi40OTgtMTIuNDk3IDMyLjc1OC0xMi40OTcgNDUuMjU2IDAgMTIuNDk2IDEyLjQ5NyAxMi40OTYgMzIuNzU4IDAgNDUuMjU1TDQyNCAxOTcuMjU1VjI1Nmg1NmMxNy45NzIgMCAzMi40ODQgMTQuODE2IDMxLjk4OCAzMi45ek0yNTcgMGMtNjEuODU2IDAtMTEyIDUwLjE0NC0xMTIgMTEyaDIyNEMzNjkgNTAuMTQ0IDMxOC44NTYgMCAyNTcgMHoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=
)][Download] [![Discord](https://img.shields.io/discord/313125603924639766.svg?color=%237289da&label=Discord&logo=discord&logoColor=%237289da)][Discord] [![Support](https://img.shields.io/badge/Patreon-Support-orange.svg?logo=Patreon)][Patreon]
Forge is a free, open-source modding API all of your favourite mods use!
| Version | Support |
|---------| ------------- |
| 1.20.x | Active |
| 1.19.x | Legacy |
| Version | Support |
|--------------|-----------------------------|
| All versions | [Supported][Support-Policy] |
* [Download]
* [Forum]
@@ -31,6 +30,19 @@ You can download the installer for the *Recommended Build* or the
For support and questions, visit [the Support Forum][Support-Forum] or [the Forge Discord server][Discord].
## Required Java Versions
We require the version of Java that Minecraft requires, which is currently:
| Minecraft | Java |
|---------------|-------------------|
| 1.20.6+ | [21][Adoptium-21] |
| 1.18 - 1.20.4 | [17][Adoptium-17] |
| 1.17.1 | [16][Adoptium-16] |
| 1.16.5- | [8][Adoptium-8] |
**If you are on Windows and have difficulty using Java to launch JAR files, consider using [Jarfix by Johann Löfflmann][Jarfix].**
# Creating Mods
[See the "Getting Started" section in the Forge Documentation][Getting-Started].
@@ -63,8 +75,15 @@ Please ensure you have a valid email address associated with your GitHub account
[Download]: https://files.minecraftforge.net/
[Adoptium-21]: https://adoptium.net/temurin/releases/?version=21
[Adoptium-17]: https://adoptium.net/temurin/releases/?version=17
[Adoptium-16]: https://adoptium.net/temurin/releases/?version=16
[Adoptium-8]: https://adoptium.net/temurin/releases/?version=8
[Jarfix]: https://johann.loefflmann.net/en/software/jarfix/index.html
[Forum]: https://forums.minecraftforge.net/
[Support-Forum]: https://forums.minecraftforge.net/forum/18-support-bug-reports/
[Support-Policy]: https://forums.minecraftforge.net/topic/144690-new-tiered-support-policy
[Discord]: https://discord.minecraftforge.net/
@@ -68,13 +68,15 @@ public interface IModLoadingState {
* @return a transition task for this state
* @see #buildTransition(Executor, Executor, ProgressMeter, Function, Function)
*/
default <T extends Event & IModBusEvent>
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar) {
default <T extends Event & IModBusEvent> Optional<CompletableFuture<Void>> buildTransition(
final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar
) {
return buildTransition(syncExecutor, parallelExecutor, progressBar,
e -> CompletableFuture.runAsync(() -> {}, e),
e -> CompletableFuture.runAsync(() -> {}, e));
e -> CompletableFuture.completedFuture(null),
e -> CompletableFuture.completedFuture(null)
);
}
/**
@@ -9,8 +9,6 @@ import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.BiFunction;
@@ -18,103 +16,67 @@ import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
@SuppressWarnings("unchecked")
import org.jetbrains.annotations.Nullable;
public interface IModStateTransition {
/** Magic value to allow me to optimize the futures list by ignoring the default value without making old methods nullable. */
public static final BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> NULL_HOOK = (e, g) -> CompletableFuture.completedFuture(null);
static IModStateTransition buildNoopTransition() {
return new NoopTransition();
return ModStateTransitionHelper.NOOP;
}
default <T extends Event & IModBusEvent>
CompletableFuture<Void> build(final String name,
final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask) {
List<CompletableFuture<Void>> futures = new ArrayList<>();
this.eventFunctionStream().get()
.map(f -> (EventGenerator<T>) f)
.reduce((head, tail) -> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, futures, progressBar, head, ModLoadingStage::currentState, tail))
.ifPresent(last -> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, futures, progressBar, last, nextModLoadingStage(), null));
final CompletableFuture<Void> preSyncTaskCF = preSyncTask.apply(syncExecutor);
final CompletableFuture<Void> eventDispatchCF = ModList.gather(futures).thenCompose(ModList::completableFutureFromExceptionList);
final CompletableFuture<Void> postEventDispatchCF = preSyncTaskCF
.thenApplyAsync(n -> {
progressBar.label(progressBar.name() + ": dispatching "+name);
return null;
}, parallelExecutor)
.thenComposeAsync(n -> eventDispatchCF, parallelExecutor)
.thenApply(r -> {
postSyncTask.apply(syncExecutor);
return null;
});
return this.finalActivityGenerator().apply(syncExecutor, postEventDispatchCF);
default CompletableFuture<Void> build(
final String name,
final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask
) {
return ModStateTransitionHelper.build(this, name, syncExecutor, parallelExecutor, progressBar, preSyncTask, postSyncTask);
}
default BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextModLoadingStage() {
return ModLoadingStage::nextState;
}
private <T extends Event & IModBusEvent>
EventGenerator<T> addCompletableFutureTaskForModDispatch(final Executor syncExecutor,
final Executor parallelExecutor,
final List<CompletableFuture<Void>> completableFutures,
final ProgressMeter progressBar,
final EventGenerator<T> eventGenerator,
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextState,
final EventGenerator<T> nextGenerator) {
final Executor selectedExecutor = threadSelector().apply(syncExecutor, parallelExecutor);
var preDispatchHook = (BiFunction<Executor, EventGenerator<T>, CompletableFuture<Void>>) preDispatchHook();
completableFutures.add(preDispatchHook.apply(selectedExecutor, eventGenerator));
completableFutures.add(ModList.get().futureVisitor(eventGenerator, progressBar, nextState).apply(threadSelector().apply(syncExecutor, parallelExecutor)));
var postDispatchHook = (BiFunction<Executor, EventGenerator<T>, CompletableFuture<Void>>) postDispatchHook();
completableFutures.add(postDispatchHook.apply(selectedExecutor, eventGenerator));
return nextGenerator;
/**
* This used to allow you to fire multiple events during the transition. However, in doing so it would cause issues with the default
* ModContainer's event handlers causing issues such as mod classes being constructed multiple times.
*/
@Deprecated(since = "1.21.3", forRemoval = true)
default Supplier<Stream<EventGenerator<?>>> eventFunctionStream() {
return () -> Stream.ofNullable(eventFunction());
}
@Nullable
default <T extends Event & IModBusEvent> EventGenerator<T> eventFunction() {
return null;
}
Supplier<Stream<EventGenerator<?>>> eventFunctionStream();
ThreadSelector threadSelector();
BiFunction<Executor, CompletableFuture<Void>, CompletableFuture<Void>> finalActivityGenerator();
BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> preDispatchHook();
BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> postDispatchHook();
/**
* I think this was meant as a way to do some things for each mod container beforge/after the transition had been sent to the container.
* However, the Future returned by this was never linked to the main transition future in any way. Which means that it was run
* in parallel and couldn't guarantee the state of the ModContainer.
* <p>
* Plus all existing code that I could find returned a completedFuture, so I don't think anyone ever used this.
* <p>
* If I were to add this back it would be a CompletableFuture wrap(ModContainer, CompletableFuture)
* <p>
* Added magic value NULL_HOOK to allow me to optimize the futures list by ignoring the default value without making this method nullable.
*/
@Deprecated(since = "1.21.3", forRemoval = true)
default BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> preDispatchHook() { return NULL_HOOK; }
@Deprecated(since = "1.21.3", forRemoval = true)
default BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> postDispatchHook() { return NULL_HOOK; }
interface EventGenerator<T extends Event & IModBusEvent> extends Function<ModContainer, T> {
static <FN extends Event & IModBusEvent> EventGenerator<FN> fromFunction(Function<ModContainer, FN> fn) {
return fn::apply;
}
}
}
record NoopTransition() implements IModStateTransition {
@Override
public Supplier<Stream<EventGenerator<?>>> eventFunctionStream() {
return Stream::of;
}
@Override
public ThreadSelector threadSelector() {
return ThreadSelector.SYNC;
}
@Override
public BiFunction<Executor, CompletableFuture<Void>, CompletableFuture<Void>> finalActivityGenerator() {
return (e, t) -> t.thenApplyAsync(Function.identity(), e);
}
@Override
public BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> preDispatchHook() {
return (t, f)-> CompletableFuture.completedFuture(null);
}
@Override
public BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> postDispatchHook() {
return (t, f)-> CompletableFuture.completedFuture(null);
}
}
@@ -9,18 +9,15 @@ import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.config.IConfigEvent;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import net.minecraftforge.forgespi.language.IModInfo;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.BiFunction;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -33,13 +30,8 @@ import java.util.function.Supplier;
* a mechanism by which we can wrap actual mod code so that the loader and other
* facilities can treat mods at arms length.
* </p>
*
* @author cpw
*
*/
public abstract class ModContainer
{
public abstract class ModContainer {
protected final String modId;
protected final String namespace;
protected final IModInfo modInfo;
@@ -48,11 +40,14 @@ public abstract class ModContainer
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")
final Set<ModContainer> dependencies = new HashSet<>();
/**
* If you want to handle the event, override {@link #dispatchConfigEvent(IConfigEvent)}
*/
@Deprecated(since = "1.21.3", forRemoval = true)
protected Optional<Consumer<IConfigEvent>> configHandler = Optional.empty();
public ModContainer(IModInfo info)
{
public ModContainer(IModInfo info) {
this.modId = info.getModId();
// TODO: Currently not reading namespace from configuration..
this.namespace = this.modId;
@@ -82,58 +77,35 @@ public abstract class ModContainer
/**
* Errored container state, used for filtering. Does nothing.
*/
ModContainer()
{
ModContainer() {
this.modLoadingStage = ModLoadingStage.ERROR;
modId = "BROKEN";
namespace = "BROKEN";
modInfo = null;
}
/**
* @return the modid for this mod
*/
public final String getModId()
{
public final String getModId() {
return modId;
}
/**
* @return the resource prefix for the mod
*/
public final String getNamespace()
{
public final String getNamespace() {
return namespace;
}
/**
* @return The current loading stage for this mod
*/
public ModLoadingStage getCurrentState()
{
public ModLoadingStage getCurrentState() {
return modLoadingStage;
}
public static <T extends Event & IModBusEvent> CompletableFuture<Void> buildTransitionHandler(
final ModContainer target,
final IModStateTransition.EventGenerator<T> eventGenerator,
final ProgressMeter progressBar,
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> stateChangeHandler,
final Executor executor) {
return CompletableFuture
.runAsync(() -> {
ModLoadingContext.get().setActiveContainer(target);
target.activityMap.getOrDefault(target.modLoadingStage, ()->{}).run();
target.acceptEvent(eventGenerator.apply(target));
}, executor)
.whenComplete((mc, exception) -> {
target.modLoadingStage = stateChangeHandler.apply(target.modLoadingStage, exception);
progressBar.increment();
ModLoadingContext.get().setActiveContainer(null);
});
}
public IModInfo getModInfo()
{
public IModInfo getModInfo() {
return modInfo;
}
@@ -142,8 +114,7 @@ public abstract class ModContainer
return Optional.ofNullable((T)extensionPoints.getOrDefault(point,()-> null).get());
}
public <T extends Record & IExtensionPoint<T>> void registerExtensionPoint(Class<? extends IExtensionPoint<T>> point, Supplier<T> extension)
{
public <T extends Record & IExtensionPoint<T>> void registerExtensionPoint(Class<? extends IExtensionPoint<T>> point, Supplier<T> extension) {
extensionPoints.put(point, extension);
}
@@ -5,9 +5,6 @@
package net.minecraftforge.fml;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.language.ModFileScanData;
@@ -15,21 +12,15 @@ 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 java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -43,20 +34,21 @@ public class ModList {
private static ModList INSTANCE;
private final List<IModFileInfo> modFiles;
private final List<IModInfo> sortedList;
private final Map<String, ModFileInfo> fileById;
private final Map<String, IModFileInfo> fileById;
private List<ModContainer> mods;
private Map<String, ModContainer> indexedMods;
private List<ModFileScanData> modFileScanData;
private List<ModContainer> sortedContainers;
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).
collect(Collectors.toList());
this.fileById = this.modFiles.stream().map(IModFileInfo::getMods).flatMap(Collection::stream).
map(ModInfo.class::cast).
collect(Collectors.toMap(ModInfo::getModId, ModInfo::getOwningFile));
this.modFiles = modFiles.stream().map(ModFile::getModFileInfo).toList();
this.sortedList = sortedList.stream().map(IModInfo.class::cast).toList();
var byId = new HashMap<String, IModFileInfo>();
for (var file : this.modFiles) {
for (var mod : file.getMods())
byId.put(mod.getModId(), mod.getOwningFile());
}
this.fileById = Collections.unmodifiableMap(byId);
CrashReportCallables.registerCrashCallable("Mod List", this::crashReport);
}
@@ -95,54 +87,9 @@ public class ModList {
return this.fileById.get(modid);
}
<T extends Event & IModBusEvent> Function<Executor, CompletableFuture<Void>> futureVisitor(
final IModStateTransition.EventGenerator<T> eventGenerator,
final ProgressMeter progressBar,
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> stateChange) {
return executor -> gather(
this.mods.stream()
.map(mod -> ModContainer.buildTransitionHandler(mod, eventGenerator, progressBar, stateChange, executor))
.toList()
).thenComposeAsync(ModList::completableFutureFromExceptionList, executor);
}
static CompletionStage<Void> completableFutureFromExceptionList(List<? extends Map.Entry<?, Throwable>> t) {
if (t.stream().noneMatch(e->e.getValue()!=null)) {
return CompletableFuture.completedFuture(null);
} else {
final List<Throwable> throwables = t.stream().filter(e -> e.getValue() != null).map(Map.Entry::getValue).toList();
CompletableFuture<Void> cf = new CompletableFuture<>();
final RuntimeException accumulator = new RuntimeException();
cf.completeExceptionally(accumulator);
for (Throwable exception : throwables) {
if (exception instanceof CompletionException) {
exception = exception.getCause();
}
if (exception.getSuppressed().length != 0) {
for (Throwable throwable : exception.getSuppressed()) {
accumulator.addSuppressed(throwable);
}
} else {
accumulator.addSuppressed(exception);
}
}
return cf;
}
}
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()];
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) {
this.mods = modContainers;
this.sortedContainers = modContainers.stream().sorted(Comparator.comparingInt(c->sortedList.indexOf(c.getModInfo()))).toList();
this.sortedContainers = modContainers.stream().sorted(Comparator.comparingInt(c -> sortedList.indexOf(c.getModInfo()))).toList();
this.indexedMods = modContainers.stream().collect(Collectors.toMap(ModContainer::getModId, Function.identity()));
}
@@ -201,6 +148,10 @@ public class ModList {
this.sortedContainers.forEach(containerConsumer);
}
public List<ModContainer> getLoadedMods() {
return this.sortedContainers;
}
public <T> Stream<T> applyForEachModContainer(Function<ModContainer, T> function) {
return indexedMods.values().stream().map(function);
}
@@ -6,7 +6,6 @@
package net.minecraftforge.fml;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.FMLEnvironment;
@@ -33,8 +32,6 @@ import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static net.minecraftforge.fml.Logging.CORE;
import static net.minecraftforge.fml.Logging.LOADING;
@@ -74,8 +71,7 @@ import static net.minecraftforge.fml.Logging.LOADING;
* and completes the mod loading sequence.</dd>
* </dl>
*/
public class ModLoader
{
public class ModLoader {
private static final Logger LOGGER = LogManager.getLogger();
private final LoadingModList loadingModList;
@@ -84,17 +80,15 @@ public class ModLoader
private final List<ModLoadingWarning> loadingWarnings;
private final ModStateManager stateManager;
private static boolean loadingStateValid;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private final Optional<Consumer<String>> statusConsumer = StartupNotificationManager.modLoaderConsumer();
private final Consumer<String> statusConsumer = StartupNotificationManager.modLoaderConsumer().orElse(msg -> {});
private final Set<IModLoadingState> completedStates = new HashSet<>();
private ModList modList;
private ModLoader()
{
private ModLoader() {
this.loadingModList = FMLLoader.getLoadingModList();
this.loadingExceptions = this.loadingModList.getErrors().stream()
.flatMap(ModLoadingException::fromEarlyException)
.collect(Collectors.toList());
.toList();
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());
@@ -156,7 +150,7 @@ public class ModLoader
loadingModList.getMods());
if (!this.loadingExceptions.isEmpty()) {
LOGGER.fatal(CORE, "Error during pre-loading phase", loadingExceptions.getFirst());
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
statusConsumer.accept("ERROR DURING MOD LOADING");
modList.setLoadedMods(Collections.emptyList());
loadingStateValid = false;
throw new LoadingFailedException(loadingExceptions);
@@ -169,7 +163,7 @@ public class ModLoader
if (!failedBounds.isEmpty()) {
LOGGER.fatal(CORE, "Failed to validate feature bounds for mods: {}", failedBounds);
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
statusConsumer.accept("ERROR DURING MOD LOADING");
modList.setLoadedMods(Collections.emptyList());
loadingStateValid = false;
throw new LoadingFailedException(failedBounds.stream()
@@ -177,19 +171,42 @@ public class ModLoader
.toList());
}
final List<ModContainer> modContainers = loadingModList.getModFiles().stream()
.map(ModFileInfo::getFile)
.map(this::buildMods)
.flatMap(List::stream)
.toList();
final var modContainers = new HashMap<String, ModContainer>();
for (var file : loadingModList.getModFiles()) {
var containers = this.buildMods(file.getFile());
for (var container : containers)
modContainers.put(container.getModId(), container);
}
if (!loadingExceptions.isEmpty()) {
LOGGER.fatal(CORE, "Failed to initialize mod containers", loadingExceptions.getFirst());
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
statusConsumer.accept("ERROR DURING MOD LOADING");
modList.setLoadedMods(Collections.emptyList());
loadingStateValid = false;
throw new LoadingFailedException(loadingExceptions);
}
modList.setLoadedMods(modContainers);
// Gather all dependencies, so we can make sure they have finished their transition events before we fire ours
for (var mod : modContainers.values()) {
for (var dep : mod.getModInfo().getDependencies()) {
var target = modContainers.get(dep.getModId());
if (target == null)
continue;
switch (dep.getOrdering()) {
case AFTER:
mod.dependencies.add(target);
break;
case BEFORE:
target.dependencies.add(mod);
break;
default:
break;
}
}
}
modList.setLoadedMods(modContainers.values().stream().toList());
this.modList = modList;
var stateList = stateManager.getStates(ModLoadingPhase.GATHER);
var progress = StartupMessageManager.addProgressBar("Mod Gather", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(this.modList)).sum());
@@ -214,7 +231,7 @@ public class ModLoader
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())));
statusConsumer.accept(String.format("Mod loading complete - %d mods loaded", this.modList.size()));
progress.complete();
}
@@ -243,10 +260,12 @@ public class ModLoader
pb.complete();
syncExecutor.drive(ticker);
}
private void waitForTransition(final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker, final CompletableFuture<Void> transition) {
while (!transition.isDone()) {
syncExecutor.drive(ticker);
}
try {
transition.join();
} catch (CompletionException e) {
@@ -256,36 +275,38 @@ public class ModLoader
.anyMatch(obj -> !(obj instanceof ModLoadingException));
if (hasNotModLoadingEx) {
LOGGER.fatal("Encountered non-modloading exceptions!", e);
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
statusConsumer.accept("ERROR DURING MOD LOADING");
throw e;
}
final List<ModLoadingException> modLoadingExceptions = Arrays.stream(t.getSuppressed())
.filter(ModLoadingException.class::isInstance)
.map(ModLoadingException.class::cast)
.collect(Collectors.toList());
.toList();
LOGGER.fatal(LOADING,"Failed to complete lifecycle event {}, {} errors found", state.name(), modLoadingExceptions.size());
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
statusConsumer.accept("ERROR DURING MOD LOADING");
throw new LoadingFailedException(modLoadingExceptions);
}
}
private List<ModContainer> buildMods(final IModFile modFile)
{
final Map<String, IModInfo> modInfoMap = modFile.getModFileInfo().getMods().stream().collect(Collectors.toMap(IModInfo::getModId, Function.identity()));
private List<ModContainer> buildMods(final IModFile modFile) {
final Map<String, IModInfo> modInfoMap = new HashMap<>();
for (var mod : modFile.getModFileInfo().getMods())
modInfoMap.put(mod.getModId(), mod);
LOGGER.trace(LOADING, "ModContainer is {}", ModContainer.class.getClassLoader());
final List<ModContainer> containers = modFile.getScanResult().getTargets()
.entrySet()
.stream()
.map(e -> buildModContainerFromTOML(modFile, modInfoMap, e))
.filter(Objects::nonNull)
.toList();
final List<ModContainer> containers = new ArrayList<>();
for (var entry : modFile.getScanResult().getTargets().entrySet()) {
var container = buildModContainerFromTOML(modFile, modInfoMap, entry.getKey(), entry.getValue());
if (container != null)
containers.add(container);
}
if (containers.size() != modInfoMap.size()) {
var modIds = modInfoMap.values().stream().map(IModInfo::getModId).sorted().toList();
var containerIds = containers.stream().map(c -> c != null ? c.getModId() : "(null)").sorted().toList();
LOGGER.fatal(LOADING,"File {} constructed {} mods: {}, but had {} mods specified: {}",
LOGGER.fatal(LOADING, "File {} constructed {} mods: {}, but had {} mods specified: {}",
modFile.getFilePath(),
containers.size(), containerIds,
modInfoMap.size(), modIds);
@@ -300,15 +321,15 @@ public class ModLoader
loadingExceptions.add(new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingclasses", null, modFile.getFilePath()));
}
// remove errored mod containers
return containers.stream().filter(mc -> mc.modLoadingStage != ModLoadingStage.ERROR).toList();
}
private ModContainer buildModContainerFromTOML(final IModFile modFile, final Map<String, IModInfo> modInfoMap, final Map.Entry<String, ? extends IModLanguageProvider.IModLanguageLoader> idToProviderEntry) {
private ModContainer buildModContainerFromTOML(final IModFile modFile, final Map<String, IModInfo> modInfoMap,
final String modId, final IModLanguageProvider.IModLanguageLoader languageLoader
) {
try {
final String modId = idToProviderEntry.getKey();
final IModLanguageProvider.IModLanguageLoader languageLoader = idToProviderEntry.getValue();
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)
@@ -351,6 +372,7 @@ public class ModLoader
}
ModList.get().forEachModInOrder(mc -> mc.acceptEvent(e));
}
public <T extends Event & IModBusEvent> T postEventWithReturn(T e) {
if (!loadingStateValid) {
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
@@ -359,9 +381,15 @@ public class ModLoader
ModList.get().forEachModInOrder(mc -> mc.acceptEvent(e));
return e;
}
@SuppressWarnings("removal")
public <T extends Event & IModBusEvent> void postEventWrapContainerInModOrder(T event) {
postEventWithWrapInModOrder(event, (mc, e) -> ModLoadingContext.get().setActiveContainer(mc), (mc, e) -> ModLoadingContext.get().setActiveContainer(null));
postEventWithWrapInModOrder(event,
(mc, e) -> ModLoadingContext.get().setActiveContainer(mc),
(mc, e) -> ModLoadingContext.get().setActiveContainer(null)
);
}
public <T extends Event & IModBusEvent> void postEventWithWrapInModOrder(T e, BiConsumer<ModContainer, T> pre, BiConsumer<ModContainer, T> post) {
if (!loadingStateValid) {
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
@@ -374,13 +402,11 @@ public class ModLoader
});
}
public List<ModLoadingWarning> getWarnings()
{
public List<ModLoadingWarning> getWarnings() {
return ImmutableList.copyOf(this.loadingWarnings);
}
public void addWarning(ModLoadingWarning warning)
{
public void addWarning(ModLoadingWarning warning) {
this.loadingWarnings.add(warning);
}
@@ -26,19 +26,23 @@ import java.util.function.ToIntFunction;
* @param inlineRunnable an optional runnable, which runs before starting the transition from this state to the next
* @param transition optional state transition information
*/
public record ModLoadingState(String name, String previous,
Function<ModList, String> message,
ToIntFunction<ModList> size,
ModLoadingPhase phase,
Optional<Consumer<ModList>> inlineRunnable,
Optional<IModStateTransition> transition) implements IModLoadingState {
public record ModLoadingState(
String name,
String previous,
Function<ModList, String> message,
ToIntFunction<ModList> size,
ModLoadingPhase phase,
Optional<Consumer<ModList>> inlineRunnable,
Optional<IModStateTransition> transition
) implements IModLoadingState {
@Override
public <T extends Event & IModBusEvent>
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask) {
public <T extends Event & IModBusEvent> Optional<CompletableFuture<Void>> buildTransition(
final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask
) {
var transition = this.transition.orElse(null);
return transition == null
? Optional.empty()
@@ -52,9 +56,12 @@ public record ModLoadingState(String name, String previous,
* @param name the name of the state
* @param previous the name of the immediately previous state to this state
* @param phase the mod loading phase the state belongs to
*
* @deprecated Use the builder
*/
@Deprecated(since = "1.21.3", forRemoval = true)
public static ModLoadingState empty(final String name, final String previous, final ModLoadingPhase phase) {
return new ModLoadingState(name, previous, ml -> "", f->0, phase, Optional.empty(), Optional.empty());
return of(name, phase).after(previous).empty();
}
/**
@@ -65,11 +72,13 @@ public record ModLoadingState(String name, String previous,
* @param previous the name of the immediately previous state to this state
* @param phase the mod loading phase the state belongs to
* @param transition the state transition information
* @return a mod loading state with state transition information and a default message
*
* @deprecated Use the builder
*/
@Deprecated(since = "1.21.3", forRemoval = true)
public static ModLoadingState withTransition(final String name, final String previous, final ModLoadingPhase phase,
final IModStateTransition transition) {
return new ModLoadingState(name, previous, ml -> "Processing transition " + name, ModList::size, phase, Optional.empty(), Optional.of(transition));
return of(name, phase).after(previous).withTransition(transition);
}
/**
@@ -81,11 +90,14 @@ public record ModLoadingState(String name, String previous,
* @param phase the mod loading phase the state belongs to
* @param transition the state transition information
* @return a mod loading state with state transition information and a custom message
*
* @deprecated Use the builder
*/
@Deprecated(since = "1.21.3", forRemoval = true)
public static ModLoadingState withTransition(final String name, final String previous,
final Function<ModList, String> message, final ModLoadingPhase phase,
final IModStateTransition transition) {
return new ModLoadingState(name, previous, message, ModList::size, phase, Optional.empty(), Optional.of(transition));
return of(name, phase).after(previous).message(message).withTransition(transition);
}
/**
@@ -97,9 +109,59 @@ public record ModLoadingState(String name, String previous,
* @param phase the mod loading phase the state belongs to
* @param inline an optional runnable, which runs before starting the transition from this state to the next
* @return a mod loading state with an inline runnable and default message
*
* @deprecated Use the builder
*/
@Deprecated(since = "1.21.3", forRemoval = true)
public static ModLoadingState withInline(final String name, final String previous, final ModLoadingPhase phase,
final Consumer<ModList> inline) {
return new ModLoadingState(name, previous, ml -> "Processing work " + name, ml->0, phase, Optional.of(inline), Optional.empty());
return of(name, phase).after(previous).withInline(inline);
}
public static Builder of(final String name, final ModLoadingPhase phase) {
return new Builder(name, phase);
}
public static class Builder {
private final String name;
private final ModLoadingPhase phase;
private String after;
private Function<ModList, String> message = null;
private ToIntFunction<ModList> size = null;
private Builder(final String name, final ModLoadingPhase phase) {
this.name = name;
this.phase = phase;
}
public Builder after(final ModLoadingState value) { return after(value.name()); }
public Builder after(final String value) {
this.after = value;
return this;
}
public Builder message(final String value) { return message(ml -> value); }
public Builder message(final Function<ModList, String> value) {
this.message = value;
return this;
}
public Builder size(final int value) { return size(ml -> value); }
public Builder size(final ToIntFunction<ModList> value) {
this.size = value;
return this;
}
public ModLoadingState empty() {
return new ModLoadingState(name, after, message != null ? message : ml -> "", size != null ? size : ml -> 0, phase, Optional.empty(), Optional.empty());
}
public ModLoadingState withTransition(final IModStateTransition transition) {
return new ModLoadingState(name, after, message != null ? message : ml -> "Processing transition " + name, size != null ? size : ModList::size, phase, Optional.empty(), Optional.of(transition));
}
public ModLoadingState withInline(final Consumer<ModList> inline) {
return new ModLoadingState(name, after, message != null ? message : ml -> "Processing work " + name, size != null ? size : ml -> 0, phase, Optional.of(inline), Optional.empty());
}
}
}
@@ -0,0 +1,181 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Executor;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.jetbrains.annotations.ApiStatus;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.IModStateTransition.EventGenerator;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
@ApiStatus.Internal
class ModStateTransitionHelper {
static final IModStateTransition NOOP = new NoopTransition();
record NoopTransition() implements IModStateTransition {
@Override
public ThreadSelector threadSelector() {
return ThreadSelector.SYNC;
}
@Override
public BiFunction<Executor, CompletableFuture<Void>, CompletableFuture<Void>> finalActivityGenerator() {
return (e, t) -> t.thenApplyAsync(Function.identity(), e);
}
}
static <V> CompletionStage<Void> completableFutureFromExceptionList(List<FutureResult<V>> t) {
if (t.stream().noneMatch(e -> e.exception() != null)) {
return CompletableFuture.completedFuture(null);
} else {
final var throwables = t.stream().map(FutureResult::exception).filter(e -> e != null).toList();
CompletableFuture<Void> cf = new CompletableFuture<>();
final RuntimeException accumulator = new RuntimeException();
cf.completeExceptionally(accumulator);
for (Throwable exception : throwables) {
if (exception instanceof CompletionException) {
exception = exception.getCause();
}
if (exception.getSuppressed().length != 0) {
for (Throwable throwable : exception.getSuppressed()) {
accumulator.addSuppressed(throwable);
}
} else {
accumulator.addSuppressed(exception);
}
}
return cf;
}
}
record FutureResult<V>(V value, Throwable exception){}
static <V> CompletableFuture<List<FutureResult<V>>> gather(Collection<? extends CompletableFuture<? extends V>> futures) {
var list = new ArrayList<FutureResult<V>>(futures.size());
var results = new CompletableFuture[futures.size()];
for (var future : futures) {
int i = list.size();
list.add(null);
results[i] = future.whenComplete((result, exception) -> list.set(i, new FutureResult<>(result, exception)));
}
return CompletableFuture.allOf(results).handle((r, th)->null).thenApply(res -> list);
}
private static <T extends Event & IModBusEvent> void addCompletableFutureTaskForModDispatch(
final IModStateTransition transition,
final Executor executor,
final List<CompletableFuture<Void>> completableFutures,
final ProgressMeter progressBar,
final EventGenerator<T> eventGenerator,
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextState
) {
@SuppressWarnings("removal")
var preDispatchHook = getHook(transition.preDispatchHook(), executor, eventGenerator);
if (preDispatchHook != null)
completableFutures.add(preDispatchHook);
var modFutures = new LinkedHashMap<String, CompletableFuture<Void>>();
for (var mod : ModList.get().getLoadedMods()) {
CompletableFuture<Void> parent = null;
if (mod.dependencies.isEmpty()) {
parent = CompletableFuture.allOf();
} else {
var deps = new CompletableFuture[mod.dependencies.size()];
int idx = 0;
for (var depContainer : mod.dependencies) {
var future = modFutures.get(depContainer.getModId());
if (future == null)
throw new IllegalStateException("Could not find dependency future " + depContainer.getModId() + " for " + mod.getModId());
deps[idx++] = future;
}
parent = CompletableFuture.allOf(deps);
}
@SuppressWarnings("removal")
var dispatch = parent
.thenRunAsync(() -> {
ModLoadingContext.get().setActiveContainer(mod);
var handler = mod.activityMap.get(mod.modLoadingStage);
if (handler != null)
handler.run();
mod.acceptEvent(eventGenerator.apply(mod));
}, executor)
.whenComplete((mc, exception) -> {
mod.modLoadingStage = nextState.apply(mod.modLoadingStage, exception);
progressBar.increment();
ModLoadingContext.get().setActiveContainer(null);
});
modFutures.put(mod.getModId(), dispatch);
}
var dispatch = gather(modFutures.values()).thenComposeAsync(ModStateTransitionHelper::completableFutureFromExceptionList, executor);
completableFutures.add(dispatch);
@SuppressWarnings("removal")
var postDispatchHook = getHook(transition.preDispatchHook(), executor, eventGenerator);
if (postDispatchHook != null)
completableFutures.add(postDispatchHook);
}
private static <T extends Event & IModBusEvent> CompletableFuture<Void> getHook(BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> hook, Executor executor, EventGenerator<T> eventGenerator) {
if (hook == null || hook == IModStateTransition.NULL_HOOK) return null;
@SuppressWarnings("unchecked")
var hookTyped = (BiFunction<Executor, EventGenerator<T>, CompletableFuture<Void>>)hook;
return hookTyped.apply(executor, eventGenerator);
}
static <T extends Event & IModBusEvent> CompletableFuture<Void> build(
final IModStateTransition transition,
final String name,
final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask
) {
List<CompletableFuture<Void>> futures = new ArrayList<>();
final var executor = transition.threadSelector().apply(syncExecutor, parallelExecutor);
@SuppressWarnings({ "removal", "unchecked" })
var events = transition.eventFunctionStream().get().map(f -> (EventGenerator<T>)f).toList();
for (int x = 0; x < events.size(); x++) {
var gen = events.get(x);
BiFunction<ModLoadingStage, Throwable, ModLoadingStage> state = x == events.size() - 1
? transition.nextModLoadingStage()
: ModLoadingStage::currentState;
addCompletableFutureTaskForModDispatch(transition, executor, futures, progressBar, gen, state);
}
final CompletableFuture<Void> preSyncTaskCF = preSyncTask.apply(syncExecutor);
final CompletableFuture<Void> eventDispatchCF = gather(futures).thenCompose(ModStateTransitionHelper::completableFutureFromExceptionList);
final CompletableFuture<Void> postEventDispatchCF = preSyncTaskCF
.thenApplyAsync(n -> {
progressBar.label(progressBar.name() + ": dispatching " + name);
return null;
}, parallelExecutor)
.thenComposeAsync(n -> eventDispatchCF, parallelExecutor)
.thenApply(r -> {
postSyncTask.apply(syncExecutor);
return null;
});
return transition.finalActivityGenerator().apply(syncExecutor, postEventDispatchCF);
}
}
@@ -14,6 +14,7 @@ import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.FMLConfig;
import net.minecraftforge.fml.loading.FMLPaths;
import org.apache.commons.io.FilenameUtils;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import java.io.IOException;
@@ -25,9 +26,66 @@ import static net.minecraftforge.fml.config.ConfigTracker.CONFIG;
public class ConfigFileTypeHandler {
private static final Logger LOGGER = LogUtils.getLogger();
static final ConfigFileTypeHandler TOML = new ConfigFileTypeHandler();
private static final Path defaultConfigPath = FMLPaths.GAMEDIR.get().resolve(FMLConfig.getConfigValue(FMLConfig.ConfigValue.DEFAULT_CONFIG_PATH));
private static final ConfigFileTypeHandler CLIENT = new ConfigFileTypeHandler(ModConfig.Type.CLIENT);
private static final ConfigFileTypeHandler COMMON = new ConfigFileTypeHandler(ModConfig.Type.COMMON);
private static final ConfigFileTypeHandler SERVER = new ConfigFileTypeHandler(ModConfig.Type.SERVER);
private final @Nullable ModConfig.Type type;
private @Nullable FileWatcher watcher;
// exists for bin compat
public ConfigFileTypeHandler() {
this(null);
}
private ConfigFileTypeHandler(@Nullable ModConfig.Type type) {
this.type = type;
}
/**
* Gets the handler for the given {@link ModConfig.Type}.
*
* @param type The type to get the handler for
* @return The handler
*/
static ConfigFileTypeHandler get(ModConfig.Type type) {
return switch (type) {
case CLIENT -> CLIENT;
case COMMON -> COMMON;
case SERVER -> SERVER;
};
}
/**
* Gets the {@link FileWatcher} for this handler, creating it if it doesn't exist and/or has been stopped.
*
* @return The watcher
*
* @apiNote This is package-private so modders can't just call {@link FileWatcher#stop()} and fuck up everything.
*/
FileWatcher getWatcher() {
if (this.watcher == null) {
LOGGER.debug(CONFIG, "Starting watcher for handler: {}", this);
this.watcher = new FileWatcher();
}
return this.watcher;
}
/**
* Stops the {@link FileWatcher} for this handler, and sets it to null afterward. Use this instead of
* {@link FileWatcher#stop()}.
*/
void stopWatcher() {
if (this.watcher == null) return;
LOGGER.debug(CONFIG, "Stopping watcher for hander: {}", this);
this.watcher.stop();
this.watcher = null;
}
public Function<ModConfig, CommentedFileConfig> reader(Path configBasePath) {
return (c) -> {
final Path configPath = configBasePath.resolve(c.getFileName());
@@ -37,7 +95,7 @@ public class ConfigFileTypeHandler {
onFileNotFound((newfile, configFormat)-> setupConfigFile(c, newfile, configFormat)).
writingMode(WritingMode.REPLACE).
build();
LOGGER.debug(CONFIG, "Built TOML config for {}", configPath.toString());
LOGGER.debug(CONFIG, "Built TOML config for {}", configPath);
try
{
configData.load();
@@ -46,9 +104,9 @@ public class ConfigFileTypeHandler {
{
throw new ConfigLoadingException(c, ex);
}
LOGGER.debug(CONFIG, "Loaded TOML config file {}", configPath.toString());
FileWatcher.defaultInstance().addWatch(configPath, new ConfigWatcher(c, configData, Thread.currentThread().getContextClassLoader()));
LOGGER.debug(CONFIG, "Watching TOML config file {} for changes", configPath.toString());
LOGGER.debug(CONFIG, "Loaded TOML config file {}", configPath);
this.getWatcher().addWatch(configPath, new ConfigWatcher(c, configData, Thread.currentThread().getContextClassLoader()));
LOGGER.debug(CONFIG, "Watching TOML config file {} for changes", configPath);
return configData;
};
}
@@ -56,9 +114,9 @@ public class ConfigFileTypeHandler {
public void unload(Path configBasePath, ModConfig config) {
Path configPath = configBasePath.resolve(config.getFileName());
try {
FileWatcher.defaultInstance().removeWatch(configBasePath.resolve(config.getFileName()));
this.getWatcher().removeWatch(configPath);
} catch (RuntimeException e) {
LOGGER.error("Failed to remove config {} from tracker!", configPath.toString(), e);
LOGGER.error("Failed to remove config {} from tracker!", configPath, e);
}
}
@@ -109,6 +167,11 @@ public class ConfigFileTypeHandler {
}
}
@Override
public String toString() {
return "ConfigFileTypeHandler[" + (type != null ? type : "UNKNOWN") + "]";
}
private record ConfigWatcher(
ModConfig modConfig,
CommentedFileConfig commentedFileConfig,
@@ -8,6 +8,9 @@ package net.minecraftforge.fml.config;
import com.electronwill.nightconfig.core.CommentedConfig;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.mojang.logging.LogUtils;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.fml.loading.FMLPaths;
import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
@@ -52,11 +55,24 @@ public class ConfigTracker {
}
}
// TODO: [FML] This is only called for the server (outside of forceUnload)
// rethink config implementation for eventual FML rewrite
public void unloadConfigs(ModConfig.Type type, Path configBasePath) {
LOGGER.debug(CONFIG, "Unloading configs type {}", type);
for (ModConfig config : this.configSets.get(type)) {
closeConfig(config, configBasePath);
}
ConfigFileTypeHandler.get(type).stopWatcher();
}
// If there is a better way to do this, please tell me. Because this is FUCKED
public void forceUnload() {
// This is how ModStateProvider handles loading configs. So we're doing the same but with unloadConfigs instead
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> this.unloadConfigs(ModConfig.Type.CLIENT, FMLPaths.CONFIGDIR.get()));
this.unloadConfigs(ModConfig.Type.COMMON, FMLPaths.CONFIGDIR.get());
// just in case server watcher is still alive somehow...
ConfigFileTypeHandler.get(ModConfig.Type.SERVER).stopWatcher();
}
private static void openConfig(final ModConfig config, final Path configBasePath) {
@@ -30,7 +30,7 @@ public class ModConfig
this.spec = spec;
this.fileName = fileName;
this.container = container;
this.configHandler = ConfigFileTypeHandler.TOML;
this.configHandler = ConfigFileTypeHandler.get(type);
ConfigTracker.INSTANCE.trackConfig(this);
}
@@ -604,10 +604,25 @@ public class DisplayWindow implements ImmediateWindowProvider {
@Override
public void updateModuleReads(final ModuleLayer layer) {
var fm = layer.findModule("forge").orElseThrow();
getClass().getModule().addReads(fm);
var clz = FMLLoader.getGameLayer().findModule("forge").map(l->Class.forName(l, "net.minecraftforge.client.loading.ForgeLoadingOverlay")).orElseThrow();
loadingOverlay = Arrays.stream(clz.getDeclaredMethods()).filter(m-> Modifier.isStatic(m.getModifiers()) && m.getName().equals("newInstance")).findFirst().orElseThrow();
final var FORGE_MODULE = "net.minecraftforge.forge";
var forge_module = layer.findModule(FORGE_MODULE).orElse(null);
if (forge_module == null)
throw new IllegalStateException("Could not find " + FORGE_MODULE + " in " + layer);
getClass().getModule().addReads(forge_module);
var clz = Class.forName(forge_module, "net.minecraftforge.client.loading.ForgeLoadingOverlay");
for (var mtd : clz.getDeclaredMethods()) {
if (Modifier.isStatic(mtd.getModifiers()) && "newInstance".equals(mtd.getName())) {
this.loadingOverlay = mtd;
break;
}
}
if (loadingOverlay == null)
throw new IllegalStateException("Could not find static newInstace method in " + clz.getName());
}
public int getFramebufferTextureId() {
@@ -13,10 +13,10 @@ import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
public class PerformanceInfo {
private static final MemoryMXBean MEMORY_BEAN = ManagementFactory.getMemoryMXBean();
private final boolean showCPUUsage;
private final OperatingSystemMXBean osBean;
private final MemoryMXBean memoryBean;
private float memory;
private String text;
@@ -24,15 +24,14 @@ public class PerformanceInfo {
PerformanceInfo() {
showCPUUsage = FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_SHOW_CPU);
osBean = showCPUUsage ? ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class) : null;
memoryBean = ManagementFactory.getMemoryMXBean();
}
void update() {
final MemoryUsage heapusage = memoryBean.getHeapMemoryUsage();
final MemoryUsage heapusage = MEMORY_BEAN.getHeapMemoryUsage();
memory = (float) heapusage.getUsed() / heapusage.getMax();
if (!showCPUUsage) {
text = "Heap: %d/%d MB (%.1f%%) OffHeap: %d MB".formatted(heapusage.getUsed() >> 20, heapusage.getMax() >> 20, memory * 100.0, memoryBean.getNonHeapMemoryUsage().getUsed() >> 20);
text = "Memory: %d/%dMB (%.1f%%)".formatted(heapusage.getUsed() >>> 20, heapusage.getMax() >>> 20, memory * 100f);
return;
}
@@ -44,7 +43,7 @@ public class PerformanceInfo {
cpuText = "CPU: %.1f%%".formatted(cpuLoad * 100f);
}
text = "Heap: %d/%d MB (%.1f%%) OffHeap: %d MB %s".formatted(heapusage.getUsed() >> 20, heapusage.getMax() >> 20, memory * 100.0, memoryBean.getNonHeapMemoryUsage().getUsed() >> 20, cpuText);
text = "Memory: %d/%dMB (%.1f%%) %s".formatted(heapusage.getUsed() >>> 20, heapusage.getMax() >>> 20, memory * 100f, cpuText);
}
String text() {
@@ -50,7 +50,14 @@ public class CapabilityTokenSubclass implements ILaunchPluginService {
@Override
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
return isEmpty ? NAY : YAY;
if (isEmpty)
return NAY;
String internalName = classType.getInternalName();
if (internalName.startsWith("net/minecraft/") || internalName.startsWith("com/mojang/"))
return NAY;
return YAY;
}
@Override
@@ -8,6 +8,7 @@ package net.minecraftforge.fml.common.asm;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -35,6 +36,9 @@ public class ObjectHolderDefinalize implements ILaunchPluginService {
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.toUnmodifiableMap(VanillaObjectHolderData::holderClass, Function.identity()));
private static final Set<String> VANILLA_OBJECT_HOLDER_CLASSES = VANILLA_OBJECT_HOLDERS.keySet().stream()
.map(it -> it.replace('.', '/'))
.collect(Collectors.toUnmodifiableSet());
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;
@@ -47,9 +51,26 @@ public class ObjectHolderDefinalize implements ILaunchPluginService {
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
@Override
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty)
{
return isEmpty ? NAY : YAY;
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
if (isEmpty)
return NAY;
// Let's skip processing classes that definitely don't have object holders
String internalName = classType.getInternalName();
// Forge classes that aren't in the debug package (that package is used for tests)
if (internalName.startsWith("net/minecraftforge/") && !internalName.substring(18).contains("debug"))
return NAY;
// Vanilla classes that don't have object holders
if (internalName.startsWith("com/mojang/"))
return NAY;
// ...except specific ones we added to the VANILLA_OBJECT_HOLDERS map
if (internalName.startsWith("net/minecraft/") && !VANILLA_OBJECT_HOLDER_CLASSES.contains(internalName))
return NAY;
return YAY;
}
private static boolean hasHolder(List<AnnotationNode> lst) {
@@ -48,9 +48,15 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
@Override
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty)
{
return isEmpty ? NAY : YAY;
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
if (isEmpty)
return NAY;
String internalName = classType.getInternalName();
if (internalName.startsWith("net/minecraftforge/") || internalName.startsWith("com/mojang/"))
return NAY;
return YAY;
}
@Override
@@ -59,15 +65,14 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
if ((classNode.access & Opcodes.ACC_ENUM) == 0)
return ComputeFlags.NO_REWRITE;
if (!classNode.interfaces.contains(MARKER_IFACE.getInternalName()))
return ComputeFlags.NO_REWRITE;
Type array = Type.getType("[" + classType.getDescriptor());
String arrayDesc = array.getDescriptor();
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;
}
//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"))
@@ -140,7 +140,7 @@ public class ImmediateWindowHandler {
@Override
public void updateModuleReads(final ModuleLayer layer) {
var fm = layer.findModule("forge");
var fm = layer.findModule("net.minecraftforge.forge");
if (fm.isPresent()) {
getClass().getModule().addReads(fm.get());
var clz = fm.map(l -> Class.forName(l, "net.minecraftforge.client.loading.NoVizFallback")).orElseThrow();
@@ -5,31 +5,25 @@
package net.minecraftforge.fml.loading;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import net.minecraftforge.fml.loading.moddiscovery.BackgroundScanHandler;
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 java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.stream.Collectors;
/**
* Master list of all mods <em>in the loading context. This class cannot refer outside the
* loading package</em>
*/
public class LoadingModList
{
public class LoadingModList {
private static LoadingModList INSTANCE;
private final List<ModFileInfo> modFiles;
private final List<ModInfo> sortedList;
@@ -37,15 +31,14 @@ public class LoadingModList
private final List<EarlyLoadingException> preLoadErrors;
private List<IModFile> brokenFiles;
private LoadingModList(final List<ModFile> modFiles, final List<ModInfo> sortedList)
{
private LoadingModList(final List<ModFile> modFiles, final List<ModInfo> sortedList) {
this.modFiles = modFiles.stream()
.map(ModFile::getModFileInfo)
.map(ModFileInfo.class::cast)
.collect(Collectors.toList());
.toList();
this.sortedList = sortedList.stream()
.map(ModInfo.class::cast)
.collect(Collectors.toList());
.toList();
this.fileById = this.modFiles.stream()
.map(ModFileInfo::getMods)
.flatMap(Collection::stream)
@@ -54,21 +47,18 @@ public class LoadingModList
this.preLoadErrors = new ArrayList<>();
}
public static LoadingModList of(List<ModFile> modFiles, List<ModInfo> sortedList, final EarlyLoadingException earlyLoadingException)
{
public static LoadingModList of(List<ModFile> modFiles, List<ModInfo> sortedList, final EarlyLoadingException earlyLoadingException) {
INSTANCE = new LoadingModList(modFiles, sortedList);
if (earlyLoadingException != null)
{
INSTANCE.preLoadErrors.add(earlyLoadingException);
}
return INSTANCE;
}
public static LoadingModList get() {
return INSTANCE;
}
public void addCoreMods()
{
public void addCoreMods() {
modFiles.stream()
.map(ModFileInfo::getFile)
.map(ModFile::getCoreMods)
@@ -76,8 +66,7 @@ public class LoadingModList
.forEach(FMLLoader.getCoreModProvider()::addCoreMod);
}
public void addAccessTransformers()
{
public void addAccessTransformers() {
for (ModFileInfo modFile : modFiles) {
ModFile mod = modFile.getFile();
var at = mod.getAccessTransformer().orElse(null);
@@ -87,21 +76,18 @@ public class LoadingModList
}
}
public void addForScanning(BackgroundScanHandler backgroundScanHandler)
{
public void addForScanning(BackgroundScanHandler backgroundScanHandler) {
backgroundScanHandler.setLoadingModList(this);
modFiles.stream()
.map(ModFileInfo::getFile)
.forEach(backgroundScanHandler::submitForScanning);
}
public List<ModFileInfo> getModFiles()
{
public List<ModFileInfo> getModFiles() {
return modFiles;
}
public Path findResource(final String className)
{
public Path findResource(final String className) {
for (ModFileInfo mf : modFiles) {
final Path resource = mf.getFile().findResource(className);
if (Files.exists(resource)) return resource;
@@ -109,55 +95,11 @@ public class LoadingModList
return null;
}
public Enumeration<URL> findAllURLsForResource(final String resName) {
final String resourceName;
// strip a leading slash
if (resName.startsWith("/")) {
resourceName = resName.substring(1);
} else {
resourceName = resName;
}
return new Enumeration<URL>() {
private final Iterator<ModFileInfo> modFileIterator = modFiles.iterator();
private URL next;
@Override
public boolean hasMoreElements() {
if (next!=null) return true;
next = findNextURL();
return next != null;
}
@Override
public URL nextElement() {
if (next == null) {
next = findNextURL();
if (next == null) throw new NoSuchElementException();
}
URL result = next;
next = null;
return result;
}
private URL findNextURL() {
while (modFileIterator.hasNext()) {
final ModFileInfo next = modFileIterator.next();
final Path resource = next.getFile().findResource(resourceName);
if (Files.exists(resource)) {
return LamdbaExceptionUtils.uncheck(()->new URL("modjar://" + next.getMods().get(0).getModId() + "/" + resourceName));
}
}
return null;
}
};
}
public ModFileInfo getModFileById(String modid)
{
public ModFileInfo getModFileById(String modid) {
return this.fileById.get(modid);
}
public List<ModInfo> getMods()
{
public List<ModInfo> getMods() {
return this.sortedList;
}
@@ -8,7 +8,7 @@ package net.minecraftforge.fml.loading;
import com.google.common.graph.GraphBuilder;
import com.google.common.graph.MutableGraph;
import com.mojang.logging.LogUtils;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.language.IModInfo.ModVersion;
import net.minecraftforge.fml.loading.EarlyLoadingException.ExceptionData;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
@@ -20,235 +20,250 @@ import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.stream.Collectors.*;
import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
public class ModSorter
{
public class ModSorter {
private static final Logger LOGGER = LogUtils.getLogger();
private final UniqueModListBuilder uniqueModListBuilder;
private List<ModFile> modFiles;
private List<ModInfo> sortedList;
private Map<String, IModInfo> modIdNameLookup;
private List<ModFile> systemMods;
private ModSorter(final List<ModFile> modFiles)
{
this.uniqueModListBuilder = new UniqueModListBuilder(modFiles);
}
private record State(List<ModFile> files, List<ModInfo> mods) {}
private ModSorter() {}
@SuppressWarnings("removal")
public static LoadingModList sort(List<ModFile> mods, final List<ExceptionData> errors) {
State systemMods = detectSystemMods(mods);
List<ModFile> modFiles;
public static LoadingModList sort(List<ModFile> mods, final List<ExceptionData> errors)
{
final ModSorter ms = new ModSorter(mods);
try {
ms.buildUniqueList();
modFiles = new UniqueModListBuilder(mods).buildUniqueList().modFiles();
} catch (EarlyLoadingException e) {
// We cannot build any list with duped mods. We have to abort immediately and report it
// Note this will never actually throw an error because the duplicate checks are done in ModDiscovererer before we get to this phase
// 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)).toList(), e);
return LoadingModList.of(systemMods.files(), systemMods.mods(), e);
}
var named = new HashMap<String, ModInfo>();
for (var file : modFiles) {
for (var info : file.getModInfos())
named.put(info.getModId(), (ModInfo)info);
}
// try and validate dependencies
final List<ExceptionData> failedList = Stream.concat(ms.verifyDependencyVersions().stream(), errors.stream()).toList();
final List<ExceptionData> failedList = Stream.concat(verifyDependencyVersions(modFiles).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)).toList(), new EarlyLoadingException("failure to validate mod list", null, failedList));
return LoadingModList.of(systemMods.files(), systemMods.mods(), new EarlyLoadingException("failure to validate mod list", null, failedList));
} else {
// Otherwise, lets try and sort the modlist and proceed
EarlyLoadingException earlyLoadingException = null;
try {
ms.sort();
var sorted = sort(modFiles, named);
return LoadingModList.of(sorted.files(), sorted.mods(), null);
} catch (EarlyLoadingException e) {
earlyLoadingException = e;
// The only exception that can happen here is a cyclic exception, but fall back to system mods so we can display the nice screen.
return LoadingModList.of(systemMods.files(), systemMods.mods(), e);
}
return LoadingModList.of(ms.modFiles, ms.sortedList, earlyLoadingException);
}
}
private void sort()
{
// lambdas are identity based, so sorting them is impossible unless you hold reference to them
final MutableGraph<ModFileInfo> graph = GraphBuilder.directed().build();
AtomicInteger counter = new AtomicInteger();
Map<ModFileInfo, Integer> infos = modFiles.stream()
.map(ModFile::getModFileInfo)
.filter(ModFileInfo.class::isInstance)
.map(ModFileInfo.class::cast)
.collect(toMap(Function.identity(), e -> counter.incrementAndGet()));
infos.keySet().forEach(graph::addNode);
modFiles.stream()
.map(ModFile::getModInfos)
.flatMap(List::stream)
.map(IModInfo::getDependencies)
.<IModInfo.ModVersion>flatMap(List::stream)
.forEach(dep -> addDependency(graph, dep));
private static State sort(final List<ModFile> modFiles, final Map<String, ModInfo> named) {
final MutableGraph<ModInfo> graph = GraphBuilder.directed().build();
final List<ModFileInfo> sorted;
try
{
sorted = TopologicalSort.topologicalSort(graph, Comparator.comparing(infos::get));
}
catch (CyclePresentException e)
{
Set<Set<ModFileInfo>> cycles = e.getCycles();
if (LOGGER.isErrorEnabled(LOADING))
{
LOGGER.error(LOADING, "Mod Sorting failed.\nDetected Cycles: {}\n", cycles);
int counter = 0;
var infos = new HashMap<ModInfo, Integer>();
for (var file : modFiles) {
if (file.getModFileInfo() instanceof ModFileInfo info) {
for (var imod : info.getMods()) {
var mod = (ModInfo)imod;
infos.put(mod, counter++);
graph.addNode(mod);
}
}
var dataList = cycles.stream()
.flatMap(Set::stream)
.<IModInfo>mapMulti((mf,c)->mf.getMods().forEach(c))
.map(IModInfo::getModId)
.map(list -> new ExceptionData("fml.modloading.cycle", list))
.toList();
}
for (var file : modFiles) {
for (var info : file.getModInfos()) {
for (var dep : info.getDependencies()) {
// Ordering isn't effected by sides, should it be?
//if (!dep.getSide().isCorrectSide())
// continue;
var target = named.get(dep.getModId());
// soft dep that doesn't exist. No edge required.
if (target == null)
continue;
var self = (ModInfo)dep.getOwner();
switch (dep.getOrdering()) {
case BEFORE -> graph.putEdge(self, target);
case AFTER -> graph.putEdge(target, self);
default -> {}
}
}
}
}
final List<ModInfo> sorted;
try {
sorted = TopologicalSort.topologicalSort(graph, Comparator.comparing(infos::get));
} catch (CyclePresentException e) {
Set<Set<ModInfo>> cycles = e.getCycles();
var buf = new StringBuilder();
buf.append("Mod Sorting failed - Detected Cycles: \n");
var dataList = new ArrayList<ExceptionData>();
for (var cycle : cycles) {
buf.append("\tCycle:\n");
for (var mod : cycle) {
var modDeps = new StringBuilder()
.append(mod.getModId())
.append(' ')
.append(mod.getDependencies().stream()
.filter(v -> cycle.stream().anyMatch(m -> m.getModId().equals(v.getModId())))
.map(dep -> dep.getOrdering().name() + " " + dep.getModId())
.collect(Collectors.joining(", "))
);
dataList.add(new ExceptionData("fml.modloading.cycle", modDeps.toString()));
buf.append("\t\tMod: ").append(modDeps.toString()).append('\n');
}
}
LOGGER.error(LOADING, buf.toString());
throw new EarlyLoadingException("Sorting error", e, dataList);
}
this.sortedList = sorted.stream()
.map(ModFileInfo::getMods)
.flatMap(List::stream)
.map(ModInfo.class::cast)
.collect(toList());
this.modFiles = sorted.stream()
.map(ModFileInfo::getFile)
.collect(toList());
}
private void addDependency(MutableGraph<ModFileInfo> topoGraph, IModInfo.ModVersion dep)
{
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;
var files = new LinkedHashSet<ModFile>();
var list = new ArrayList<ModInfo>();
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);
default -> {}
for (var mod : sorted) {
files.add(mod.getOwningFile().getFile());
list.add(mod);
}
return new State(files.stream().toList(), list);
}
private void buildUniqueList()
{
final UniqueModListBuilder.UniqueModListData uniqueModListData = uniqueModListBuilder.buildUniqueList();
this.modFiles = uniqueModListData.modFiles();
detectSystemMods(uniqueModListData.modFilesByFirstId());
modIdNameLookup = uniqueModListData.modFilesByFirstId().entrySet().stream()
.filter(e -> !e.getValue().get(0).getModInfos().isEmpty())
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().get(0).getModInfos().get(0)
));
}
private void detectSystemMods(final Map<String, List<ModFile>> modFilesByFirstId) {
private static State detectSystemMods(final List<ModFile> modFiles) {
// Capture system mods (ex. MC, Forge) here, so we can keep them for later
var systemMods = List.of("minecraft", "forge");
LOGGER.debug("Configured system mods: {}", systemMods);
this.systemMods = new ArrayList<>();
var mods = new ArrayList<ModInfo>();
var files = new ArrayList<ModFile>();
for (var systemMod : systemMods) {
var container = modFilesByFirstId.get(systemMod);
if (container != null && !container.isEmpty()) {
LOGGER.debug("Found system mod: {}", systemMod);
this.systemMods.add(container.getFirst());
} else {
var mod = findMod(modFiles, systemMod);
if (mod == null)
throw new IllegalStateException("Failed to find system mod: " + systemMod);
}
LOGGER.debug("Found system mod: {}", systemMod);
mods.add(mod.info());
files.add(mod.file());
}
return new State(files, mods);
}
private List<EarlyLoadingException.ExceptionData> verifyDependencyVersions()
{
final var modVersions = modFiles.stream()
.map(ModFile::getModInfos)
.flatMap(List::stream)
.collect(toMap(IModInfo::getModId, IModInfo::getVersion));
private record ModPair(ModFile file, ModInfo info) {}
private static ModPair findMod(final List<ModFile> modFiles, String name) {
for (var file : modFiles) {
for (var mod : file.getModFileInfo().getMods()) {
if (name.equals(mod.getModId()))
return new ModPair(file, (ModInfo)mod);
}
}
return null;
}
final var modVersionDependencies = modFiles.stream()
.map(ModFile::getModInfos)
.flatMap(List::stream)
.collect(groupingBy(Function.identity(), flatMapping(e -> e.getDependencies().stream(), toList())));
private static List<ExceptionData> verifyDependencyVersions(final List<ModFile> files) {
final var modVersions = new HashMap<String, ArtifactVersion>();
final var modRequirements = new HashSet<ModVersion>();
int mandatoryRequired = 0;
final var modRequirements = modVersionDependencies.values().stream()
.<IModInfo.ModVersion>flatMap(List::stream)
.filter(mv -> mv.getSide().isCorrectSide())
.collect(toSet());
for (var file : files) {
for (var info : file.getModInfos()) {
modVersions.put(info.getModId(), info.getVersion());
for (var dep : info.getDependencies()) {
if (dep.getSide().isCorrectSide()) {
if (modRequirements.add(dep) && dep.isMandatory())
mandatoryRequired++;
}
}
}
}
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())) && !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);
if (!missingVersions.isEmpty()) {
if (mandatoryMissing > 0) {
LOGGER.error(
LOADING,
"Missing or unsupported mandatory dependencies:\n{}",
missingVersions.stream()
.filter(IModInfo.ModVersion::isMandatory)
.map(ver -> formatDependencyError(ver, modVersions))
.collect(Collectors.joining("\n"))
);
}
if (missingVersions.size() - mandatoryMissing > 0) {
LOGGER.error(
LOADING,
"Unsupported installed optional dependencies:\n{}",
missingVersions.stream()
.filter(ver -> !ver.isMandatory())
.map(ver -> formatDependencyError(ver, modVersions))
.collect(Collectors.joining("\n"))
);
}
final var missingMandatory = new HashSet<ModVersion>();
final var missingOptional = new HashSet<ModVersion>();
return missingVersions.stream()
.map(mv -> new ExceptionData(mv.isMandatory() ? "fml.modloading.missingdependency" : "fml.modloading.missingdependency.optional",
mv.getOwner(), mv.getModId(), mv.getOwner().getModId(), mv.getVersionRange(),
modVersions.getOrDefault(mv.getModId(), new DefaultArtifactVersion("null"))))
.toList();
for (var dep : modRequirements) {
var modId = dep.getModId();
var existing = modVersions.get(modId);
if (!dep.isMandatory() && existing == null)
continue;
var range = dep.getVersionRange();
if (existing != null && (range.containsVersion(existing) || "0.0NONE".equals(existing.toString())))
continue;
if (!VersionSupportMatrix.testVersionSupportMatrix(range, modId, "mod"))
(dep.isMandatory() ? missingMandatory : missingOptional).add(dep);
}
return Collections.emptyList();
LOGGER.debug(LOADING, "Found {} mod requirements missing ({} mandatory, {} optional)", missingMandatory.size() + missingOptional.size(), missingMandatory.size(), missingOptional.size());
var ret = new ArrayList<ExceptionData>();
if (!missingMandatory.isEmpty()) {
LOGGER.error(LOADING, "Missing or unsupported mandatory dependencies:\n{}", formatDependencyError(missingMandatory, modVersions));
for (var mv : missingMandatory)
ret.add(data(mv, modVersions, "fml.modloading.missingdependency"));
}
if (!missingOptional.isEmpty()) {
LOGGER.error(LOADING, "Unsupported installed optional dependencies:\n{}", formatDependencyError(missingMandatory, modVersions));
for (var mv : missingMandatory)
ret.add(data(mv, modVersions, "fml.modloading.missingdependency.optional"));
}
return ret;
}
private static String formatDependencyError(IModInfo.ModVersion dependency, Map<String, ArtifactVersion> modVersions)
{
ArtifactVersion installed = modVersions.get(dependency.getModId());
return String.format(
private static String formatDependencyError(Collection<ModVersion> missing, Map<String, ArtifactVersion> modVersions) {
var ret = new ArrayList<String>();
for (var dep : missing) {
var installed = modVersions.get(dep.getModId());
ret.add(String.format(
"\tMod ID: '%s', Requested by: '%s', Expected range: '%s', Actual version: '%s'",
dependency.getModId(),
dependency.getOwner().getModId(),
dependency.getVersionRange(),
dep.getModId(),
dep.getOwner().getModId(),
dep.getVersionRange(),
installed != null ? installed.toString() : "[MISSING]"
);
));
}
return String.join("\n", ret);
}
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");
private static final ArtifactVersion NULL_VERSION = new DefaultArtifactVersion("null");
private static ExceptionData data(ModVersion mv, Map<String, ArtifactVersion> modVersions, String key) {
return new ExceptionData(key, mv.getOwner(), mv.getModId(), mv.getOwner().getModId(), mv.getVersionRange(), modVersions.getOrDefault(mv.getModId(), NULL_VERSION));
}
}
@@ -203,7 +203,14 @@ public class RuntimeDistCleaner implements ILaunchPluginService {
@Override
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
return isEmpty ? NAY : YAY;
if (isEmpty)
return NAY;
String internalName = classType.getInternalName();
if (internalName.startsWith("net/minecraftforge/"))
return NAY;
return YAY;
}
private static class LambdaGatherer extends MethodVisitor {
@@ -9,6 +9,7 @@ import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.forgespi.language.IModInfo;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.jetbrains.annotations.ApiStatus;
import org.slf4j.Logger;
import java.util.ArrayList;
@@ -23,16 +24,18 @@ import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.joining;
import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
public class UniqueModListBuilder
{
@ApiStatus.Internal
@Deprecated(since = "1.21.3", forRemoval = true) // TODO: [FML][Loading] Convert to package private in 1.22
public class UniqueModListBuilder {
private final static Logger LOGGER = LogUtils.getLogger();
private final List<ModFile> modFiles;
public UniqueModListBuilder(final List<ModFile> modFiles) {this.modFiles = modFiles;}
public UniqueModListBuilder(final List<ModFile> modFiles) {
this.modFiles = modFiles;
}
public UniqueModListData buildUniqueList()
{
public UniqueModListData buildUniqueList() {
List<ModFile> uniqueModList;
List<ModFile> uniqueLibListWithVersion;
@@ -135,9 +138,8 @@ public class UniqueModListBuilder
return modFile.getSecureJar().name();
}
return modFile.getModFileInfo().moduleName();
return modFile.getModFileInfo().getMods().get(0).getModId();
}
public record UniqueModListData(List<ModFile> modFiles, Map<String, List<ModFile>> modFilesByFirstId) {}
}
@@ -6,6 +6,7 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import cpw.mods.jarhandling.JarMetadata;
import cpw.mods.jarhandling.SecureJar;
import net.minecraftforge.fml.loading.LogMarkers;
@@ -22,19 +23,25 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import java.io.IOException;
import java.lang.module.InvalidModuleDescriptorException;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleDescriptor.Version;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
@ApiStatus.Internal
public abstract class AbstractModProvider implements IModProvider {
private static final Logger LOGGER = LogUtils.getLogger();
protected static final String MODS_TOML = "META-INF/mods.toml";
private static final Logger LOGGER = LogUtils.getLogger();
protected static final String MODS_TOML = "META-INF/mods.toml";
protected static final String MODULE_INFO = "module-info.class";
protected IModLocator.ModFileOrException createMod(Path path) {
return createMod(path, false);
@@ -48,10 +55,12 @@ public abstract class AbstractModProvider implements IModProvider {
@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),
path
);
SecureJar sj = null;
try {
sj = SecureJar.from(jar -> loadMetaFromJar(jar, mjm), path);
} catch (Throwable t) {
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Failed to create secure jar for \"" + path + "\" - " + t.getMessage()));
}
IModFile mod;
var type = sj.moduleDataProvider().getManifest().getMainAttributes().getValue(ModFile.TYPE);
@@ -61,6 +70,8 @@ public abstract class AbstractModProvider implements IModProvider {
if (sj.moduleDataProvider().findFile(MODS_TOML).isPresent()) {
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MODS_TOML, type, path);
mod = new ModFile(sj, this, ModFileParser::modsTomlParser);
// ModJarMetadata is only used when loading mods.toml mods as ModJarMetadata
mjm.setModFile(mod);
if (mod.getModFileInfo().getFileProperties().containsKey(ModFileInfo.NOT_A_FORGE_MOD_PROP)) {
LOGGER.error(LogMarkers.SCAN, "Unable to load file \"{}\" because its mods.toml is requesting an invalid javafml loaderVersion (use \"*\" if you want to allow all versions) and is missing a forge modId dependency declaration (see the sample mods.toml in the MDK).", path);
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("File \"%s\" is not a Forge mod and cannot be loaded. Look for a Forge version of this mod or consider alternative mods.".formatted(mod.getFileName())));
@@ -73,10 +84,54 @@ public abstract class AbstractModProvider implements IModProvider {
} else
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Invalid mod file found " + path));
mjm.setModFile(mod);
return new IModLocator.ModFileOrException(mod, null);
}
private static JarMetadata loadMetaFromJar(SecureJar jar, ModJarMetadata mjm) {
var info = jar.moduleDataProvider().open(MODULE_INFO).orElse(null);
if (info != null) {
try {
var desc = ModuleDescriptor.read(info, jar::getPackages);
var all = new HashSet<>(jar.getPackages());
all.removeAll(desc.packages());
if (!all.isEmpty()) {
var missing = all.stream().sorted().collect(Collectors.joining(", "));
LOGGER.error("Invalid module-info, missing packages " + missing);
throw new ModFileLoadingException("Invalid module-info, missing packages " + missing);
} else {
return new JarMetadata() {
@Override
public String name() {
return desc.name();
}
@Override
public String version() {
return desc.version().map(Version::toString).or(desc::rawVersion).orElse(null);
}
@Override
public ModuleDescriptor descriptor() {
return desc;
}
};
}
} catch (InvalidModuleDescriptorException | IOException e) {
LOGGER.error("Failed to parse " + jar.getPrimaryPath() + " module-info", e);
throw new ModFileLoadingException("Invalid module-info: " + e.getMessage());
} finally {
try {
info.close();
} catch (IOException e) {}
}
}
if (jar.moduleDataProvider().findFile(MODS_TOML).isEmpty())
return JarMetadata.from(jar, jar.getPrimaryPath());
return mjm;
}
protected IModFileInfo manifestParser(final IModFile mod) {
var mf = mod.getSecureJar().moduleDataProvider().getManifest().getMainAttributes();
var license = mf.getValue("LICENSE");
@@ -22,13 +22,11 @@ public final class MinecraftLocator extends AbstractModProvider implements IModL
@Override
public List<IModLocator.ModFileOrException> scanMods() {
var minecraft = FMLLoader.getLaunchHandler().getMinecraftPaths();
var paths = minecraft.toArray(Path[]::new);
// Minecraft itself.
var meta = new ModJarMetadata();
var mcjar = SecureJar.from(
jar -> meta,
minecraft.toArray(Path[]::new)
);
var mcjar = SecureJar.from(jar -> meta, paths);
var mc = ModFileFactory.FACTORY.build(mcjar, this, MinecraftLocator::buildMinecraftTOML);
meta.setModFile(mc);
@@ -68,6 +68,7 @@ public class ModDiscoverer {
}
}
@SuppressWarnings("removal")
public ModValidator discoverMods() {
LOGGER.debug(LogMarkers.SCAN,"Scanning for mods and other resources to load. We know {} ways to find mods", modLocatorList.size());
List<ModFile> loadedFiles = new ArrayList<>();
@@ -259,7 +259,7 @@ public class ModFileInfo implements IModFileInfo, IConfigurable {
@Override
public String moduleName() {
return getMods().get(0).getModId();
return this.modFile.getSecureJar().name();
}
@Override
@@ -9,49 +9,64 @@ import cpw.mods.jarhandling.JarMetadata;
import net.minecraftforge.forgespi.locating.IModFile;
import java.lang.module.ModuleDescriptor;
import java.util.Objects;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Internal
public final class ModJarMetadata implements JarMetadata {
private static final String AUTOMATIC_MODULE_NAME = "Automatic-Module-Name";
private IModFile modFile;
private String name;
private String version;
private ModuleDescriptor descriptor;
ModJarMetadata() {
}
ModJarMetadata() { }
public void setModFile(IModFile file) {
this.modFile = file;
var mods = this.modFile.getModFileInfo().getMods();
if (!mods.isEmpty()) {
var main = mods.get(0);
this.name = main.getModId();
this.version = main.getVersion().toString();
}
var jar = file.getSecureJar();
var aname = jar.moduleDataProvider().getManifest().getMainAttributes().getValue(AUTOMATIC_MODULE_NAME);
if (aname != null)
this.name = aname;
}
@Override
public String name() {
return modFile.getModFileInfo().moduleName();
return this.name;
}
@Override
public String version() {
return modFile.getModFileInfo().versionString();
return this.version;
}
@Override
public ModuleDescriptor descriptor() {
if (descriptor != null) return descriptor;
if (descriptor != null)
return descriptor;
var bld = ModuleDescriptor.newAutomaticModule(name())
.version(version())
.packages(modFile.getSecureJar().getPackages());
modFile.getSecureJar().getProviders().stream()
.filter(p -> !p.providers().isEmpty())
.forEach(p -> bld.provides(p.serviceName(), p.providers()));
modFile.getModFileInfo().usesServices().forEach(bld::uses);
.version(version())
.packages(modFile.getSecureJar().getPackages());
for (var provider : modFile.getSecureJar().getProviders()) {
if (provider.providers().isEmpty())
continue;
bld.provides(provider.serviceName(), provider.providers());
}
descriptor = bld.build();
return descriptor;
}
public IModFile modFile() {
return modFile;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
@@ -46,7 +46,7 @@ record Scanner(ModFile fileToScan, ModFileScanData result) {
try (InputStream in = Files.newInputStream(path)) {
ModClassVisitor mcv = new ModClassVisitor();
ClassReader cr = new ClassReader(in);
cr.accept(mcv, 0);
cr.accept(mcv, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
mcv.buildData(result.getClasses(), result.getAnnotations());
} catch (IOException | IllegalArgumentException e) {
// mark path bad
@@ -73,7 +73,7 @@ public abstract class CommonLaunchHandler implements ILaunchHandlerService {
protected static final LaunchType CLIENT = new LaunchType("client", "minecraft", "net.minecraft.client.main.Main", Dist.CLIENT, false);
protected static final LaunchType DATA = new LaunchType("data", "minecraft", "net.minecraft.data.Main", Dist.CLIENT, true);
protected static final LaunchType SERVER = new LaunchType("server", "minecraft", "net.minecraft.server.Main", Dist.DEDICATED_SERVER, false);
protected static final LaunchType SERVER_GAMETEST = new LaunchType("server_gametest", "forge", "net.minecraftforge.gametest.GameTestMain", Dist.DEDICATED_SERVER, false);
protected static final LaunchType SERVER_GAMETEST = new LaunchType("server_gametest", "net.minecraftforge.forge", "net.minecraftforge.gametest.GameTestMain", Dist.DEDICATED_SERVER, false);
protected void runTarget(String module, String target, final String[] arguments, final ModuleLayer layer) throws Throwable {
var mod = layer.findModule(module).orElse(null);
@@ -91,23 +91,24 @@ public final class ForgeDevLocator extends AbstractModProvider implements IModLo
// First lets find all class files, that have the @Mod annotation and map packages to modids.
var packages = findTestModPackages(path);
for (var entry : packages.entrySet()) {
var pkg = entry.getKey();
var pkg = entry.getKey() + '/';
var modids = entry.getValue();
// Find resource directories for every mod in this group
var resourcePaths = new LinkedHashSet<Path>();
var paths = new LinkedHashSet<Path>();
for (var modid : modids) {
var rsc = path.resolve(modid);
if (Files.exists(rsc))
resourcePaths.add(rsc);
paths.add(rsc);
}
var root = memory.getPath(modids.iterator().next()); // use the first modid as our root
buildModsToml(resourcePaths, modids, root);
buildPackMeta(resourcePaths, root);
buildModsToml(paths, modids, root);
buildPackMeta(paths, root);
moveModuleInfo(paths, root);
if (Files.exists(root))
resourcePaths.add(root);
paths.add(root);
// We want just the class files from the root of the input paths. So make a new union with a filter.
var classes = UnionHelper.newFileSystem(
@@ -121,14 +122,10 @@ public final class ForgeDevLocator extends AbstractModProvider implements IModLo
}, new Path[] { path }
);
// And we want the resources, and none of the classes, just in case things get kinda fucky and somehow a package overlaps a sub package {shading?}
var resources = UnionHelper.newFileSystem((name, base) -> !name.endsWith(".class"), resourcePaths.toArray(Path[]::new));
paths.addFirst(classes.getRootDirectories().iterator().next());
// Union of unions, Yay!
var union = UnionHelper.newFileSystem(null, new Path[] {
classes.getRootDirectories().iterator().next(),
resources.getRootDirectories().iterator().next()
});
var union = UnionHelper.newFileSystem(null, paths.stream().toArray(Path[]::new));
mod.add(union.getRootDirectories().iterator().next());
}
@@ -274,6 +271,25 @@ public final class ForgeDevLocator extends AbstractModProvider implements IModLo
}
}
private static void moveModuleInfo(Set<Path> paths, Path root) {
var existing = paths.stream()
.map(p -> p.resolve("module-info.dat"))
.filter(Files::exists)
.findFirst()
.orElse(null);
if (existing == null)
return;
var target = root.resolve("module-info.class");
try {
Files.createDirectories(target.getParent());
Files.copy(existing, target);
} catch (IOException e) {
throw new IllegalStateException("Failed to copy module-info.dat to memory: " + target, e);
}
}
@SuppressWarnings("unchecked")
private static <E extends Throwable, R> R sneak(Throwable e) throws E {
throw (E)e;
Binary file not shown.
+2 -1
View File
@@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+13 -9
View File
@@ -83,7 +83,8 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -130,10 +131,13 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
@@ -141,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@@ -149,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -198,11 +202,11 @@ fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
Vendored
+10 -10
View File
@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
+3 -1
View File
@@ -13,6 +13,8 @@ dependencies {
compileOnly(libs.nulls)
implementation(project(':fmlloader'))
implementation(project(':fmlcore'))
implementation(libs.unsafe)
implementation(libs.securemodules)
}
java {
@@ -57,7 +59,7 @@ publishing {
license PomUtils.Licenses.LGPLv2_1
}
}
repositories {
maven gradleutils.publishingForgeMaven
}
@@ -13,18 +13,24 @@ import net.minecraftforge.eventbus.api.IEventListener;
import net.minecraftforge.fml.ModContainer;
import net.minecraftforge.fml.ModLoadingException;
import net.minecraftforge.fml.ModLoadingStage;
import net.minecraftforge.fml.config.IConfigEvent;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.language.ModFileScanData;
import net.minecraftforge.unsafe.UnsafeHacks;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import cpw.mods.jarhandling.SecureJar;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.Optional;
import java.util.jar.Attributes;
public class FMLModContainer extends ModContainer {
private static final Logger LOGGER = LogManager.getLogger();
@@ -41,20 +47,87 @@ public class FMLModContainer extends ModContainer {
this.scanResults = modFileScanResults;
activityMap.put(ModLoadingStage.CONSTRUCT, this::constructMod);
this.eventBus = BusBuilder.builder().setExceptionHandler(FMLModContainer::onEventFailed).setTrackPhases(false).markerType(IModBusEvent.class).useModLauncher().build();
this.configHandler = Optional.of(ce->this.eventBus.post(ce.self()));
this.contextExtension = () -> context;
try {
var moduleName = info.getOwningFile().moduleName();
var module = gameLayer.findModule(moduleName)
.orElseThrow(() -> new IllegalStateException("Failed to find " + moduleName + " in " + gameLayer));
openModules(gameLayer, module, info.getOwningFile().getFile().getSecureJar());
modClass = Class.forName(module, className);
LOGGER.trace(LOADING,"Loaded modclass {} with {}", modClass.getName(), modClass.getClassLoader());
LOGGER.debug(LOADING,"Loaded modclass {}/{} with {}", modClass.getModule().getName(), modClass.getName(), modClass.getClassLoader());
} catch (Throwable e) {
LOGGER.error(LOADING, "Failed to load class {}", className, e);
throw new ModLoadingException(info, ModLoadingStage.CONSTRUCT, "fml.modloading.failedtoloadmodclass", e);
}
}
/**
* <pre>
* Reads the Add-Exports and Add-Opens attributes (see note) from a mod file and
* attempts to apply them. This differers from the JEP in two significant ways:
* 1) Instead of opening things to ALL-UNNAMED it instead only opens to the
* mod's module itself. Because I don't want other mods accidently relying
* on transitive behavior
* 2) It is read from all mods not just the executable jars.
*
* From <a href = "https://openjdk.org/jeps/261">JEP 261: Module System</a>
*
* Two new JDK-specific JAR-file manifest attributes are defined to correspond
* to the --add-exports and --add-opens command-line options:
* Add-Exports: &lt;module&gt;/&lt;package&gt;( &lt;module&gt;/&lt;package&gt;)*
* Add-Opens: &lt;module&gt;/&lt;package&gt;( &lt;module&gt;/&lt;package&gt;)*
*
* The value of each attribute is a space-separated list of slash-separated
* module-name/package-name pairs. A &gt;module&lt;/&gt;package&lt; pair in
* the value of an Add-Exports attribute has the same meaning as the
* command-line option --add-exports &gt;module&lt;/&gt;package&lt;=ALL-UNNAMED.
*
* A &gt;module&lt;/&gt;package&lt; pair in the value of an Add-Opens attribute has the
* same meaning as the command-line option --add-opens &gt;module&lt;/&gt;package&lt;=ALL-UNNAMED.
*
* Each attribute can occur at most once, in the main section of a MANIFEST.MF file.
* A particular pair can be listed more than once. If a specified module was not
* resolved, or if a specified package does not exist, then the corresponding pair
* is ignored.
*/
private static void openModules(ModuleLayer layer, Module self, SecureJar jar) throws NoSuchMethodException, SecurityException, IllegalAccessException, InvocationTargetException {
var manifest = jar.moduleDataProvider().getManifest().getMainAttributes();
addOpenOrExports(layer, self, true, manifest);
addOpenOrExports(layer, self, false, manifest);
}
private static void addOpenOrExports(ModuleLayer layer, Module self, boolean open, Attributes attrs) throws NoSuchMethodException, SecurityException, IllegalAccessException, InvocationTargetException {
var key = open ? "Add-Opens" : "Add-Exports";
var entry = attrs.getValue(key);
if (entry == null)
return;
for (var pair : entry.split(" ")) {
var pts = pair.trim().split("/");
if (pts.length == 2) {
var target = layer.findModule(pts[0]).orElse(null);
if (target == null || !target.getDescriptor().packages().contains(pts[1]))
continue;
addOpenOrExport(target, pts[1], self, open);
} else {
LOGGER.warn(LOADING, "Invalid {} entry in {}: {}", key, self.getName(), pair);
}
}
}
private static Method implAddExportsOrOpens;
private static void addOpenOrExport(Module target, String pkg, Module reader, boolean open) throws NoSuchMethodException, SecurityException, IllegalAccessException, InvocationTargetException {
if (implAddExportsOrOpens == null) {
implAddExportsOrOpens = Module.class.getDeclaredMethod("implAddExportsOrOpens", String.class, Module.class, boolean.class, boolean.class);
UnsafeHacks.setAccessible(implAddExportsOrOpens);
}
LOGGER.info(LOADING, "{} {}/{} to {}", open ? "Opening" : "Exporting", target.getName(), pkg, reader.getName());
implAddExportsOrOpens.invoke(target, pkg, reader, open, /*syncVM*/true);
}
private static void onEventFailed(IEventBus iEventBus, Event event, IEventListener[] iEventListeners, int i, Throwable throwable) {
LOGGER.error(new EventBusErrorMessage(event, i, iEventListeners, throwable));
}
@@ -115,4 +188,14 @@ public class FMLModContainer extends ModContainer {
throw new ModLoadingException(modInfo, modLoadingStage, "fml.modloading.errorduringevent", t);
}
}
@Override
public void dispatchConfigEvent(IConfigEvent event) {
this.eventBus.post(event.self());
}
@Override
public String toString() {
return "FMLModContainer[" + this.getModInfo().getModId() + ", " + this.getClass().getName() + "]";
}
}
+4 -3
View File
@@ -141,9 +141,6 @@ dependencies {
// For more info:
// http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html
// http://www.gradle.org/docs/current/userguide/dependency_management.html
// Hack fix for now, force jopt-simple to be exactly 5.0.4 because Mojang ships that version, but some transitive dependencies request 6.0+
implementation('net.sf.jopt-simple:jopt-simple:5.0.4') { version { strictly '5.0.4' } }
}
// This block of code expands all declared replace properties in the specified resource targets.
@@ -197,6 +194,10 @@ tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
}
// IntelliJ no longer downloads javadocs and sources by default.
// This tells Gradle to force IDEA to do it.
idea.module { downloadJavadoc = downloadSources = true }
eclipse {
// Run everytime eclipse builds the code
//autoBuildTasks genEclipseRuns
@@ -11,18 +11,11 @@
}
this.setFilterMode(9728, true);
@@ -106,8 +_,13 @@
GlStateManager._texImage2D(3553, 0, 32856, this.width, this.height, 0, 6408, 5121, null);
GlStateManager._glBindFramebuffer(36160, this.frameBufferId);
@@ -108,6 +_,7 @@
GlStateManager._glFramebufferTexture2D(36160, 36064, 3553, this.colorTextureId, 0);
- if (this.useDepth) {
+ if (this.useDepth && !stencilEnabled) {
if (this.useDepth) {
GlStateManager._glFramebufferTexture2D(36160, 36096, 3553, this.depthBufferId, 0);
+ } else if(this.useDepth && net.minecraftforge.common.ForgeConfig.CLIENT.useCombinedDepthStencilAttachment.get()) {
+ GlStateManager._glFramebufferTexture2D(org.lwjgl.opengl.GL30.GL_FRAMEBUFFER, org.lwjgl.opengl.GL30.GL_DEPTH_STENCIL_ATTACHMENT, 3553, this.depthBufferId, 0);
+ } else if (this.useDepth) {
+ GlStateManager._glFramebufferTexture2D(org.lwjgl.opengl.GL30.GL_FRAMEBUFFER, org.lwjgl.opengl.GL30.GL_DEPTH_ATTACHMENT, 3553, this.depthBufferId, 0);
+ GlStateManager._glFramebufferTexture2D(org.lwjgl.opengl.GL30.GL_FRAMEBUFFER, org.lwjgl.opengl.GL30.GL_STENCIL_ATTACHMENT, 3553, this.depthBufferId, 0);
+ if (stencilEnabled) GlStateManager._glFramebufferTexture2D(org.lwjgl.opengl.GL30.GL_FRAMEBUFFER, org.lwjgl.opengl.GL30.GL_STENCIL_ATTACHMENT, 3553, this.depthBufferId, 0);
}
this.checkStatus();
@@ -1,5 +1,17 @@
--- a/com/mojang/blaze3d/platform/GlStateManager.java
+++ b/com/mojang/blaze3d/platform/GlStateManager.java
@@ -94,6 +_,11 @@
}
}
+ public static boolean _isBlendEnabled() {
+ RenderSystem.assertOnRenderThread();
+ return BLEND.mode.enabled;
+ }
+
public static void _disableBlend() {
RenderSystem.assertOnRenderThread();
BLEND.mode.disable();
@@ -519,9 +_,17 @@
}
}
@@ -0,0 +1,14 @@
--- a/com/mojang/blaze3d/systems/RenderSystem.java
+++ b/com/mojang/blaze3d/systems/RenderSystem.java
@@ -196,6 +_,11 @@
GlStateManager._depthMask(p_69459_);
}
+ public static boolean isBlendEnabled() {
+ assertOnRenderThread();
+ return GlStateManager._isBlendEnabled();
+ }
+
public static void enableBlend() {
assertOnRenderThread();
GlStateManager._enableBlend();
@@ -145,6 +145,14 @@
this.screen = p_91153_;
if (this.screen != null) {
this.screen.added();
@@ -1146,6 +_,7 @@
FreeTypeUtil.destroy();
Util.shutdownExecutors();
+ net.minecraftforge.fml.config.ConfigTracker.INSTANCE.forceUnload();
} catch (Throwable throwable) {
LOGGER.error("Shutdown failure!", throwable);
throw throwable;
@@ -1215,9 +_,11 @@
this.mouseHandler.handleAccumulatedMovement();
profilerfiller.pop();
@@ -11,15 +11,17 @@
}
}
@@ -171,8 +_,11 @@
@@ -171,8 +_,13 @@
if (!(f < 1.0F)) {
PostChain postchain = this.minecraft.getShaderManager().getPostChain(BLUR_POST_CHAIN_ID, LevelTargetBundle.MAIN_TARGETS);
if (postchain != null) {
+ // FORGE: Blending the blur was removed in 1.21. This is necessary for screen layering to work properly. https://github.com/MinecraftForge/MinecraftForge/issues/10114
+ RenderSystem.enableBlend();
+ boolean wasBlendEnabled = RenderSystem.isBlendEnabled();
+ if (wasBlendEnabled)
+ RenderSystem.disableBlend();
postchain.setUniform("Radius", f);
postchain.process(this.minecraft.getMainRenderTarget(), this.resourcePool);
+ RenderSystem.disableBlend();
+ if (wasBlendEnabled)
+ RenderSystem.enableBlend();
}
}
}
@@ -38,12 +38,12 @@
}
}
+ /** @deprecated Forge: Use {@link net.minecraftforge.client.RenderTypeHelper#getEntityRenderType(RenderType, boolean)} while iterating through {@link net.minecraft.client.resources.model.BakedModel#getRenderTypes(BlockState, net.minecraft.util.RandomSource, net.minecraftforge.client.model.data.ModelData)}. */
+ /** @deprecated Forge: Use {@link net.minecraftforge.client.RenderTypeHelper#getEntityRenderType(RenderType)} while iterating through {@link net.minecraft.client.resources.model.BakedModel#getRenderTypes(BlockState, net.minecraft.util.RandomSource, net.minecraftforge.client.model.data.ModelData)}. */
+ @Deprecated // Note: this method does NOT support model-based render types
public static RenderType getRenderType(BlockState p_364446_) {
RenderType rendertype = getChunkRenderType(p_364446_);
return rendertype == RenderType.translucent() ? Sheets.translucentItemSheet() : Sheets.cutoutBlockSheet();
@@ -385,12 +_,77 @@
@@ -385,12 +_,82 @@
}
}
@@ -59,6 +59,11 @@
renderCutout = p_109292_;
+ }
+
+ /** Forge: Check if we are running in {@linkplain net.minecraft.client.Minecraft#useFancyGraphics() fancy graphics} to account for fast graphics render types */
+ public static boolean isFancy() {
+ return renderCutout;
+ }
+
+ private static final net.minecraftforge.client.ChunkRenderTypeSet CUTOUT_MIPPED = net.minecraftforge.client.ChunkRenderTypeSet.of(RenderType.cutoutMipped());
+ private static final net.minecraftforge.client.ChunkRenderTypeSet SOLID = net.minecraftforge.client.ChunkRenderTypeSet.of(RenderType.solid());
+ private static final Map<net.minecraft.core.Holder.Reference<Block>, net.minecraftforge.client.ChunkRenderTypeSet> BLOCK_RENDER_TYPES = Util.make(new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(TYPE_BY_BLOCK.size(), 0.5F), map -> {
@@ -75,7 +80,7 @@
+ });
+
+ /** @deprecated Use {@link net.minecraft.client.resources.model.BakedModel#getRenderTypes(BlockState, net.minecraft.util.RandomSource, net.minecraftforge.client.model.data.ModelData)}. */
+ //@Deprecated(since = "1.19", forRemoval = true)
+ @Deprecated(since = "1.21.4", forRemoval = true)
+ public static net.minecraftforge.client.ChunkRenderTypeSet getRenderLayers(BlockState state) {
+ Block block = state.getBlock();
+ if (block instanceof LeavesBlock) {
@@ -86,20 +91,20 @@
+ }
+
+ /** @deprecated Set your render type in your block model's JSON (eg. {@code "render_type": "cutout"}) or override {@link net.minecraft.client.resources.model.BakedModel#getRenderTypes(BlockState, net.minecraft.util.RandomSource, net.minecraftforge.client.model.data.ModelData)} */
+ //@Deprecated(since = "1.19", forRemoval = true)
+ @Deprecated(since = "1.21.4", forRemoval = true)
+ public static void setRenderLayer(Block block, RenderType type) {
+ com.google.common.base.Preconditions.checkArgument(type.getChunkLayerId() >= 0, "The argument must be a valid chunk render type returned by RenderType#chunkBufferLayers().");
+ setRenderLayer(block, net.minecraftforge.client.ChunkRenderTypeSet.of(type));
+ }
+
+ /** @deprecated Set your render type in your block model's JSON (eg. {@code "render_type": "cutout"}) or override {@link net.minecraft.client.resources.model.BakedModel#getRenderTypes(BlockState, net.minecraft.util.RandomSource, net.minecraftforge.client.model.data.ModelData)} */
+ //@Deprecated(since = "1.19", forRemoval = true)
+ @Deprecated(since = "1.21.4", forRemoval = true)
+ public static synchronized void setRenderLayer(Block block, java.util.function.Predicate<RenderType> predicate) {
+ setRenderLayer(block, createSetFromPredicate(predicate));
+ }
+
+ /** @deprecated Set your render type in your block model's JSON (eg. {@code "render_type": "cutout"}) or override {@link net.minecraft.client.resources.model.BakedModel#getRenderTypes(BlockState, net.minecraft.util.RandomSource, net.minecraftforge.client.model.data.ModelData)} */
+ //@Deprecated(since = "1.19", forRemoval = true)
+ @Deprecated(since = "1.21.4", forRemoval = true)
+ public static synchronized void setRenderLayer(Block block, net.minecraftforge.client.ChunkRenderTypeSet layers) {
+ checkClientLoading();
+ BLOCK_RENDER_TYPES.put(net.minecraftforge.registries.ForgeRegistries.BLOCKS.getDelegateOrThrow(block), layers);
@@ -177,15 +177,6 @@
}
}
@@ -1112,7 +_,7 @@
boolean flag = false;
if (this.minecraft.options.prioritizeChunkUpdates().get() == PrioritizeChunkUpdates.NEARBY) {
BlockPos blockpos1 = sectionrenderdispatcher$rendersection.getOrigin().offset(8, 8, 8);
- flag = blockpos1.distSqr(blockpos) < 768.0 || sectionrenderdispatcher$rendersection.isDirtyFromPlayer();
+ flag = !net.minecraftforge.common.ForgeConfig.CLIENT.alwaysSetupTerrainOffThread.get() && (blockpos1.distSqr(blockpos) < 768.0D || sectionrenderdispatcher$rendersection.isDirtyFromPlayer()); // the target is the else block below, so invert the forge addition to get there early
} else if (this.minecraft.options.prioritizeChunkUpdates().get() == PrioritizeChunkUpdates.PLAYER_AFFECTED) {
flag = sectionrenderdispatcher$rendersection.isDirtyFromPlayer();
}
@@ -1391,7 +_,7 @@
} else {
int i = p_109538_.getBrightness(LightLayer.SKY, p_109540_);
@@ -30,7 +30,7 @@
public boolean hasAmbientOcclusion() {
if (this.hasAmbientOcclusion != null) {
return this.hasAmbientOcclusion;
@@ -130,6 +_,10 @@
@@ -130,12 +_,18 @@
throw new IllegalStateException("BlockModel parent has to be a block model.");
}
@@ -41,6 +41,14 @@
this.parent = blockmodel;
}
}
@Override
public BakedModel bake(ModelBaker p_252120_, Function<Material, TextureAtlasSprite> p_250023_, ModelState p_251130_) {
+ if (this.customData.getCustomGeometry() != null)
+ return this.customData.getCustomGeometry().bake(customData, p_252120_, p_250023_, p_251130_);
return this.bake(p_250023_, p_251130_, true);
}
@@ -229,7 +_,18 @@
ItemTransform itemtransform5 = this.getTransform(ItemDisplayContext.GUI);
ItemTransform itemtransform6 = this.getTransform(ItemDisplayContext.GROUND);
@@ -41,8 +41,8 @@
+
+ @Override
+ public void render(PlayerRenderState state, PoseStack pose, MultiBufferSource source, int p_115313_) {
+ if (net.minecraftforge.client.event.ForgeEventFactoryClient.onRenderLivingPre(state, this, pose, source, p_115313_)) return;
+ if (net.minecraftforge.client.event.ForgeEventFactoryClient.onRenderPlayerPre(state, this, pose, source, p_115313_)) return;
+ super.render(state, pose, source, p_115313_);
+ net.minecraftforge.client.event.ForgeEventFactoryClient.onRenderLivingPre(state, this, pose, source, p_115313_);
+ net.minecraftforge.client.event.ForgeEventFactoryClient.onRenderPlayerPost(state, this, pose, source, p_115313_);
}
}
@@ -5,7 +5,7 @@
int k1 = Mth.log2(j1);
int l1;
- if (k1 < p_261919_) {
+ if (false) { // Forge: Do not lower the mipmap level
+ if (k1 < p_261919_ && net.minecraftforge.common.ForgeConfig.CLIENT.allowMipmapLowering()) { // Forge: Do not lower the mipmap level
LOGGER.warn("{}: dropping miplevel from {} to {}, because of minimum power of two: {}", this.location, p_261919_, k1, j1);
l1 = k1;
} else {
@@ -0,0 +1,12 @@
--- a/net/minecraft/client/resources/model/ModelDiscovery.java
+++ b/net/minecraft/client/resources/model/ModelDiscovery.java
@@ -37,7 +_,8 @@
private static Set<ModelResourceLocation> listMandatoryModels() {
Set<ModelResourceLocation> set = new HashSet<>();
BuiltInRegistries.ITEM.listElements().forEach(p_368638_ -> {
- ResourceLocation resourcelocation = p_368638_.value().components().get(DataComponents.ITEM_MODEL);
+ // Forge: Force use vanilla components without gathering mod components in the event
+ ResourceLocation resourcelocation = p_368638_.value().vanillaComponents().get(DataComponents.ITEM_MODEL);
if (resourcelocation != null) {
set.add(ModelResourceLocation.inventory(resourcelocation));
}
@@ -1,74 +1,142 @@
--- a/net/minecraft/client/resources/model/SimpleBakedModel.java
+++ b/net/minecraft/client/resources/model/SimpleBakedModel.java
@@ -25,6 +_,15 @@
@@ -25,7 +_,32 @@
protected final boolean usesBlockLight;
protected final TextureAtlasSprite particleIcon;
protected final ItemTransforms transforms;
-
+ /** Forge: Block render types to be used with {@linkplain net.minecraft.client.GraphicsStatus#FANCY fancy graphics} */
+ protected final net.minecraftforge.client.ChunkRenderTypeSet blockRenderTypes;
+ /** Forge: Block render types to be used with {@linkplain net.minecraft.client.GraphicsStatus#FAST fast graphics} */
+ protected final net.minecraftforge.client.ChunkRenderTypeSet blockRenderTypesFast;
+ /** Forge: Item render types to be used with {@linkplain net.minecraft.client.GraphicsStatus#FANCY fancy graphics} */
+ protected final List<net.minecraft.client.renderer.RenderType> itemRenderTypes;
+ /** Forge: Item render types to be used with {@linkplain net.minecraft.client.GraphicsStatus#FAST fast graphics} */
+ protected final List<net.minecraft.client.renderer.RenderType> itemRenderTypesFast;
+ /** Forge: Item render types to be used with {@linkplain net.minecraft.client.GraphicsStatus#FABULOUS fabulous graphics} */
+ protected final List<net.minecraft.client.renderer.RenderType> fabulousItemRenderTypes;
+ /** Forge: If this model's {@linkplain #blockRenderTypes block render types} are rendering cutout, to account for older leaves model JSONs */
+ protected final boolean isRenderingCutout;
+
+ /** @deprecated Forge: Use {@linkplain #SimpleBakedModel(List, Map, boolean, boolean, boolean, TextureAtlasSprite, ItemTransforms, net.minecraftforge.client.RenderTypeGroup) variant with RenderTypeGroup} **/
+ /** @deprecated Forge: Use {@linkplain #SimpleBakedModel(List, Map, boolean, boolean, boolean, TextureAtlasSprite, ItemTransforms, net.minecraftforge.client.RenderTypeGroup, net.minecraftforge.client.RenderTypeGroup) variant with RenderTypeGroup} **/
+ @Deprecated
+ public SimpleBakedModel(List<BakedQuad> p_119489_, Map<Direction, List<BakedQuad>> p_119490_, boolean p_119491_, boolean p_119492_, boolean p_119493_, TextureAtlasSprite p_119494_, ItemTransforms p_119495_) {
+ this(p_119489_, p_119490_, p_119491_, p_119492_, p_119493_, p_119494_, p_119495_, net.minecraftforge.client.RenderTypeGroup.EMPTY);
+ }
+
+ /** @deprecated Forge: Use {@linkplain #SimpleBakedModel(List, Map, boolean, boolean, boolean, TextureAtlasSprite, ItemTransforms, net.minecraftforge.client.RenderTypeGroup, net.minecraftforge.client.RenderTypeGroup) variant with RenderTypeGroup for fast graphics} **/
+ @Deprecated(forRemoval = true, since = "1.21.4")
+ public SimpleBakedModel(List<BakedQuad> p_119489_, Map<Direction, List<BakedQuad>> p_119490_, boolean p_119491_, boolean p_119492_, boolean p_119493_, TextureAtlasSprite p_119494_, ItemTransforms p_119495_, net.minecraftforge.client.RenderTypeGroup renderTypes) {
+ this(p_119489_, p_119490_, p_119491_, p_119492_, p_119493_, p_119494_, p_119495_, renderTypes, net.minecraftforge.client.RenderTypeGroup.EMPTY);
+ }
+
+ /** Constructor with {@link net.minecraftforge.client.RenderTypeGroup RenderTypeGroup} for fancy and fast graphics. Preferred over {@link net.minecraft.client.renderer.ItemBlockRenderTypes#setRenderLayer(net.minecraft.world.level.block.Block, net.minecraft.client.renderer.RenderType) ItemBlockRenderTypes.setRenderLayer(Block, RenderType)}. */
public SimpleBakedModel(
List<BakedQuad> p_119489_,
@@ -33,7 +_,8 @@
Map<Direction, List<BakedQuad>> p_119490_,
@@ -33,7 +_,9 @@
boolean p_119492_,
boolean p_119493_,
TextureAtlasSprite p_119494_,
- ItemTransforms p_119495_
+ ItemTransforms p_119495_,
+ net.minecraftforge.client.RenderTypeGroup renderTypes
+ net.minecraftforge.client.RenderTypeGroup renderTypes,
+ net.minecraftforge.client.RenderTypeGroup renderTypesFast
) {
this.unculledFaces = p_119489_;
this.culledFaces = p_119490_;
@@ -42,6 +_,9 @@
@@ -42,6 +_,15 @@
this.usesBlockLight = p_119492_;
this.particleIcon = p_119494_;
this.transforms = p_119495_;
+ this.blockRenderTypes = !renderTypes.isEmpty() ? net.minecraftforge.client.ChunkRenderTypeSet.of(renderTypes.block()) : null;
+ this.itemRenderTypes = !renderTypes.isEmpty() ? List.of(renderTypes.entity()) : null;
+ this.fabulousItemRenderTypes = !renderTypes.isEmpty() ? List.of(renderTypes.entityFabulous()) : null;
+
+ boolean hasRenderTypes = renderTypes != null && !renderTypes.isEmpty();
+ boolean hasRenderTypesFast = renderTypesFast != null && !renderTypesFast.isEmpty();
+ this.blockRenderTypes = hasRenderTypes ? net.minecraftforge.client.ChunkRenderTypeSet.of(renderTypes.block()) : null;
+ this.blockRenderTypesFast = hasRenderTypesFast ? net.minecraftforge.client.ChunkRenderTypeSet.of(renderTypesFast.block()) : null;
+ this.itemRenderTypes = hasRenderTypes ? List.of(renderTypes.entity()) : null;
+ this.itemRenderTypesFast = hasRenderTypesFast ? List.of(renderTypesFast.entity()) : null;
+ this.fabulousItemRenderTypes = hasRenderTypes ? List.of(renderTypes.entityFabulous()) : null;
+ this.isRenderingCutout = hasRenderTypes && (renderTypes.block() == net.minecraft.client.renderer.RenderType.cutout() || renderTypes.block() == net.minecraft.client.renderer.RenderType.cutoutMipped());
}
@Override
@@ -79,6 +_,25 @@
@@ -79,6 +_,38 @@
return this.transforms;
}
+ private static final net.minecraftforge.client.ChunkRenderTypeSet SOLID_BLOCK = net.minecraftforge.client.ChunkRenderTypeSet.of(net.minecraft.client.renderer.RenderType.solid());
+ private static final List<net.minecraft.client.renderer.RenderType> SOLID_BLOCK_ITEM = List.of(net.minecraft.client.renderer.RenderType.solid());
+
+ @Override
+ public net.minecraftforge.client.ChunkRenderTypeSet getRenderTypes(BlockState state, RandomSource rand, net.minecraftforge.client.model.data.ModelData data) {
+ return blockRenderTypes != null ? blockRenderTypes : BakedModel.super.getRenderTypes(state, rand, data);
+ if (!net.minecraft.client.renderer.ItemBlockRenderTypes.isFancy()) {
+ if (blockRenderTypesFast != null)
+ return blockRenderTypesFast;
+ if (isRenderingCutout && state.getBlock() instanceof net.minecraft.world.level.block.LeavesBlock)
+ return SOLID_BLOCK;
+ }
+ return blockRenderTypes != null ? blockRenderTypes : BakedModel.super.getRenderTypes(state, rand, data);
+ }
+
+ @Override
+ public List<net.minecraft.client.renderer.RenderType> getRenderTypes(net.minecraft.world.item.ItemStack itemStack, boolean fabulous) {
+ if (!fabulous) {
+ if (itemRenderTypes != null) {
+ return itemRenderTypes;
+ }
+ } else {
+ if (fabulousItemRenderTypes != null) {
+ return fabulousItemRenderTypes;
+ }
+ }
+ return BakedModel.super.getRenderTypes(itemStack, fabulous);
+ if (!fabulous) {
+ if (!net.minecraft.client.renderer.ItemBlockRenderTypes.isFancy()) {
+ if (itemRenderTypesFast != null)
+ return itemRenderTypesFast;
+ if (isRenderingCutout && itemStack.getItem() instanceof net.minecraft.world.item.BlockItem blockItem && blockItem.getBlock() instanceof net.minecraft.world.level.block.LeavesBlock)
+ return SOLID_BLOCK_ITEM;
+ }
+ if (itemRenderTypes != null)
+ return itemRenderTypes;
+ } else {
+ if (fabulousItemRenderTypes != null)
+ return fabulousItemRenderTypes;
+ }
+ return BakedModel.super.getRenderTypes(itemStack, fabulous);
+ }
+
@OnlyIn(Dist.CLIENT)
public static class Builder {
private final ImmutableList.Builder<BakedQuad> unculledFaces = ImmutableList.builder();
@@ -124,13 +_,19 @@
@@ -124,13 +_,50 @@
return this;
}
+ private net.minecraftforge.client.RenderTypeGroup renderTypes = net.minecraftforge.client.RenderTypeGroup.EMPTY;
+ private net.minecraftforge.client.RenderTypeGroup renderTypesFast = net.minecraftforge.client.RenderTypeGroup.EMPTY;
+
+ /**
+ * Sets the render type to be used for this model, and will be used for any graphics setting.
+ * <p>
+ * If you need to set a specific render type for
+ * {@linkplain net.minecraft.client.GraphicsStatus#FAST fast graphics}, consider using
+ * {@link #renderTypes(net.minecraftforge.client.RenderTypeGroup, net.minecraftforge.client.RenderTypeGroup)
+ * renderTypes(RenderTypeGroup, RenderTypeGroup)} instead which allows choosing render types for both fancy and
+ * fast graphics.
+ *
+ * @apiNote If this model is for {@linkplain net.minecraft.world.level.block.LeavesBlock leaves} and if the
+ * given render type is either {@linkplain net.minecraft.client.renderer.RenderType#cutout() cutout} or
+ * {@linkplain net.minecraft.client.renderer.RenderType#cutoutMipped() cutout mipped}, it will be overridden
+ * with {@linkplain net.minecraft.client.renderer.RenderType#solid() solid} when fast graphics is enabled.
+ * @see #renderTypes(net.minecraftforge.client.RenderTypeGroup, net.minecraftforge.client.RenderTypeGroup)
+ * renderTypes(RenderTypeGroup, RenderTypeGroup)
+ */
+ public SimpleBakedModel.Builder renderTypes(net.minecraftforge.client.RenderTypeGroup renderTypes) {
+ return this.renderTypes(renderTypes, net.minecraftforge.client.RenderTypeGroup.EMPTY);
+ }
+
+ /**
+ * Sets the render types to be used for this model: one for
+ * {@linkplain net.minecraft.client.GraphicsStatus#FANCY fancy graphics} and one for
+ * {@linkplain net.minecraft.client.GraphicsStatus#FAST fast graphics}.
+ *
+ * @see #renderTypes(net.minecraftforge.client.RenderTypeGroup)
+ */
+ public SimpleBakedModel.Builder renderTypes(net.minecraftforge.client.RenderTypeGroup renderTypes, net.minecraftforge.client.RenderTypeGroup renderTypesFast) {
+ this.renderTypes = renderTypes;
+ this.renderTypesFast = renderTypesFast;
+ return this;
+ }
+
@@ -79,7 +147,8 @@
Map<Direction, List<BakedQuad>> map = Maps.transformValues(this.culledFaces, ImmutableList.Builder::build);
return new SimpleBakedModel(
- this.unculledFaces.build(), new EnumMap<>(map), this.hasAmbientOcclusion, this.usesBlockLight, this.isGui3d, this.particleIcon, this.transforms
+ this.unculledFaces.build(), new EnumMap<>(map), this.hasAmbientOcclusion, this.usesBlockLight, this.isGui3d, this.particleIcon, this.transforms, this.renderTypes
+ // Forge: Account for render types in model JSONs
+ this.unculledFaces.build(), new EnumMap<>(map), this.hasAmbientOcclusion, this.usesBlockLight, this.isGui3d, this.particleIcon, this.transforms, this.renderTypes, this.renderTypesFast
);
}
}
@@ -1,12 +1,17 @@
--- a/net/minecraft/core/Direction.java
+++ b/net/minecraft/core/Direction.java
@@ -49,6 +_,10 @@
@@ -49,6 +_,15 @@
private final Vec3i normal;
private final Vec3 normalVec3;
private static final Direction[] VALUES = values();
+ private static final List<Direction> VALUES_VIEW = java.util.Collections.unmodifiableList(Arrays.asList(VALUES));
+ public static final List<Direction> valuesView() {
+ return VALUES_VIEW;
+ }
+ // In vanilla updates are notified only on the horizontal plane, in Forge we also notify up and down
+ private static final List<Direction> UPDATE_ORDER = java.util.Collections.unmodifiableList(Stream.concat(Plane.HORIZONTAL.stream(), Plane.VERTICAL.stream()).toList());
+ public static final List<Direction> getUpdateOrder() {
+ return UPDATE_ORDER;
+ }
private static final Direction[] BY_3D_DATA = Arrays.stream(VALUES)
.sorted(Comparator.comparingInt(p_235687_ -> p_235687_.data3d))
@@ -35,7 +35,7 @@
}
if (this.allTags.isBound()) {
@@ -390,6 +_,26 @@
@@ -390,6 +_,31 @@
};
}
@@ -49,6 +49,11 @@
+ }
+
+ protected final void markKnown() {
+ // Some vanilla registries rely on others in some objects
+ // So we have to have a last-in ordering. Simplest way is to just remove and re-add our selves.
+ // A copy of this map is then captured after vanilla bootstraping to freeze it hopefully before Modder's change the order.
+ // https://github.com/MinecraftForge/MinecraftForge/issues/10179
+ KNOWN.remove(key().location());
+ KNOWN.add(key().location());
+ }
+
@@ -9,3 +9,29 @@
private final GameTestInfo testInfo;
private boolean finalCheckAdded;
@@ -996,5 +_,25 @@
if (either.right().isPresent()) {
this.fail("Failed to set biome for test");
}
+ }
+
+ /**
+ * Adds a cleanup handler that will be called when the test is done, pass or fail.
+ */
+ public void addCleanup(Consumer<Boolean> handler) {
+ this.testInfo.addListener(new GameTestListener() {
+ @Override
+ public void testPassed(GameTestInfo info, GameTestRunner runner) {
+ handler.accept(true);
+ }
+
+ @Override
+ public void testFailed(GameTestInfo info, GameTestRunner runner) {
+ handler.accept(false);
+ }
+
+ @Override public void testStructureLoaded(GameTestInfo info) { }
+ @Override public void testAddedForRerun(GameTestInfo oldInfo, GameTestInfo newInfo, GameTestRunner runner) { }
+ });
}
}
@@ -0,0 +1,11 @@
--- a/net/minecraft/gametest/framework/GameTestInfo.java
+++ b/net/minecraft/gametest/framework/GameTestInfo.java
@@ -265,7 +_,7 @@
BlockPos blockpos = this.getOrCalculateNorthwestCorner();
this.structureBlockEntity = StructureUtils.prepareTestStructure(this, blockpos, this.getRotation(), this.level);
this.structureBlockPos = this.structureBlockEntity.getBlockPos();
- StructureUtils.addCommandBlockAndButtonToStartTest(this.structureBlockPos, new BlockPos(1, 0, -1), this.getRotation(), this.level);
+ StructureUtils.addCommandBlockAndButtonToStartTest(this.structureBlockPos, new BlockPos(1, 0, -1), Rotation.NONE, this.level); // Forge: The control blocks are always in the North West Corner
StructureUtils.encaseStructure(this.getStructureBounds(), this.level, !this.testFunction.skyAccess());
this.listeners.forEach(p_127630_ -> p_127630_.testStructureLoaded(this));
return this;
@@ -0,0 +1,22 @@
--- a/net/minecraft/gametest/framework/ReportGameListener.java
+++ b/net/minecraft/gametest/framework/ReportGameListener.java
@@ -151,7 +_,7 @@
private static BlockPos getBeaconPos(GameTestInfo p_344999_) {
BlockPos blockpos = p_344999_.getStructureBlockPos();
BlockPos blockpos1 = new BlockPos(-1, -2, -1);
- return StructureTemplate.transform(blockpos.offset(blockpos1), Mirror.NONE, p_344999_.getRotation(), blockpos);
+ return blockpos.offset(blockpos1); // Forge: The control blocks are always in the North West Corner
}
private static void updateBeaconGlass(GameTestInfo p_343978_, Block p_344076_) {
@@ -167,8 +_,8 @@
ServerLevel serverlevel = p_177739_.getLevel();
BlockPos blockpos = p_177739_.getStructureBlockPos();
BlockPos blockpos1 = new BlockPos(-1, 0, -1);
- BlockPos blockpos2 = StructureTemplate.transform(blockpos.offset(blockpos1), Mirror.NONE, p_177739_.getRotation(), blockpos);
- serverlevel.setBlockAndUpdate(blockpos2, Blocks.LECTERN.defaultBlockState().rotate(p_177739_.getRotation()));
+ BlockPos blockpos2 = blockpos.offset(blockpos1); // Forge: The control blocks are always in the North West Corner
+ serverlevel.setBlockAndUpdate(blockpos2, Blocks.LECTERN.defaultBlockState());
BlockState blockstate = serverlevel.getBlockState(blockpos2);
ItemStack itemstack = createBook(p_177739_.getTestName(), p_177739_.isRequired(), p_177740_);
LecternBlock.tryPlaceBook(null, serverlevel, blockpos2, blockstate, itemstack);
@@ -0,0 +1,21 @@
--- a/net/minecraft/gametest/framework/StructureUtils.java
+++ b/net/minecraft/gametest/framework/StructureUtils.java
@@ -132,10 +_,16 @@
.orElseThrow(() -> new IllegalStateException("Missing test structure: " + p_311701_.getStructureName()))
.getSize();
BoundingBox boundingbox = getStructureBoundingBox(p_311042_, vec3i, p_310584_);
- BlockPos blockpos = getStartCorner(p_311701_, p_311042_, p_310584_, p_312330_);
+ BlockPos blockpos = getStartCorner(p_311701_, p_311042_, Rotation.NONE, p_312330_); // Forge: The control blocks are always in the North West Corner
forceLoadChunks(boundingbox, p_312330_);
clearSpaceForStructure(boundingbox, p_312330_);
- return createStructureBlock(p_311701_, blockpos.below(), p_310584_, p_312330_);
+ var entity = createStructureBlock(p_311701_, blockpos.below(), p_310584_, p_312330_);
+ //Forge: We need to offset the structure so that it will load within bounds.
+ if (p_310584_ != Rotation.NONE) {
+ var rotated = StructureTemplate.getZeroPositionWithTransform(BlockPos.ZERO, Mirror.NONE, p_310584_, entity.getStructureSize().getX(), entity.getStructureSize().getZ());
+ entity.setStructurePos(rotated.offset(new BlockPos(0, 1, 0)));
+ }
+ return entity;
}
public static void encaseStructure(AABB p_330422_, ServerLevel p_331249_, boolean p_328180_) {
@@ -0,0 +1,15 @@
--- a/net/minecraft/gametest/framework/TestCommand.java
+++ b/net/minecraft/gametest/framework/TestCommand.java
@@ -313,7 +_,11 @@
return Optional.empty();
} else {
TestFunction testfunction = optional.get();
- GameTestInfo gametestinfo = new GameTestInfo(testfunction, structureblockentity.getRotation(), p_328153_, p_330368_);
+ // Forge: The rotation is stored in the structure block, and added in the GameTestInfo constructor.
+ // So reverse it to find the manually specified rotation so the test runs the same every time.
+ var steps = StructureUtils.getRotationStepsForRotation(structureblockentity.getRotation()) - StructureUtils.getRotationStepsForRotation(testfunction.rotation());
+ if (steps < 0) steps += 4;
+ GameTestInfo gametestinfo = new GameTestInfo(testfunction, StructureUtils.getRotationForRotationSteps(steps), p_328153_, p_330368_);
gametestinfo.setStructureBlockPos(p_332856_);
return !verifyStructureExists(p_328153_, gametestinfo.getStructureName()) ? Optional.empty() : Optional.of(gametestinfo);
}
@@ -27,7 +27,7 @@
p_358539_.layers(), p_358539_.lookupWithUpdatedTags(), p_250212_, p_249301_, p_366334_, p_251126_
);
+ var listeners = new java.util.ArrayList<>(reloadableserverresources.listeners());
+ listeners.addAll(net.minecraftforge.event.ForgeEventFactory.onResourceReload(reloadableserverresources, p_330376_.compositeAccess()));
+ listeners.addAll(net.minecraftforge.event.ForgeEventFactory.onResourceReload(reloadableserverresources, p_358539_.lookupWithUpdatedTags(), p_330376_.compositeAccess()));
return SimpleReloadInstance.create(
- p_248588_, reloadableserverresources.listeners(), p_249136_, p_249601_, DATA_RELOAD_INITIAL_TASK, LOGGER.isDebugEnabled()
+ p_248588_, listeners, p_249136_, p_249601_, DATA_RELOAD_INITIAL_TASK, LOGGER.isDebugEnabled()
@@ -45,7 +45,7 @@
}
}
@@ -275,6 +_,11 @@
@@ -275,6 +_,13 @@
if (this.queryThreadGs4 != null) {
this.queryThreadGs4.stop();
}
@@ -54,6 +54,8 @@
+ this.dediLanPinger.interrupt();
+ this.dediLanPinger = null;
+ }
+
+ net.minecraftforge.fml.config.ConfigTracker.INSTANCE.forceUnload();
}
@Override
@@ -24,6 +24,35 @@
this.write(chunkpos, completablefuture::join).handle((p_358676_, p_358677_) -> {
if (p_358677_ != null) {
this.level.getServer().reportChunkSaveFailure(p_358677_, this.storageInfo(), chunkpos);
@@ -934,10 +_,27 @@
void forEachSpawnCandidateChunk(Consumer<ChunkHolder> p_364582_) {
LongIterator longiterator = this.distanceManager.getSpawnCandidateChunks();
+ // FORGE: separated forEach logic into its own method
+ this.forEachChunk(longiterator, this::anyPlayerCloseEnoughForSpawningInternal, p_364582_);
+ }
+
+ /** FORGE: grab forced chunks, use forEachChunk to avoid duplicate logic */
+ void forEachForcedChunk(Consumer<ChunkHolder> callback) {
+ LongIterator longiterator = this.distanceManager.forcedTickets.keySet().iterator();
+ this.forEachChunk(longiterator, pos -> true, callback);
+ }
+
+ /** FORGE: check if there are any forced chunks */
+ boolean hasForcedChunks() {
+ return !this.distanceManager.forcedTickets.isEmpty();
+ }
+
+ /** FORGE: commonize logic between forEachSpawnCandidateChunk and forEachForcedChunk */
+ private void forEachChunk(LongIterator longiterator, java.util.function.Predicate<ChunkPos> filter, Consumer<ChunkHolder> p_364582_) {
while (longiterator.hasNext()) {
long i = longiterator.nextLong();
ChunkHolder chunkholder = this.visibleChunkMap.get(i);
- if (chunkholder != null && this.anyPlayerCloseEnoughForSpawningInternal(chunkholder.getPos())) {
+ if (chunkholder != null && filter.test(chunkholder.getPos())) {
p_364582_.accept(chunkholder);
}
}
@@ -1102,7 +_,7 @@
}
@@ -4,7 +4,7 @@
final Executor mainThreadExecutor;
private long ticketTickCounter;
private int simulationDistance = 10;
+ private final Long2ObjectOpenHashMap<SortedArraySet<Ticket<?>>> forcedTickets = new Long2ObjectOpenHashMap<>();
+ final Long2ObjectOpenHashMap<SortedArraySet<Ticket<?>>> forcedTickets = new Long2ObjectOpenHashMap<>();
protected DistanceManager(Executor p_140774_, Executor p_140775_) {
TaskScheduler<Runnable> taskscheduler = TaskScheduler.wrapExecutor("player ticket throttler", p_140775_);
@@ -44,7 +44,7 @@
+ }
+
+ public <T> void addRegionTicket(TicketType<T> p_140841_, ChunkPos p_140842_, int p_140843_, T p_140844_, boolean forceTicks) {
+ Ticket<T> ticket = new Ticket<>(p_140841_, ChunkLevel.byStatus(FullChunkStatus.FULL) - p_140843_, p_140844_, forceTicks);
+ Ticket<T> ticket = new Ticket<>(p_140841_, ChunkLevel.byStatus(FullChunkStatus.FULL) - p_140843_, p_140844_, forceTicks);
long i = p_140842_.toLong();
this.addTicket(i, ticket);
this.tickingTicketsTracker.addTicket(i, ticket);
@@ -11,6 +11,34 @@
ChunkAccess chunkaccess1 = chunkholder.getChunkIfPresent(ChunkStatus.FULL);
if (chunkaccess1 != null) {
this.storeInCache(i, chunkaccess1, ChunkStatus.FULL);
@@ -368,9 +_,25 @@
}
private void collectTickingChunks(List<LevelChunk> p_368970_) {
- this.chunkMap.forEachSpawnCandidateChunk(p_358696_ -> {
+ // FORGE: If there are no forced chunks, just do the vanilla logic
+ if (!this.chunkMap.hasForcedChunks()) {
+ this.chunkMap.forEachSpawnCandidateChunk(this.getChunkCollector(p_368970_, chunk -> this.level.isNaturalSpawningAllowed(chunk.getPos())));
+ return;
+ }
+
+ // FORGE: We have forced chunks? Ok, use a linked hash set to preserve order
+ Set<LevelChunk> set = new java.util.LinkedHashSet<>();
+ this.chunkMap.forEachSpawnCandidateChunk(this.getChunkCollector(set, chunk -> this.level.isNaturalSpawningAllowed(chunk.getPos())));
+ this.chunkMap.forEachForcedChunk(this.getChunkCollector(set, chunk -> true));
+ p_368970_.addAll(set);
+ }
+
+ /** FORGE: Isolate chunk collection callback */
+ private Consumer<ChunkHolder> getChunkCollector(java.util.Collection<LevelChunk> p_368970_, java.util.function.Predicate<LevelChunk> predicate) {
+ return (p_358696_ -> {
LevelChunk levelchunk = p_358696_.getTickingChunk();
- if (levelchunk != null && this.level.isNaturalSpawningAllowed(p_358696_.getPos())) {
+ // FORGE: Use custom predicate instead of only checking isNaturalSpawningAllowed
+ if (levelchunk != null && predicate.test(levelchunk)) {
p_368970_.add(levelchunk);
}
});
@@ -466,11 +_,19 @@
}
@@ -1,6 +1,6 @@
--- a/net/minecraft/server/packs/resources/SimpleJsonResourceReloadListener.java
+++ b/net/minecraft/server/packs/resources/SimpleJsonResourceReloadListener.java
@@ -55,7 +_,13 @@
@@ -55,7 +_,15 @@
ResourceLocation resourcelocation1 = filetoidconverter.fileToId(resourcelocation);
try (Reader reader = entry.getValue().openAsReader()) {
@@ -12,6 +12,8 @@
+ continue;
+ }
+ p_368755_.parse(p_369854_, json).ifSuccess(p_370131_ -> {
+ p_370131_ = net.minecraftforge.common.ForgeHooks.onJsonDataParsed(p_368755_, resourcelocation1, p_370131_);
+ if (p_370131_ == null) return;
if (p_279404_.putIfAbsent(resourcelocation1, (T)p_370131_) != null) {
throw new IllegalStateException("Duplicate data file ignored with ID " + resourcelocation1);
}
@@ -12,7 +12,7 @@
servergamepacketlistenerimpl.send(new ClientboundPlayerAbilitiesPacket(p_11263_.getAbilities()));
servergamepacketlistenerimpl.send(new ClientboundSetHeldSlotPacket(p_11263_.getInventory().selected));
RecipeManager recipemanager = this.server.getRecipeManager();
+ net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.OnDatapackSyncEvent(this, null));
+ net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.OnDatapackSyncEvent(this, p_11263_));
servergamepacketlistenerimpl.send(new ClientboundUpdateRecipesPacket(recipemanager.getSynchronizedItemProperties(), recipemanager.getSynchronizedStonecutterRecipes()));
this.sendPlayerPermissionLevel(p_11263_);
p_11263_.getStats().markAllDirty();
@@ -392,7 +392,7 @@
FluidState fluidstate = this.level().getFluidState(this.blockPosition());
- if ((this.isInWater() || this.isInLava()) && this.isAffectedByFluids() && !this.canStandOnFluid(fluidstate)) {
- this.travelInFluid(p_21280_);
+ if ((this.isInWater() || (this.isInFluidType(fluidstate) && fluidstate.getFluidType() != net.minecraftforge.common.ForgeMod.LAVA_TYPE.get())) && this.isAffectedByFluids() && !this.canStandOnFluid(fluidstate)) {
+ if ((this.isInWater() || this.isInLava() || this.isInFluidType(fluidstate)) && this.isAffectedByFluids() && !this.canStandOnFluid(fluidstate)) {
+ this.travelInFluid(p_21280_, fluidstate);
} else if (this.isFallFlying()) {
this.travelFallFlying();
@@ -406,7 +406,7 @@
float f1 = f * 0.91F;
Vec3 vec3 = this.handleRelativeFrictionAndCalculateMovement(p_362087_, f);
double d0 = vec3.y;
@@ -2214,11 +_,16 @@
@@ -2214,10 +_,18 @@
}
}
@@ -420,11 +420,12 @@
boolean flag = this.getDeltaMovement().y <= 0.0;
double d0 = this.getY();
double d1 = this.getEffectiveGravity();
- if (this.isInWater()) {
+ if (this.isInWater() || (this.isInFluidType(fluidstate) && !this.moveInFluid(fluidstate, p_362114_, d0))) {
+ if (this.isInFluidType(fluidstate) && this.moveInFluid(fluidstate, p_362114_, d0)) {
+ // Modded fluid handled it
+ } else
if (this.isInWater()) {
float f = this.isSprinting() ? 0.9F : this.getWaterSlowDown();
float f1 = 0.02F;
float f2 = (float)this.getAttributeValue(Attributes.WATER_MOVEMENT_EFFICIENCY);
@@ -2235,6 +_,7 @@
f = 0.96F;
}
@@ -469,7 +470,7 @@
if (this.isInLava()) {
d3 = this.getFluidHeight(FluidTags.LAVA);
} else {
@@ -2737,15 +_,19 @@
@@ -2737,15 +_,22 @@
double d4 = this.getFluidJumpThreshold();
if (!flag || this.onGround() && !(d3 > d4)) {
if (!this.isInLava() || this.onGround() && !(d3 > d4)) {
@@ -483,7 +484,10 @@
+ }
} else {
- this.jumpInLiquid(FluidTags.LAVA);
+ var old = this.getDeltaMovement();
+ this.jumpInFluid(net.minecraftforge.common.ForgeMod.LAVA_TYPE.get());
+ if (this instanceof Player)
+ System.out.println(old + " " + this.getDeltaMovement());
}
} else {
- this.jumpInLiquid(FluidTags.WATER);
@@ -518,14 +522,12 @@
this.completeUsingItem();
}
}
@@ -3134,8 +_,12 @@
@@ -3134,8 +_,10 @@
public void startUsingItem(InteractionHand p_21159_) {
ItemStack itemstack = this.getItemInHand(p_21159_);
if (!itemstack.isEmpty() && !this.isUsingItem()) {
+ int duration = net.minecraftforge.event.ForgeEventFactory.onItemUseStart(this, itemstack, itemstack.getUseDuration(this));
+ if (duration <= 0) {
+ return;
+ }
+ if (duration < 0) return;
this.useItem = itemstack;
- this.useItemRemaining = itemstack.getUseDuration(this);
+ this.useItemRemaining = duration;
@@ -0,0 +1,21 @@
--- a/net/minecraft/world/entity/npc/VillagerTrades.java
+++ b/net/minecraft/world/entity/npc/VillagerTrades.java
@@ -1359,6 +_,7 @@
private final int villagerXp;
public EmeraldsForVillagerTypeItem(int p_35669_, int p_35670_, int p_35671_, Map<VillagerType, Item> p_35672_) {
+ if (false) // FORGE: Modders can add custom villager types, so remove this validation
BuiltInRegistries.VILLAGER_TYPE.stream().filter(p_35680_ -> !p_35672_.containsKey(p_35680_)).findAny().ifPresent(p_327046_ -> {
throw new IllegalStateException("Missing trade for villager type: " + BuiltInRegistries.VILLAGER_TYPE.getKey(p_327046_));
});
@@ -1372,7 +_,9 @@
@Override
public MerchantOffer getOffer(Entity p_219685_, RandomSource p_219686_) {
if (p_219685_ instanceof VillagerDataHolder villagerdataholder) {
- ItemCost itemcost = new ItemCost(this.trades.get(villagerdataholder.getVillagerData().getType()), this.cost);
+ Item item = this.trades.get(villagerdataholder.getVillagerData().getType());
+ if (item == null) return null; // FORGE: Account for modded villager types by returning null if there is no trade
+ ItemCost itemcost = new ItemCost(item, this.cost);
return new MerchantOffer(itemcost, new ItemStack(Items.EMERALD), this.maxUses, this.villagerXp, 0.05F);
} else {
return null;
@@ -0,0 +1,12 @@
--- a/net/minecraft/world/entity/npc/VillagerType.java
+++ b/net/minecraft/world/entity/npc/VillagerType.java
@@ -69,4 +_,9 @@
public static VillagerType byBiome(Holder<Biome> p_204074_) {
return p_204074_.unwrapKey().map(BY_BIOME::get).orElse(PLAINS);
}
+
+ /** FORGE: Registers the VillagerType that will spawn in the given biome. This method should be called during FMLCommonSetupEvent using event.enqueueWork() */
+ public static void registerBiomeType(ResourceKey<Biome> biomeKey, VillagerType villagerType) {
+ BY_BIOME.put(biomeKey, villagerType);
+ }
}
@@ -129,7 +129,7 @@
super.blockUsingShield(p_36295_);
ItemStack itemstack = this.getItemBlockingWith();
- if (p_36295_.canDisableShield() && itemstack != null) {
+ if (itemstack != null && itemstack.canDisableShield(this.useItem, this, p_36295_)) {
+ if (itemstack != null && p_36295_.getWeaponItem().canDisableShield(itemstack, this, p_36295_)) {
this.disableShield(itemstack);
}
}
@@ -142,6 +142,19 @@
if (!this.level().isClientSide) {
this.awardStat(Stats.ITEM_USED.get(this.useItem.getItem()));
}
@@ -928,8 +_,11 @@
if (p_36383_ >= 3.0F) {
int i = 1 + Mth.floor(p_36383_);
InteractionHand interactionhand = this.getUsedItemHand();
+ // FORGE: cache this.useItem -- if this.stopUsingItem() is called, it will be set to ItemStack.EMPTY
+ ItemStack currentItem = this.useItem;
this.useItem.hurtAndBreak(i, this, getSlotForHand(interactionhand));
- if (this.useItem.isEmpty()) {
+ // FORGE: use cached item since this.useItem could be ItemStack.EMPTY
+ if (currentItem.isEmpty()) {
if (interactionhand == InteractionHand.MAIN_HAND) {
this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY);
} else {
@@ -946,10 +_,13 @@
@Override
protected void actuallyHurt(ServerLevel p_365751_, DamageSource p_36312_, float p_36313_) {
@@ -18,6 +18,15 @@
f = Math.max(f, fluidstate.getHeight(this.level(), blockpos$mutableblockpos));
}
@@ -522,7 +_,7 @@
voxelshape,
BooleanOp.AND
)) {
- f += blockstate.getBlock().getFriction();
+ f += blockstate.getFriction(this.level(), blockpos$mutableblockpos, this);
k1++;
}
}
@@ -551,7 +_,7 @@
for (int i2 = i1; i2 < j1; i2++) {
blockpos$mutableblockpos.set(k1, l1, i2);
@@ -10,7 +10,7 @@
if (optional.isEmpty()) {
return InteractionResult.PASS;
} else {
@@ -115,5 +_,16 @@
@@ -115,5 +_,21 @@
private Optional<BlockState> getStripped(BlockState p_150691_) {
return Optional.ofNullable(STRIPPABLES.get(p_150691_.getBlock()))
.map(p_359378_ -> p_359378_.defaultBlockState().setValue(RotatedPillarBlock.AXIS, p_150691_.getValue(RotatedPillarBlock.AXIS)));
@@ -25,5 +25,10 @@
+ public static BlockState getAxeStrippingState(BlockState originalState) {
+ Block block = STRIPPABLES.get(originalState.getBlock());
+ return block != null ? block.defaultBlockState().setValue(RotatedPillarBlock.AXIS, originalState.getValue(RotatedPillarBlock.AXIS)) : null;
+ }
+
+ @Override
+ public boolean canDisableShield(ItemStack stack, ItemStack shield, LivingEntity entity, LivingEntity attacker) {
+ return true;
}
}
@@ -28,7 +28,7 @@
}
@Deprecated
@@ -123,17 +_,25 @@
@@ -123,17 +_,35 @@
return this.builtInRegistryHolder;
}
@@ -42,6 +42,16 @@
+ }
+
+ return builtComponents;
+ }
+
+ /**
+ * Forge: Hack fix used in
+ * {@link net.minecraft.client.resources.model.ModelDiscovery#listMandatoryModels ModelBakery.listMandatoryModels},
+ * during mod loading, before the Forge event bus is ready.
+ */
+ @org.jetbrains.annotations.ApiStatus.Internal
+ public DataComponentMap vanillaComponents() {
+ return components;
}
public int getDefaultMaxStackSize() {
@@ -5,10 +5,27 @@
import org.slf4j.Logger;
-public final class ItemStack implements DataComponentHolder {
+public final class ItemStack implements DataComponentHolder, net.minecraftforge.common.extensions.IForgeItemStack {
+public final class ItemStack extends net.minecraftforge.common.capabilities.CapabilityProvider<ItemStack> implements DataComponentHolder, net.minecraftforge.common.extensions.IForgeItemStack {
public static final Codec<ItemStack> CODEC = Codec.lazyInitialized(
() -> RecordCodecBuilder.create(
p_359412_ -> p_359412_.group(
@@ -252,13 +_,16 @@
}
private ItemStack(ItemLike p_331826_, int p_332766_, PatchedDataComponentMap p_333722_) {
+ super(ItemStack.class, true);
this.item = p_331826_.asItem();
this.count = p_332766_;
this.components = p_333722_;
this.getItem().verifyComponentsAfterLoad(this);
+ gatherCapabilities(() -> this.item.getCapabilityProvider(this));
}
private ItemStack(@Nullable Void p_282703_) {
+ super(ItemStack.class, false);
this.item = null;
this.components = new PatchedDataComponentMap(DataComponentMap.EMPTY);
}
@@ -348,13 +_,22 @@
}
@@ -33,6 +50,34 @@
if (player != null && interactionresult instanceof InteractionResult.Success interactionresult$success && interactionresult$success.wasItemInteraction()) {
player.awardStat(Stats.ITEM_USED.get(item));
}
@@ -459,18 +_,26 @@
}
public void hurtAndBreak(int p_220158_, ServerLevel p_342197_, @Nullable ServerPlayer p_220160_, Consumer<Item> p_343361_) {
- int i = this.processDurabilityChange(p_220158_, p_342197_, p_220160_);
+ // FORGE: use context-sensitive sister of processDurabilityChange that calls IForgeItem.damageItem
+ int i = this.processDurabilityChange(p_220158_, p_342197_, p_220160_, true, p_343361_);
if (i != 0) {
this.applyDamage(this.getDamageValue() + i, p_220160_, p_343361_);
}
}
private int processDurabilityChange(int p_362423_, ServerLevel p_364910_, @Nullable ServerPlayer p_365570_) {
+ return this.processDurabilityChange(p_362423_, p_364910_, p_365570_, false, p_359411_ -> { });
+ }
+
+ /** FORGE: context-sensitive sister of processDurabilityChange that calls IForgeItem.damageItem */
+ private int processDurabilityChange(int p_362423_, ServerLevel p_364910_, @Nullable ServerPlayer p_365570_, boolean canBreak, Consumer<Item> onBreak) {
if (!this.isDamageableItem()) {
return 0;
} else if (p_365570_ != null && p_365570_.hasInfiniteMaterials()) {
return 0;
} else {
+ // FORGE: modify the base damage based on the item's impl of IForgeItem.damageItem
+ p_362423_ = this.damageItem(p_362423_, p_364910_, p_365570_, canBreak, onBreak);
return p_362423_ > 0 ? EnchantmentHelper.processDurabilityChange(p_364910_, this, p_362423_) : p_362423_;
}
}
@@ -507,7 +_,13 @@
p_41623_,
serverlevel,
@@ -41,7 +86,7 @@
+ p_341563_ -> {
+ if (p_41624_ instanceof Player player) {
+ net.minecraftforge.event.ForgeEventFactory.onPlayerDestroyItem(player, this, p_335324_);
+ player.stopUsingItem(); // Forge: fix MC-168573
+ if (player.getUseItem() == this) player.stopUsingItem(); // Forge: fix MC-168573
+ }
+ p_41624_.onEquippedItemBroken(p_341563_, p_335324_);
+ }
@@ -5,7 +5,7 @@
List<EnchantmentInstance> list = Lists.newArrayList();
boolean flag = p_44819_.is(Items.BOOK);
- p_342857_.filter(p_341799_ -> p_341799_.value().isPrimaryItem(p_44819_) || flag).forEach(p_341708_ -> {
+ p_342857_.filter(p_341799_ -> p_44819_.canApplyAtEnchantingTable(p_341799_.value()) || flag).forEach(p_341708_ -> {
+ p_342857_.filter(p_341799_ -> p_44819_.canApplyAtEnchantingTable(p_341799_) || flag).forEach(p_341708_ -> {
Enchantment enchantment = p_341708_.value();
for (int i = enchantment.getMaxLevel(); i >= enchantment.getMinLevel(); i--) {
@@ -157,12 +157,12 @@
T t = p_261885_.tryCast(enderdragonpart);
if (t != null && p_261688_.test(t)) {
p_262071_.add(t);
@@ -919,16 +_,15 @@
@@ -919,17 +_,16 @@
public abstract Scoreboard getScoreboard();
public void updateNeighbourForOutputSignal(BlockPos p_46718_, Block p_46719_) {
- for (Direction direction : Direction.Plane.HORIZONTAL) {
+ for (Direction direction : Direction.valuesView()) {
+ for (Direction direction : Direction.getUpdateOrder()) {
BlockPos blockpos = p_46718_.relative(direction);
if (this.hasChunkAt(blockpos)) {
BlockState blockstate = this.getBlockState(blockpos);
@@ -174,10 +174,12 @@
blockpos = blockpos.relative(direction);
blockstate = this.getBlockState(blockpos);
- if (blockstate.is(Blocks.COMPARATOR)) {
- this.neighborChanged(blockstate, blockpos, p_46719_, null, false);
+ if (blockstate.getWeakChanges(this, blockpos)) {
this.neighborChanged(blockstate, blockpos, p_46719_, null, false);
+ blockstate.onNeighborChange(this, blockpos, p_46718_);
}
}
}
@@ -1031,6 +_,20 @@
}
@@ -91,7 +91,7 @@
}
public void setPlacedBy(Level p_49847_, BlockPos p_49848_, BlockState p_49849_, @Nullable LivingEntity p_49850_, ItemStack p_49851_) {
@@ -375,6 +_,7 @@
@@ -375,10 +_,12 @@
p_49822_.setDeltaMovement(p_49822_.getDeltaMovement().multiply(1.0, 0.0, 1.0));
}
@@ -99,6 +99,11 @@
public ItemStack getCloneItemStack(LevelReader p_312699_, BlockPos p_49824_, BlockState p_49825_) {
return new ItemStack(this);
}
+ /** @deprecated Forge use {@link IForgeStateBlock#getFriction(Level, BlockPos, Entity)}*/
public float getFriction() {
return this.friction;
}
@@ -449,7 +_,7 @@
this.item = Item.byBlock(this);
}
@@ -1,14 +1,14 @@
--- a/net/minecraft/world/level/block/Blocks.java
+++ b/net/minecraft/world/level/block/Blocks.java
@@ -2800,7 +_,7 @@
@@ -766,7 +_,7 @@
public static final Block RED_BED = registerBed("red_bed", DyeColor.RED);
public static final Block BLACK_BED = registerBed("black_bed", DyeColor.BLACK);
public static final Block POWERED_RAIL = register(
- "powered_rail", PoweredRailBlock::new, BlockBehaviour.Properties.of().noCollission().strength(0.7F).sound(SoundType.METAL)
+ "powered_rail", prop -> new PoweredRailBlock(prop, true), BlockBehaviour.Properties.of().noCollission().strength(0.7F).sound(SoundType.METAL)
);
public static final Block QUARTZ_STAIRS = registerLegacyStair("quartz_stairs", QUARTZ_BLOCK);
public static final Block ACTIVATOR_RAIL = register(
- "activator_rail", PoweredRailBlock::new, BlockBehaviour.Properties.of().noCollission().strength(0.7F).sound(SoundType.METAL)
+ "activator_rail", prop -> new PoweredRailBlock(prop, false), BlockBehaviour.Properties.of().noCollission().strength(0.7F).sound(SoundType.METAL)
);
public static final Block DROPPER = register(
"dropper",
public static final Block DETECTOR_RAIL = register(
"detector_rail", DetectorRailBlock::new, BlockBehaviour.Properties.of().noCollission().strength(0.7F).sound(SoundType.METAL)
@@ -6552,14 +_,5 @@
private static Block register(String p_50796_, BlockBehaviour.Properties p_362760_) {
@@ -31,12 +31,12 @@
p_368198_.addFreshEntity(primedtnt);
}
+ @Deprecated //Forge: Prefer using IForgeBlock#catchFire
+ @Deprecated //Forge: Prefer using IForgeBlock#onCaughtFire
public static void explode(Level p_57434_, BlockPos p_57435_) {
explode(p_57434_, p_57435_, null);
}
+ @Deprecated //Forge: Prefer using IForgeBlock#catchFire
+ @Deprecated //Forge: Prefer using IForgeBlock#onCaughtFire
private static void explode(Level p_57437_, BlockPos p_57438_, @Nullable LivingEntity p_57439_) {
if (!p_57437_.isClientSide) {
PrimedTnt primedtnt = new PrimedTnt(
@@ -1,13 +1,24 @@
--- a/net/minecraft/world/level/storage/LevelSummary.java
+++ b/net/minecraft/world/level/storage/LevelSummary.java
@@ -277,6 +_,10 @@
}
}
@@ -14,7 +_,7 @@
import net.minecraft.world.level.LevelSettings;
import org.apache.commons.lang3.StringUtils;
+ public boolean isLifecycleExperimental() {
+ return this.settings.getLifecycle().equals(com.mojang.serialization.Lifecycle.experimental());
-public class LevelSummary implements Comparable<LevelSummary> {
+public class LevelSummary implements Comparable<LevelSummary>, net.minecraftforge.common.extensions.IForgeLevelSummary {
public static final Component PLAY_WORLD = Component.translatable("selectWorld.select");
private final LevelSettings settings;
private final LevelVersion levelVersion;
@@ -275,6 +_,12 @@
public boolean canRecreate() {
return false;
}
+ }
+
+ // TODO Forge: Remove in 1.22. It is kept here for binary compatibility, but already exists in IForgeLevelSummary
+ @Deprecated(forRemoval = true, since = "1.21.4")
+ public boolean isLifecycleExperimental() {
+ return net.minecraftforge.common.extensions.IForgeLevelSummary.super.isLifecycleExperimental();
}
public static class SymlinkLevelSummary extends LevelSummary {
private static final Component MORE_INFO_BUTTON = Component.translatable("symlink_warning.more_info");
private static final Component INFO = Component.translatable("symlink_warning.title").withColor(-65536);
@@ -1,16 +0,0 @@
--- a/net/minecraft/world/level/storage/loot/LootDataType.java
+++ b/net/minecraft/world/level/storage/loot/LootDataType.java
@@ -29,9 +_,12 @@
}
private static LootDataType.Validator<LootTable> createLootTableValidator() {
- return (p_327551_, p_327552_, p_327553_) -> p_327553_.validate(
+ return (p_327551_, p_327552_, p_327553_) -> {
+ p_327553_.setLootTableId(p_327552_.location());
+ p_327553_.validate(
p_327551_.setContextKeySet(p_327553_.getParamSet()).enterElement("{" + p_327552_.registry() + "/" + p_327552_.location() + "}", p_327552_)
);
+ };
}
@FunctionalInterface
@@ -60,7 +60,7 @@
return objectarraylist;
}
@@ -210,6 +_,54 @@
@@ -210,6 +_,68 @@
public static LootTable.Builder lootTable() {
return new LootTable.Builder();
@@ -90,6 +90,20 @@
+ return null;
+ }
+
+ public int poolCount() {
+ return this.pools.size();
+ }
+
+ public LootPool getPool(int entry) {
+ return this.pools.get(entry);
+ }
+
+ @org.jetbrains.annotations.Nullable
+ public LootPool removePool(int entry) {
+ checkFrozen();
+ return this.pools.remove(entry);
+ }
+
+ public void addPool(LootPool pool) {
+ checkFrozen();
+ if (pools.stream().anyMatch(e -> e == pool)) {
+10 -10
View File
@@ -24,33 +24,33 @@ dependencyResolutionManagement {
versionCatalogs {
libs {
library('forgespi', 'net.minecraftforge:forgespi:7.1.5') // Needs modlauncher
library('modlauncher', 'net.minecraftforge:modlauncher:10.2.2') // Needs securemodules
library('securemodules', 'net.minecraftforge:securemodules:2.2.20') // Needs unsafe
library('modlauncher', 'net.minecraftforge:modlauncher:10.2.4') // Needs securemodules
library('securemodules', 'net.minecraftforge:securemodules:2.2.21') // Needs unsafe
library('unsafe', 'net.minecraftforge:unsafe:0.9.2')
library('accesstransformers', 'net.minecraftforge:accesstransformers:8.2.0')
library('coremods', 'net.minecraftforge:coremods:5.1.6')
library('accesstransformers', 'net.minecraftforge:accesstransformers:8.2.2')
library('coremods', 'net.minecraftforge:coremods:5.2.6')
library('nashorn', 'org.openjdk.nashorn:nashorn-core:15.4') // Needed by coremods, because the JRE no longer ships JS
library('eventbus', 'net.minecraftforge:eventbus:6.2.8')
library('eventbus', 'net.minecraftforge:eventbus:6.2.27')
library('typetools', 'net.jodah:typetools:0.6.3') // Needed by EventBus because of lambdas
library('mergetool-api', 'net.minecraftforge:mergetool-api:1.0')
library('mixin', 'org.spongepowered:mixin:0.8.7')
library('nulls', 'org.jetbrains:annotations:24.1.0') // Got to have our null annotations!
library('slf4j-api', 'org.slf4j:slf4j-api:2.0.7')
library('maven-artifact', 'org.apache.maven:maven-artifact:3.8.5')
library('maven-artifact', 'org.apache.maven:maven-artifact:3.8.8')
// Google's InMemory File System. Used by ForgeDev Tests for now, but could be useful for a lot of things.
library('jimfs', 'com.google.jimfs:jimfs:1.3.0')
bundle('jimfs', ['guava', 'failureaccess'])
version('bootstrap', '2.1.3')
version('bootstrap', '2.1.8')
library('bootstrap', 'net.minecraftforge', 'bootstrap' ).versionRef('bootstrap') // Needs modlauncher
library('bootstrap-api', 'net.minecraftforge', 'bootstrap-api' ).versionRef('bootstrap')
library('bootstrap-dev', 'net.minecraftforge', 'bootstrap-dev' ).versionRef('bootstrap')
library('bootstrap-shim', 'net.minecraftforge', 'bootstrap-shim').versionRef('bootstrap')
// ASM it's used for so many of our hacks
version('asm', '9.7')
version('asm', '9.7.1')
library('asm', 'org.ow2.asm', 'asm' ).versionRef('asm')
library('asm-tree', 'org.ow2.asm', 'asm-tree' ).versionRef('asm')
library('asm-util', 'org.ow2.asm', 'asm-util' ).versionRef('asm')
@@ -60,14 +60,14 @@ dependencyResolutionManagement {
// Terminal Console Appender.. essentually make pretty colors in the console, but it's been a PITA
library('terminalconsoleappender', 'net.minecrell:terminalconsoleappender:1.2.0')
version('jline', '3.12.1')
version('jline', '3.25.1')
library('jline-reader', 'org.jline', 'jline-reader' ).versionRef('jline')
library('jline-terminal', 'org.jline', 'jline-terminal' ).versionRef('jline')
library('jline-terminal-jna', 'org.jline', 'jline-terminal-jna').versionRef('jline') // Colors and tab completeion
bundle('terminalconsoleappender', ['terminalconsoleappender', 'jline-reader', 'jline-terminal', 'jline-terminal-jna'])
// The core of our configuration system, it has many flaws, but it works for the most part
version('night-config', '3.7.3')
version('night-config', '3.7.4')
library('night-config-toml', 'com.electronwill.night-config', 'toml').versionRef('night-config')
library('night-config-core', 'com.electronwill.night-config', 'core').versionRef('night-config')
bundle('night-config', ['night-config-toml', 'night-config-core'])
@@ -1,37 +0,0 @@
{
"parent": "minecraft:story/root",
"criteria": {
"break_glass_with_fish": {
"conditions": {
"breakingBlock": {
"tag": "forge:glass"
},
"holdingItem": {
"tag": "criterion_test:fish"
}
},
"trigger": "criterion_test:criterion"
}
},
"display": {
"announce_to_chat": true,
"description": {
"text": "Fish wins!"
},
"frame": "task",
"hidden": false,
"icon": {
"item": "minecraft:cod"
},
"show_toast": true,
"title": {
"text": "Fish vs Glass"
}
},
"requirements": [
[
"break_glass_with_fish"
]
],
"sends_telemetry_event": false
}
@@ -1,10 +0,0 @@
{
"values": [
"minecraft:cod",
"minecraft:salmon",
"minecraft:tropical_fish",
"minecraft:pufferfish",
"minecraft:cooked_cod",
"minecraft:cooked_salmon"
]
}
@@ -0,0 +1,13 @@
{
"values": [
"minecraft:stripped_acacia_log",
"minecraft:stripped_bamboo_block",
"minecraft:stripped_birch_log",
"minecraft:stripped_cherry_log",
"minecraft:stripped_dark_oak_log",
"minecraft:stripped_jungle_log",
"minecraft:stripped_mangrove_log",
"minecraft:stripped_oak_log",
"minecraft:stripped_spruce_log"
]
}
@@ -0,0 +1,12 @@
{
"values": [
"minecraft:stripped_acacia_wood",
"minecraft:stripped_birch_wood",
"minecraft:stripped_cherry_wood",
"minecraft:stripped_dark_oak_wood",
"minecraft:stripped_jungle_wood",
"minecraft:stripped_mangrove_wood",
"minecraft:stripped_oak_wood",
"minecraft:stripped_spruce_wood"
]
}
@@ -0,0 +1,13 @@
{
"values": [
"minecraft:stripped_acacia_log",
"minecraft:stripped_bamboo_block",
"minecraft:stripped_birch_log",
"minecraft:stripped_cherry_log",
"minecraft:stripped_dark_oak_log",
"minecraft:stripped_jungle_log",
"minecraft:stripped_mangrove_log",
"minecraft:stripped_oak_log",
"minecraft:stripped_spruce_log"
]
}
@@ -0,0 +1,12 @@
{
"values": [
"minecraft:stripped_acacia_wood",
"minecraft:stripped_birch_wood",
"minecraft:stripped_cherry_wood",
"minecraft:stripped_dark_oak_wood",
"minecraft:stripped_jungle_wood",
"minecraft:stripped_mangrove_wood",
"minecraft:stripped_oak_wood",
"minecraft:stripped_spruce_wood"
]
}
@@ -346,7 +346,10 @@ public class ForgeHooksClient {
return LazyInit.INSTANCE;
}
// NO-OP method, kept for bin-compat. Used in TitleScreen.java
@Deprecated(forRemoval = true, since = "1.21.4")
public static void renderMainMenu(TitleScreen gui, GuiGraphics graphics, Font font, int width, int height, int alpha) {
/*
VersionChecker.Status status = getForgeVersionStatus();
if (status == VersionChecker.Status.BETA || status == VersionChecker.Status.BETA_OUTDATED) {
@@ -356,6 +359,7 @@ public class ForgeHooksClient {
line = Component.translatable("forge.update.beta.2");
graphics.drawCenteredString(font, line, width / 2, 4 + (font.lineHeight + 1), 0xFFFFFF | alpha);
}
*/
}
public static String forgeStatusLine;
@@ -447,7 +451,7 @@ public class ForgeHooksClient {
// TODO Do we need this?
public static void fillNormal(int[] faceData, Direction facing, boolean calculateNormals) {
Vector3f v2;
if (calculateNormals || ForgeConfig.CLIENT.calculateAllNormals()) {
if (calculateNormals) {
Vector3f v1 = getVertexPos(faceData, 3);
Vector3f t1 = getVertexPos(faceData, 1);
v2 = getVertexPos(faceData, 2);
@@ -9,8 +9,6 @@ import com.mojang.blaze3d.platform.InputConstants;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.screens.Screen;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
@@ -37,7 +35,6 @@ import java.util.function.Consumer;
* @see MouseInput
* @see KeyInput
*/
@OnlyIn(Dist.CLIENT)
public abstract class ScreenEvent extends Event {
private final Screen screen;
@@ -13,8 +13,6 @@ import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.jetbrains.annotations.ApiStatus;
import java.util.Set;
/**
* Fired after a texture atlas is stitched together.
*
@@ -12,14 +12,11 @@ import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.fml.loading.FMLConfig;
import net.minecraftforge.versions.forge.ForgeVersion;
import net.minecraftforge.fml.VersionChecker;
import net.minecraftforge.client.loading.ClientModLoader;
import net.minecraftforge.api.distmarker.Dist;
@OnlyIn(Dist.CLIENT)
public class TitleScreenModUpdateIndicator extends Screen {
private static final ResourceLocation VERSION_CHECK_ICONS = ResourceLocation.fromNamespaceAndPath(ForgeVersion.MOD_ID, "textures/gui/version_check_icons.png");
@@ -24,15 +24,12 @@ import org.apache.logging.log4j.Logger;
import net.minecraft.client.Minecraft;
import net.minecraft.server.packs.PackType;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.ForgeConfig;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.client.gui.LoadingErrorScreen;
import net.minecraftforge.resource.ResourcePackLoader;
import net.minecraftforge.server.LanguageHook;
@OnlyIn(Dist.CLIENT)
public class ClientModLoader {
private static final Logger LOGGER = LogManager.getLogger();
private static boolean loading;
@@ -208,6 +208,7 @@ public class CompositeModel implements IUnbakedGeometry<CompositeModel> {
private final ItemTransforms transforms;
private TextureAtlasSprite particle;
private RenderTypeGroup lastRenderTypes = RenderTypeGroup.EMPTY;
private RenderTypeGroup lastRenderTypesFast = RenderTypeGroup.EMPTY;
private Builder(boolean isAmbientOcclusion, boolean isGui3d, boolean isSideLit, TextureAtlasSprite particle, ItemTransforms transforms) {
this.isAmbientOcclusion = isAmbientOcclusion;
@@ -218,23 +219,32 @@ public class CompositeModel implements IUnbakedGeometry<CompositeModel> {
}
public void addLayer(BakedModel model) {
flushQuads(null);
flushQuads(RenderTypeGroup.EMPTY, RenderTypeGroup.EMPTY);
children.add(model);
}
private void addLayer(RenderTypeGroup renderTypes, List<BakedQuad> quads) {
var modelBuilder = IModelBuilder.of(isAmbientOcclusion, isSideLit, isGui3d, transforms, particle, renderTypes);
this.addLayer(renderTypes, RenderTypeGroup.EMPTY, quads);
}
private void addLayer(RenderTypeGroup renderTypes, RenderTypeGroup renderTypesFast, List<BakedQuad> quads) {
var modelBuilder = IModelBuilder.of(isAmbientOcclusion, isSideLit, isGui3d, transforms, particle, renderTypes, renderTypesFast);
quads.forEach(modelBuilder::addUnculledFace);
children.add(modelBuilder.build());
}
private void flushQuads(RenderTypeGroup renderTypes) {
this.flushQuads(renderTypes, RenderTypeGroup.EMPTY);
}
private void flushQuads(RenderTypeGroup renderTypes, RenderTypeGroup renderTypesFast) {
if (!Objects.equals(renderTypes, lastRenderTypes)) {
if (!quads.isEmpty()) {
addLayer(lastRenderTypes, quads);
addLayer(lastRenderTypes, lastRenderTypesFast, quads);
quads.clear();
}
lastRenderTypes = renderTypes;
lastRenderTypesFast = renderTypesFast;
}
}
@@ -244,20 +254,28 @@ public class CompositeModel implements IUnbakedGeometry<CompositeModel> {
}
public Builder addQuads(RenderTypeGroup renderTypes, BakedQuad... quadsToAdd) {
flushQuads(renderTypes);
return this.addQuads(renderTypes, RenderTypeGroup.EMPTY, quadsToAdd);
}
public Builder addQuads(RenderTypeGroup renderTypes, RenderTypeGroup renderTypesFast, BakedQuad... quadsToAdd) {
flushQuads(renderTypes, renderTypesFast);
Collections.addAll(quads, quadsToAdd);
return this;
}
public Builder addQuads(RenderTypeGroup renderTypes, Collection<BakedQuad> quadsToAdd) {
flushQuads(renderTypes);
return this.addQuads(renderTypes, RenderTypeGroup.EMPTY, quadsToAdd);
}
public Builder addQuads(RenderTypeGroup renderTypes, RenderTypeGroup renderTypesFast, Collection<BakedQuad> quadsToAdd) {
flushQuads(renderTypes, renderTypesFast);
quads.addAll(quadsToAdd);
return this;
}
public BakedModel build() {
if (!quads.isEmpty()) {
addLayer(lastRenderTypes, quads);
addLayer(lastRenderTypes, lastRenderTypesFast, quads);
}
var childrenBuilder = ImmutableMap.<String, BakedModel>builder();
var itemPassesBuilder = ImmutableList.<BakedModel>builder();
@@ -50,7 +50,7 @@ public class EmptyModel extends SimpleUnbakedGeometry<EmptyModel> {
private static final Material MISSING_TEXTURE = new Material(InventoryMenu.BLOCK_ATLAS, MissingTextureAtlasSprite.getLocation());
public Baked() {
super(List.of(), Map.of(), false, false, false, UnitTextureAtlasSprite.INSTANCE, ItemTransforms.NO_TRANSFORMS, RenderTypeGroup.EMPTY);
super(List.of(), Map.of(), false, false, false, UnitTextureAtlasSprite.INSTANCE, ItemTransforms.NO_TRANSFORMS, RenderTypeGroup.EMPTY, RenderTypeGroup.EMPTY);
}
@Override
@@ -73,6 +73,12 @@ public class ExtendedBlockModelDeserializer extends BlockModel.Deserializer
model.customData.setRenderTypeHint(ResourceLocation.parse(renderTypeHintName));
}
if (jsonobject.has("render_type_fast"))
{
var renderTypeHintName = GsonHelper.getAsString(jsonobject, "render_type_fast");
model.customData.setRenderTypeFastHint(ResourceLocation.parse(renderTypeHintName));
}
if (jsonobject.has("visibility"))
{
JsonObject visibility = GsonHelper.getAsJsonObject(jsonobject, "visibility");
@@ -32,6 +32,18 @@ public interface IModelBuilder<T extends IModelBuilder<T>> {
return new Simple(hasAmbientOcclusion, usesBlockLight, isGui3d, transforms, particle, renderTypes);
}
static IModelBuilder<?> of(
boolean hasAmbientOcclusion,
boolean usesBlockLight,
boolean isGui3d,
ItemTransforms transforms,
TextureAtlasSprite particle,
RenderTypeGroup renderTypes,
RenderTypeGroup renderTypesFast
){
return new Simple(hasAmbientOcclusion, usesBlockLight, isGui3d, transforms, particle, renderTypes, renderTypesFast);
}
/**
* Creates a new model builder that collects quads to the provided list, returning
* {@linkplain EmptyModel#BAKED an empty model} if you call {@link #build()}.
@@ -49,12 +61,20 @@ public interface IModelBuilder<T extends IModelBuilder<T>> {
class Simple implements IModelBuilder<Simple> {
private final SimpleBakedModel.Builder builder;
private final RenderTypeGroup renderTypes;
private final RenderTypeGroup renderTypesFast;
private Simple(boolean hasAmbientOcclusion, boolean usesBlockLight, boolean isGui3d,
ItemTransforms transforms, TextureAtlasSprite particle, RenderTypeGroup renderTypes
) {
this(hasAmbientOcclusion, usesBlockLight, isGui3d, transforms, particle, renderTypes, RenderTypeGroup.EMPTY);
}
private Simple(boolean hasAmbientOcclusion, boolean usesBlockLight, boolean isGui3d,
ItemTransforms transforms, TextureAtlasSprite particle, RenderTypeGroup renderTypes, RenderTypeGroup renderTypesFast
) {
this.builder = new SimpleBakedModel.Builder(hasAmbientOcclusion, usesBlockLight, isGui3d, transforms).particle(particle);
this.renderTypes = renderTypes;
this.renderTypesFast = renderTypesFast;
}
@Override
@@ -39,10 +39,16 @@ public class ItemLayerModel implements IUnbakedGeometry<ItemLayerModel> {
@Nullable
private ImmutableList<Material> textures;
private final Int2ObjectMap<ResourceLocation> renderTypeNames;
private final Int2ObjectMap<ResourceLocation> renderTypeFastNames;
private ItemLayerModel(@Nullable ImmutableList<Material> textures, Int2ObjectMap<ResourceLocation> renderTypeNames) {
this(textures, renderTypeNames, new Int2ObjectOpenHashMap<>());
}
private ItemLayerModel(@Nullable ImmutableList<Material> textures, Int2ObjectMap<ResourceLocation> renderTypeNames, Int2ObjectMap<ResourceLocation> renderTypeFastNames) {
this.textures = textures;
this.renderTypeNames = renderTypeNames;
this.renderTypeFastNames = renderTypeFastNames;
}
@Override
@@ -69,7 +75,9 @@ public class ItemLayerModel implements IUnbakedGeometry<ItemLayerModel> {
var quads = UnbakedGeometryHelper.bakeElements(unbaked, $ -> sprite, modelState);
var renderTypeName = renderTypeNames.get(i);
var renderTypes = renderTypeName != null ? context.getRenderType(renderTypeName) : null;
builder.addQuads(renderTypes != null ? renderTypes : normalRenderTypes, quads);
var renderTypeFastName = renderTypeFastNames.get(i);
var renderTypesFast = renderTypeFastName != null ? context.getRenderType(renderTypeFastName) : null;
builder.addQuads(renderTypes != null ? renderTypes : normalRenderTypes, renderTypesFast != null ? renderTypesFast : RenderTypeGroup.EMPTY, quads);
}
return builder.build();
@@ -80,20 +88,27 @@ public class ItemLayerModel implements IUnbakedGeometry<ItemLayerModel> {
@Override
public ItemLayerModel read(JsonObject jsonObject, JsonDeserializationContext deserializationContext) {
var renderTypeNames = readRenderTypeNames(jsonObject, "render_types");
var renderTypeFastNames = readRenderTypeNames(jsonObject, "render_types_fast");
return new ItemLayerModel(null, renderTypeNames, renderTypeFastNames);
}
private static Int2ObjectMap<ResourceLocation> readRenderTypeNames(JsonObject jsonObject, String key) {
var renderTypeNames = new Int2ObjectOpenHashMap<ResourceLocation>();
if (jsonObject.has("render_types")) {
var renderTypes = jsonObject.getAsJsonObject("render_types");
if (jsonObject.has(key)) {
var renderTypes = jsonObject.getAsJsonObject(key);
for (var entry : renderTypes.entrySet()) {
var renderType = ResourceLocation.parse(entry.getKey());
for (var layer : entry.getValue().getAsJsonArray())
if (renderTypeNames.put(layer.getAsInt(), renderType) != null)
throw new JsonParseException("Registered duplicate render type for layer " + layer);
throw new JsonParseException("Registered duplicate " + key + " for layer " + layer);
}
}
return new ItemLayerModel(null, renderTypeNames);
return renderTypeNames;
}
@Deprecated(forRemoval = true, since = "1.21.4")
protected void readLayerData(JsonObject jsonObject, String name, Int2ObjectOpenHashMap<ResourceLocation> renderTypeNames, Int2ObjectMap<ForgeFaceData> layerData, boolean logWarning) {
if (!jsonObject.has(name))
return;
@@ -250,6 +250,42 @@ public abstract class BlockStateProvider implements DataProvider {
models().cubeColumnHorizontal(name(block) + "_horizontal", side, end).renderType(renderType));
}
public void axisBlockWithRenderTypeAndFast(RotatedPillarBlock block, String renderType, String renderTypeFast) {
axisBlockWithRenderTypeAndFast(block, blockTexture(block), renderType, renderTypeFast);
}
public void logBlockWithRenderTypeAndFast(RotatedPillarBlock block, String renderType, String renderTypeFast) {
axisBlockWithRenderTypeAndFast(block, blockTexture(block), extend(blockTexture(block), "_top"), renderType, renderTypeFast);
}
public void axisBlockWithRenderTypeAndFast(RotatedPillarBlock block, ResourceLocation baseName, String renderType, String renderTypeFast) {
axisBlockWithRenderTypeAndFast(block, extend(baseName, "_side"), extend(baseName, "_end"), renderType, renderTypeFast);
}
public void axisBlockWithRenderTypeAndFast(RotatedPillarBlock block, ResourceLocation side, ResourceLocation end, String renderType, String renderTypeFast) {
axisBlock(block,
models().cubeColumn(name(block), side, end).renderType(renderType, renderTypeFast),
models().cubeColumnHorizontal(name(block) + "_horizontal", side, end).renderType(renderType, renderTypeFast));
}
public void axisBlockWithRenderTypeAndFast(RotatedPillarBlock block, ResourceLocation renderType, ResourceLocation renderTypeFast) {
axisBlockWithRenderTypeAndFast(block, blockTexture(block), renderType, renderTypeFast);
}
public void logBlockWithRenderType(RotatedPillarBlock block, ResourceLocation renderType, ResourceLocation renderTypeFast) {
axisBlockWithRenderTypeAndFast(block, blockTexture(block), extend(blockTexture(block), "_top"), renderType, renderTypeFast);
}
public void axisBlockWithRenderTypeAndFast(RotatedPillarBlock block, ResourceLocation baseName, ResourceLocation renderType, ResourceLocation renderTypeFast) {
axisBlockWithRenderTypeAndFast(block, extend(baseName, "_side"), extend(baseName, "_end"), renderType, renderTypeFast);
}
public void axisBlockWithRenderTypeAndFast(RotatedPillarBlock block, ResourceLocation side, ResourceLocation end, ResourceLocation renderType, ResourceLocation renderTypeFast) {
axisBlock(block,
models().cubeColumn(name(block), side, end).renderType(renderType, renderTypeFast),
models().cubeColumnHorizontal(name(block) + "_horizontal", side, end).renderType(renderType, renderTypeFast));
}
public void axisBlock(RotatedPillarBlock block, ModelFile vertical, ModelFile horizontal) {
getVariantBuilder(block)
.partialState().with(RotatedPillarBlock.AXIS, Axis.Y)
@@ -381,6 +417,38 @@ public abstract class BlockStateProvider implements DataProvider {
stairsBlockInternalWithRenderType(block, name + "_stairs", side, bottom, top, renderType);
}
public void stairsBlockWithRenderTypeAndFast(StairBlock block, ResourceLocation texture, String renderType, String renderTypeFast) {
stairsBlockWithRenderTypeAndFast(block, texture, texture, texture, renderType, renderTypeFast);
}
public void stairsBlockWithRenderTypeAndFast(StairBlock block, String name, ResourceLocation texture, String renderType, String renderTypeFast) {
stairsBlockWithRenderTypeAndFast(block, name, texture, texture, texture, renderType, renderTypeFast);
}
public void stairsBlockWithRenderTypeAndFast(StairBlock block, ResourceLocation side, ResourceLocation bottom, ResourceLocation top, String renderType, String renderTypeFast) {
stairsBlockInternalWithRenderTypeAndFast(block, key(block).toString(), side, bottom, top, ResourceLocation.tryParse(renderType), ResourceLocation.tryParse(renderTypeFast));
}
public void stairsBlockWithRenderTypeAndFast(StairBlock block, String name, ResourceLocation side, ResourceLocation bottom, ResourceLocation top, String renderType, String renderTypeFast) {
stairsBlockInternalWithRenderTypeAndFast(block, name + "_stairs", side, bottom, top, ResourceLocation.tryParse(renderType), ResourceLocation.tryParse(renderTypeFast));
}
public void stairsBlockWithRenderTypeAndFast(StairBlock block, ResourceLocation texture, ResourceLocation renderType, ResourceLocation renderTypeFast) {
stairsBlockWithRenderTypeAndFast(block, texture, texture, texture, renderType, renderTypeFast);
}
public void stairsBlockWithRenderTypeAndFast(StairBlock block, String name, ResourceLocation texture, ResourceLocation renderType, ResourceLocation renderTypeFast) {
stairsBlockWithRenderTypeAndFast(block, name, texture, texture, texture, renderType, renderTypeFast);
}
public void stairsBlockWithRenderTypeAndFast(StairBlock block, ResourceLocation side, ResourceLocation bottom, ResourceLocation top, ResourceLocation renderType, ResourceLocation renderTypeFast) {
stairsBlockInternalWithRenderTypeAndFast(block, key(block).toString(), side, bottom, top, renderType, renderTypeFast);
}
public void stairsBlockWithRenderTypeAndFast(StairBlock block, String name, ResourceLocation side, ResourceLocation bottom, ResourceLocation top, ResourceLocation renderType, ResourceLocation renderTypeFast) {
stairsBlockInternalWithRenderTypeAndFast(block, name + "_stairs", side, bottom, top, renderType, renderTypeFast);
}
private void stairsBlockInternal(StairBlock block, String baseName, ResourceLocation side, ResourceLocation bottom, ResourceLocation top) {
ModelFile stairs = models().stairs(baseName, side, bottom, top);
ModelFile stairsInner = models().stairsInner(baseName + "_inner", side, bottom, top);
@@ -395,6 +463,13 @@ public abstract class BlockStateProvider implements DataProvider {
stairsBlock(block, stairs, stairsInner, stairsOuter);
}
private void stairsBlockInternalWithRenderTypeAndFast(StairBlock block, String baseName, ResourceLocation side, ResourceLocation bottom, ResourceLocation top, ResourceLocation renderType, ResourceLocation renderTypeFast) {
ModelFile stairs = models().stairs(baseName, side, bottom, top).renderType(renderType, renderTypeFast);
ModelFile stairsInner = models().stairsInner(baseName + "_inner", side, bottom, top).renderType(renderType, renderTypeFast);
ModelFile stairsOuter = models().stairsOuter(baseName + "_outer", side, bottom, top).renderType(renderType, renderTypeFast);
stairsBlock(block, stairs, stairsInner, stairsOuter);
}
public void stairsBlock(StairBlock block, ModelFile stairs, ModelFile stairsInner, ModelFile stairsOuter) {
getVariantBuilder(block)
.forAllStatesExcept(state -> {
@@ -532,6 +607,32 @@ public abstract class BlockStateProvider implements DataProvider {
models().fenceSide(name + "_fence_side", texture).renderType(renderType));
}
public void fenceBlockWithRenderTypeAndFast(FenceBlock block, ResourceLocation texture, String renderType, String renderTypeFast) {
String baseName = key(block).toString();
fourWayBlock(block,
models().fencePost(baseName + "_post", texture).renderType(renderType, renderTypeFast),
models().fenceSide(baseName + "_side", texture).renderType(renderType, renderTypeFast));
}
public void fenceBlockWithRenderTypeAndFast(FenceBlock block, String name, ResourceLocation texture, String renderType, String renderTypeFast) {
fourWayBlock(block,
models().fencePost(name + "_fence_post", texture).renderType(renderType, renderTypeFast),
models().fenceSide(name + "_fence_side", texture).renderType(renderType, renderTypeFast));
}
public void fenceBlockWithRenderTypeAndFast(FenceBlock block, ResourceLocation texture, ResourceLocation renderType, ResourceLocation renderTypeFast) {
String baseName = key(block).toString();
fourWayBlock(block,
models().fencePost(baseName + "_post", texture).renderType(renderType, renderTypeFast),
models().fenceSide(baseName + "_side", texture).renderType(renderType, renderTypeFast));
}
public void fenceBlockWithRenderTypeAndFast(FenceBlock block, String name, ResourceLocation texture, ResourceLocation renderType, ResourceLocation renderTypeFast) {
fourWayBlock(block,
models().fencePost(name + "_fence_post", texture).renderType(renderType, renderTypeFast),
models().fenceSide(name + "_fence_side", texture).renderType(renderType, renderTypeFast));
}
public void fenceGateBlock(FenceGateBlock block, ResourceLocation texture) {
fenceGateBlockInternal(block, key(block).toString(), texture);
}
@@ -556,6 +657,22 @@ public abstract class BlockStateProvider implements DataProvider {
fenceGateBlockInternalWithRenderType(block, name + "_fence_gate", texture, renderType);
}
public void fenceGateBlockWithRenderTypeAndFast(FenceGateBlock block, ResourceLocation texture, String renderType, String renderTypeFast) {
fenceGateBlockInternalWithRenderTypeAndFast(block, key(block).toString(), texture, ResourceLocation.tryParse(renderType), ResourceLocation.tryParse(renderTypeFast));
}
public void fenceGateBlockWithRenderTypeAndFast(FenceGateBlock block, String name, ResourceLocation texture, String renderType, String renderTypeFast) {
fenceGateBlockInternalWithRenderTypeAndFast(block, name + "_fence_gate", texture, ResourceLocation.tryParse(renderType), ResourceLocation.tryParse(renderTypeFast));
}
public void fenceGateBlockWithRenderTypeAndFast(FenceGateBlock block, ResourceLocation texture, ResourceLocation renderType, ResourceLocation renderTypeFast) {
fenceGateBlockInternalWithRenderTypeAndFast(block, key(block).toString(), texture, renderType, renderTypeFast);
}
public void fenceGateBlockWithRenderTypeAndFast(FenceGateBlock block, String name, ResourceLocation texture, ResourceLocation renderType, ResourceLocation renderTypeFast) {
fenceGateBlockInternalWithRenderTypeAndFast(block, name + "_fence_gate", texture, renderType, renderTypeFast);
}
private void fenceGateBlockInternal(FenceGateBlock block, String baseName, ResourceLocation texture) {
ModelFile gate = models().fenceGate(baseName, texture);
ModelFile gateOpen = models().fenceGateOpen(baseName + "_open", texture);
@@ -572,6 +689,14 @@ public abstract class BlockStateProvider implements DataProvider {
fenceGateBlock(block, gate, gateOpen, gateWall, gateWallOpen);
}
private void fenceGateBlockInternalWithRenderTypeAndFast(FenceGateBlock block, String baseName, ResourceLocation texture, ResourceLocation renderType, ResourceLocation renderTypeFast) {
ModelFile gate = models().fenceGate(baseName, texture).renderType(renderType, renderTypeFast);
ModelFile gateOpen = models().fenceGateOpen(baseName + "_open", texture).renderType(renderType, renderTypeFast);
ModelFile gateWall = models().fenceGateWall(baseName + "_wall", texture).renderType(renderType, renderTypeFast);
ModelFile gateWallOpen = models().fenceGateWallOpen(baseName + "_wall_open", texture).renderType(renderType, renderTypeFast);
fenceGateBlock(block, gate, gateOpen, gateWall, gateWallOpen);
}
public void fenceGateBlock(FenceGateBlock block, ModelFile gate, ModelFile gateOpen, ModelFile gateWall, ModelFile gateWallOpen) {
getVariantBuilder(block).forAllStatesExcept(state -> {
ModelFile model = gate;
@@ -613,6 +738,22 @@ public abstract class BlockStateProvider implements DataProvider {
wallBlockInternalWithRenderType(block, name + "_wall", texture, renderType);
}
public void wallBlockWithRenderTypeAndFast(WallBlock block, ResourceLocation texture, String renderType, String renderTypeFast) {
wallBlockInternalWithRenderTypeAndFast(block, key(block).toString(), texture, ResourceLocation.tryParse(renderType), ResourceLocation.tryParse(renderTypeFast));
}
public void wallBlockWithRenderTypeAndFast(WallBlock block, String name, ResourceLocation texture, String renderType, String renderTypeFast) {
wallBlockInternalWithRenderTypeAndFast(block, name + "_wall", texture, ResourceLocation.tryParse(renderType), ResourceLocation.tryParse(renderTypeFast));
}
public void wallBlockWithRenderTypeAndFast(WallBlock block, ResourceLocation texture, ResourceLocation renderType, ResourceLocation renderTypeFast) {
wallBlockInternalWithRenderTypeAndFast(block, key(block).toString(), texture, renderType, renderTypeFast);
}
public void wallBlockWithRenderTypeAndFast(WallBlock block, String name, ResourceLocation texture, ResourceLocation renderType, ResourceLocation renderTypeFast) {
wallBlockInternalWithRenderTypeAndFast(block, name + "_wall", texture, renderType, renderTypeFast);
}
private void wallBlockInternal(WallBlock block, String baseName, ResourceLocation texture) {
wallBlock(block, models().wallPost(baseName + "_post", texture),
models().wallSide(baseName + "_side", texture),
@@ -625,6 +766,12 @@ public abstract class BlockStateProvider implements DataProvider {
models().wallSideTall(baseName + "_side_tall", texture).renderType(renderType));
}
private void wallBlockInternalWithRenderTypeAndFast(WallBlock block, String baseName, ResourceLocation texture, ResourceLocation renderType, ResourceLocation renderTypeFast) {
wallBlock(block, models().wallPost(baseName + "_post", texture).renderType(renderType, renderTypeFast),
models().wallSide(baseName + "_side", texture).renderType(renderType, renderTypeFast),
models().wallSideTall(baseName + "_side_tall", texture).renderType(renderType, renderTypeFast));
}
public static final ImmutableMap<Direction, Property<WallSide>> WALL_PROPS = ImmutableMap.<Direction, Property<WallSide>>builder()
.put(Direction.EAST, BlockStateProperties.EAST_WALL)
.put(Direction.NORTH, BlockStateProperties.NORTH_WALL)
@@ -677,6 +824,22 @@ public abstract class BlockStateProvider implements DataProvider {
paneBlockInternalWithRenderType(block, name + "_pane", pane, edge, renderType);
}
public void paneBlockWithRenderTypeAndFast(IronBarsBlock block, ResourceLocation pane, ResourceLocation edge, String renderType, String renderTypeFast) {
paneBlockInternalWithRenderTypeAndFast(block, key(block).toString(), pane, edge, ResourceLocation.tryParse(renderType), ResourceLocation.tryParse(renderTypeFast));
}
public void paneBlockWithRenderTypeAndFast(IronBarsBlock block, String name, ResourceLocation pane, ResourceLocation edge, String renderType, String renderTypeFast) {
paneBlockInternalWithRenderTypeAndFast(block, name + "_pane", pane, edge, ResourceLocation.tryParse(renderType), ResourceLocation.tryParse(renderTypeFast));
}
public void paneBlockWithRenderTypeAndFast(IronBarsBlock block, ResourceLocation pane, ResourceLocation edge, ResourceLocation renderType, ResourceLocation renderTypeFast) {
paneBlockInternalWithRenderTypeAndFast(block, key(block).toString(), pane, edge, renderType, renderTypeFast);
}
public void paneBlockWithRenderTypeAndFast(IronBarsBlock block, String name, ResourceLocation pane, ResourceLocation edge, ResourceLocation renderType, ResourceLocation renderTypeFast) {
paneBlockInternalWithRenderTypeAndFast(block, name + "_pane", pane, edge, renderType, renderTypeFast);
}
private void paneBlockInternal(IronBarsBlock block, String baseName, ResourceLocation pane, ResourceLocation edge) {
ModelFile post = models().panePost(baseName + "_post", pane, edge);
ModelFile side = models().paneSide(baseName + "_side", pane, edge);
@@ -695,6 +858,15 @@ public abstract class BlockStateProvider implements DataProvider {
paneBlock(block, post, side, sideAlt, noSide, noSideAlt);
}
private void paneBlockInternalWithRenderTypeAndFast(IronBarsBlock block, String baseName, ResourceLocation pane, ResourceLocation edge, ResourceLocation renderType, ResourceLocation renderTypeFast) {
ModelFile post = models().panePost(baseName + "_post", pane, edge).renderType(renderType, renderTypeFast);
ModelFile side = models().paneSide(baseName + "_side", pane, edge).renderType(renderType, renderTypeFast);
ModelFile sideAlt = models().paneSideAlt(baseName + "_side_alt", pane, edge).renderType(renderType, renderTypeFast);
ModelFile noSide = models().paneNoSide(baseName + "_noside", pane).renderType(renderType, renderTypeFast);
ModelFile noSideAlt = models().paneNoSideAlt(baseName + "_noside_alt", pane).renderType(renderType, renderTypeFast);
paneBlock(block, post, side, sideAlt, noSide, noSideAlt);
}
public void paneBlock(IronBarsBlock block, ModelFile post, ModelFile side, ModelFile sideAlt, ModelFile noSide, ModelFile noSideAlt) {
MultiPartBlockStateBuilder builder = getMultipartBuilder(block)
.part().modelFile(post).addModel().end();
@@ -734,6 +906,22 @@ public abstract class BlockStateProvider implements DataProvider {
doorBlockInternalWithRenderType(block, name + "_door", bottom, top, renderType);
}
public void doorBlockWithRenderTypeAndFast(DoorBlock block, ResourceLocation bottom, ResourceLocation top, String renderType, String renderTypeFast) {
doorBlockInternalWithRenderTypeAndFast(block, key(block).toString(), bottom, top, ResourceLocation.tryParse(renderType), ResourceLocation.tryParse(renderTypeFast));
}
public void doorBlockWithRenderTypeAndFast(DoorBlock block, String name, ResourceLocation bottom, ResourceLocation top, String renderType, String renderTypeFast) {
doorBlockInternalWithRenderTypeAndFast(block, name + "_door", bottom, top, ResourceLocation.tryParse(renderType), ResourceLocation.tryParse(renderTypeFast));
}
public void doorBlockWithRenderTypeAndFast(DoorBlock block, ResourceLocation bottom, ResourceLocation top, ResourceLocation renderType, ResourceLocation renderTypeFast) {
doorBlockInternalWithRenderTypeAndFast(block, key(block).toString(), bottom, top, renderType, renderTypeFast);
}
public void doorBlockWithRenderTypeAndFast(DoorBlock block, String name, ResourceLocation bottom, ResourceLocation top, ResourceLocation renderType, ResourceLocation renderTypeFast) {
doorBlockInternalWithRenderTypeAndFast(block, name + "_door", bottom, top, renderType, renderTypeFast);
}
private void doorBlockInternal(DoorBlock block, String baseName, ResourceLocation bottom, ResourceLocation top) {
ModelFile bottomLeft = models().doorBottomLeft(baseName + "_bottom_left", bottom, top);
ModelFile bottomLeftOpen = models().doorBottomLeftOpen(baseName + "_bottom_left_open", bottom, top);
@@ -758,6 +946,18 @@ public abstract class BlockStateProvider implements DataProvider {
doorBlock(block, bottomLeft, bottomLeftOpen, bottomRight, bottomRightOpen, topLeft, topLeftOpen, topRight, topRightOpen);
}
private void doorBlockInternalWithRenderTypeAndFast(DoorBlock block, String baseName, ResourceLocation bottom, ResourceLocation top, ResourceLocation renderType, ResourceLocation renderTypeFast) {
ModelFile bottomLeft = models().doorBottomLeft(baseName + "_bottom_left", bottom, top).renderType(renderType, renderTypeFast);
ModelFile bottomLeftOpen = models().doorBottomLeftOpen(baseName + "_bottom_left_open", bottom, top).renderType(renderType, renderTypeFast);
ModelFile bottomRight = models().doorBottomRight(baseName + "_bottom_right", bottom, top).renderType(renderType, renderTypeFast);
ModelFile bottomRightOpen = models().doorBottomRightOpen(baseName + "_bottom_right_open", bottom, top).renderType(renderType, renderTypeFast);
ModelFile topLeft = models().doorTopLeft(baseName + "_top_left", bottom, top).renderType(renderType, renderTypeFast);
ModelFile topLeftOpen = models().doorTopLeftOpen(baseName + "_top_left_open", bottom, top).renderType(renderType, renderTypeFast);
ModelFile topRight = models().doorTopRight(baseName + "_top_right", bottom, top).renderType(renderType, renderTypeFast);
ModelFile topRightOpen = models().doorTopRightOpen(baseName + "_top_right_open", bottom, top).renderType(renderType, renderTypeFast);
doorBlock(block, bottomLeft, bottomLeftOpen, bottomRight, bottomRightOpen, topLeft, topLeftOpen, topRight, topRightOpen);
}
public void doorBlock(DoorBlock block, ModelFile bottomLeft, ModelFile bottomLeftOpen, ModelFile bottomRight, ModelFile bottomRightOpen, ModelFile topLeft, ModelFile topLeftOpen, ModelFile topRight, ModelFile topRightOpen) {
getVariantBuilder(block).forAllStatesExcept(state -> {
int yRot = ((int) state.getValue(DoorBlock.FACING).toYRot()) + 90;
@@ -824,6 +1024,22 @@ public abstract class BlockStateProvider implements DataProvider {
trapdoorBlockInternalWithRenderType(block, name + "_trapdoor", texture, orientable, renderType);
}
public void trapdoorBlockWithRenderTypeAndFast(TrapDoorBlock block, ResourceLocation texture, boolean orientable, String renderType, String renderTypeFast) {
trapdoorBlockInternalWithRenderTypeAndFast(block, key(block).toString(), texture, orientable, ResourceLocation.tryParse(renderType), ResourceLocation.tryParse(renderTypeFast));
}
public void trapdoorBlockWithRenderTypeAndFast(TrapDoorBlock block, String name, ResourceLocation texture, boolean orientable, String renderType, String renderTypeFast) {
trapdoorBlockInternalWithRenderTypeAndFast(block, name + "_trapdoor", texture, orientable, ResourceLocation.tryParse(renderType), ResourceLocation.tryParse(renderTypeFast));
}
public void trapdoorBlockWithRenderTypeAndFast(TrapDoorBlock block, ResourceLocation texture, boolean orientable, ResourceLocation renderType, ResourceLocation renderTypeFast) {
trapdoorBlockInternalWithRenderTypeAndFast(block, key(block).toString(), texture, orientable, renderType, renderTypeFast);
}
public void trapdoorBlockWithRenderTypeAndFast(TrapDoorBlock block, String name, ResourceLocation texture, boolean orientable, ResourceLocation renderType, ResourceLocation renderTypeFast) {
trapdoorBlockInternalWithRenderTypeAndFast(block, name + "_trapdoor", texture, orientable, renderType, renderTypeFast);
}
private void trapdoorBlockInternal(TrapDoorBlock block, String baseName, ResourceLocation texture, boolean orientable) {
ModelFile bottom = orientable ? models().trapdoorOrientableBottom(baseName + "_bottom", texture) : models().trapdoorBottom(baseName + "_bottom", texture);
ModelFile top = orientable ? models().trapdoorOrientableTop(baseName + "_top", texture) : models().trapdoorTop(baseName + "_top", texture);
@@ -838,6 +1054,13 @@ public abstract class BlockStateProvider implements DataProvider {
trapdoorBlock(block, bottom, top, open, orientable);
}
private void trapdoorBlockInternalWithRenderTypeAndFast(TrapDoorBlock block, String baseName, ResourceLocation texture, boolean orientable, ResourceLocation renderType, ResourceLocation renderTypeFast) {
ModelFile bottom = orientable ? models().trapdoorOrientableBottom(baseName + "_bottom", texture).renderType(renderType, renderTypeFast) : models().trapdoorBottom(baseName + "_bottom", texture).renderType(renderType, renderTypeFast);
ModelFile top = orientable ? models().trapdoorOrientableTop(baseName + "_top", texture).renderType(renderType, renderTypeFast) : models().trapdoorTop(baseName + "_top", texture).renderType(renderType, renderTypeFast);
ModelFile open = orientable ? models().trapdoorOrientableOpen(baseName + "_open", texture).renderType(renderType, renderTypeFast) : models().trapdoorOpen(baseName + "_open", texture).renderType(renderType, renderTypeFast);
trapdoorBlock(block, bottom, top, open, orientable);
}
public void trapdoorBlock(TrapDoorBlock block, ModelFile bottom, ModelFile top, ModelFile open, boolean orientable) {
getVariantBuilder(block).forAllStatesExcept(state -> {
int xRot = 0;
@@ -60,6 +60,7 @@ public class ModelBuilder<T extends ModelBuilder<T>> extends ModelFile {
protected final ExistingFileHelper existingFileHelper;
protected String renderType = null;
protected String renderTypeFast = null;
protected boolean ambientOcclusion = true;
protected GuiLight guiLight = null;
@@ -149,12 +150,17 @@ public class ModelBuilder<T extends ModelBuilder<T>> extends ModelFile {
}
/**
* Set the render type for this model.
* Set the render type for this model. Any render types to be used must be registered via
* {@link net.minecraftforge.client.event.RegisterNamedRenderTypesEvent RegisterNamedRenderTypesEvent}.
* <p>
* Consider using {@linkplain #renderType(String, String)} if you need to set a render type for
* {@linkplain net.minecraft.client.GraphicsStatus#FAST fast graphics}.
*
* @param renderType the render type. Must be registered via
* {@link net.minecraftforge.client.event.RegisterNamedRenderTypesEvent}
* @param renderType the render type
* @return this builder
* @throws NullPointerException if {@code renderType} is {@code null}
*
* @throws NullPointerException if {@code renderType} is {@code null}
* @see #renderType(String, String)
*/
public T renderType(String renderType) {
Preconditions.checkNotNull(renderType, "Render type must not be null");
@@ -162,16 +168,55 @@ public class ModelBuilder<T extends ModelBuilder<T>> extends ModelFile {
}
/**
* Set the render type for this model.
* Set the render types for this model. Any render types to be used must be registered via
* {@link net.minecraftforge.client.event.RegisterNamedRenderTypesEvent RegisterNamedRenderTypesEvent}.
*
* @param renderType the render type. Must be registered via
* {@link net.minecraftforge.client.event.RegisterNamedRenderTypesEvent}
* @param renderType the render type for {@linkplain net.minecraft.client.GraphicsStatus#FANCY fancy graphics}
* @param renderTypeFast the render type for {@linkplain net.minecraft.client.GraphicsStatus#FAST fast graphics}
* @return this builder
* @throws NullPointerException if {@code renderType} is {@code null}
*
* @throws NullPointerException if {@code renderType} is {@code null}
*/
public T renderType(String renderType, String renderTypeFast) {
Preconditions.checkNotNull(renderType, "Render type must not be null");
return renderType(ResourceLocation.parse(renderType), ResourceLocation.parse(renderTypeFast));
}
/**
* Set the render type for this model. Any render types to be used must be registered via
* {@link net.minecraftforge.client.event.RegisterNamedRenderTypesEvent RegisterNamedRenderTypesEvent}.
* <p>
* Consider using {@linkplain #renderType(ResourceLocation, ResourceLocation)} if you need to set a render type for
* {@linkplain net.minecraft.client.GraphicsStatus#FAST fast graphics}.
*
* @param renderType the render type
* @return this builder
*
* @throws NullPointerException if {@code renderType} is {@code null}
* @see #renderType(ResourceLocation, ResourceLocation)
*/
public T renderType(ResourceLocation renderType) {
Preconditions.checkNotNull(renderType, "Render type must not be null");
this.renderType = renderType.toString();
this.renderTypeFast = null;
return self();
}
/**
* Set the render types for this model. Any render types to be used must be registered via
* {@link net.minecraftforge.client.event.RegisterNamedRenderTypesEvent RegisterNamedRenderTypesEvent}.
*
* @param renderType the render type for {@linkplain net.minecraft.client.GraphicsStatus#FANCY fancy graphics}
* @param renderTypeFast the render type for {@linkplain net.minecraft.client.GraphicsStatus#FAST fast graphics}
* @return this builder
*
* @throws NullPointerException if {@code renderType} is {@code null}
*/
public T renderType(ResourceLocation renderType, ResourceLocation renderTypeFast) {
Preconditions.checkNotNull(renderType, "Render type must not be null");
Preconditions.checkNotNull(renderTypeFast, "Fast graphics render type must not be null");
this.renderType = renderType.toString();
this.renderTypeFast = renderType.toString();
return self();
}
Loaded 100 of 187 files, more files were not shown because too many files have changed in this diff. Show more