Compare commits

...
31 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
89 changed files with 2722 additions and 271 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.') }()
}
}
+3 -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;';
}
}
+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/
@@ -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);
}
@@ -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"))
@@ -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 {
@@ -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
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
+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
@@ -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();
@@ -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);
@@ -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))
@@ -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 @@
}
@@ -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_) {
@@ -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_);
+ }
@@ -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 @@
}
@@ -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);
+9 -9
View File
@@ -24,26 +24,26 @@ 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.2.1')
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')
@@ -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'])
@@ -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;
@@ -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;
@@ -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();
}
@@ -366,7 +366,8 @@ public abstract class ModelProvider<T extends ModelBuilder<T>> implements DataPr
}
public T leaves(String name, ResourceLocation texture) {
return singleTexture(name, BLOCK_FOLDER + "/leaves", "all", texture);
return singleTexture(name, BLOCK_FOLDER + "/leaves", "all", texture)
.renderType("cutout_mipped", "solid");
}
/**
@@ -34,6 +34,7 @@ public class ItemLayerModelBuilder<T extends ModelBuilder<T>> extends CustomLoad
private final Int2ObjectMap<ForgeFaceData> faceData = new Int2ObjectOpenHashMap<>();
private final Map<ResourceLocation, IntSet> renderTypes = new LinkedHashMap<>();
private final Map<ResourceLocation, IntSet> renderTypesFast = new LinkedHashMap<>();
private final IntSet layersWithRenderTypes = new IntOpenHashSet();
protected ItemLayerModelBuilder(T parent, ExistingFileHelper existingFileHelper)
@@ -116,6 +117,23 @@ public class ItemLayerModelBuilder<T extends ModelBuilder<T>> extends CustomLoad
return renderType(asLoc, layers);
}
public ItemLayerModelBuilder<T> renderType(String renderType, String renderTypeFast, int... layers)
{
Preconditions.checkNotNull(renderType, "Render type must not be null");
Preconditions.checkNotNull(renderTypeFast, "Fast graphics render type must not be null");
ResourceLocation asLoc;
if (renderType.contains(":"))
asLoc = ResourceLocation.parse(renderType);
else
asLoc = parent.getLocation().withPath(renderType);
ResourceLocation asLocFast;
if (renderTypeFast.contains(":"))
asLocFast = ResourceLocation.parse(renderTypeFast);
else
asLocFast = parent.getLocation().withPath(renderTypeFast);
return renderType(asLoc, asLocFast, layers);
}
/**
* Set the render type for a set of layers.
*
@@ -145,6 +163,25 @@ public class ItemLayerModelBuilder<T extends ModelBuilder<T>> extends CustomLoad
return this;
}
public ItemLayerModelBuilder<T> renderType(ResourceLocation renderType, ResourceLocation renderTypeFast, int... layers)
{
Preconditions.checkNotNull(renderType, "Render type must not be null");
Preconditions.checkNotNull(renderTypeFast, "Fast graphics render type must not be null");
Preconditions.checkNotNull(layers, "Layers must not be null");
Preconditions.checkArgument(layers.length > 0, "At least one layer must be specified");
Preconditions.checkArgument(Arrays.stream(layers).allMatch(i -> i >= 0), "All layers must be >= 0");
var alreadyAssigned = Arrays.stream(layers).filter(layersWithRenderTypes::contains).toArray();
Preconditions.checkArgument(alreadyAssigned.length == 0, "Attempted to re-assign layer render types: " + Arrays.toString(alreadyAssigned));
var renderTypeLayers = renderTypes.computeIfAbsent(renderType, $ -> new IntOpenHashSet());
var renderTypeFastLayers = renderTypesFast.computeIfAbsent(renderType, $ -> new IntOpenHashSet());
Arrays.stream(layers).forEach(layer -> {
renderTypeLayers.add(layer);
renderTypeFastLayers.add(layer);
layersWithRenderTypes.add(layer);
});
return this;
}
@Override
public JsonObject toJson(JsonObject json)
{
@@ -171,6 +208,14 @@ public class ItemLayerModelBuilder<T extends ModelBuilder<T>> extends CustomLoad
});
json.add("render_types", renderTypes);
JsonObject renderTypesFast = new JsonObject();
this.renderTypesFast.forEach((renderTypeFast, layers) -> {
JsonArray array = new JsonArray();
layers.intStream().sorted().forEach(array::add);
renderTypesFast.add(renderTypeFast.toString(), array);
});
json.add("render_types_fast", renderTypesFast);
return json;
}
}
@@ -34,6 +34,8 @@ public class BlockGeometryBakingContext implements IGeometryBakingContext {
private Transformation rootTransform;
@Nullable
private ResourceLocation renderTypeHint;
@Nullable
private ResourceLocation renderTypeFastHint;
private boolean gui3d = true;
@ApiStatus.Internal
@@ -115,10 +117,22 @@ public class BlockGeometryBakingContext implements IGeometryBakingContext {
return owner.parent != null ? owner.parent.customData.getRenderTypeHint() : null;
}
@Nullable
@Override
public ResourceLocation getRenderTypeFastHint() {
if (renderTypeFastHint != null)
return renderTypeFastHint;
return owner.parent != null ? owner.parent.customData.getRenderTypeFastHint() : null;
}
public void setRenderTypeHint(ResourceLocation renderTypeHint) {
this.renderTypeHint = renderTypeHint;
}
public void setRenderTypeFastHint(ResourceLocation renderTypeFastHint) {
this.renderTypeFastHint = renderTypeFastHint;
}
public void setGui3d(boolean gui3d) {
this.gui3d = gui3d;
}
@@ -128,6 +142,7 @@ public class BlockGeometryBakingContext implements IGeometryBakingContext {
this.rootTransform = other.rootTransform;
this.visibilityData.copyFrom(other.visibilityData);
this.renderTypeHint = other.renderTypeHint;
this.renderTypeFastHint = other.renderTypeFastHint;
this.gui3d = other.gui3d;
}
@@ -74,6 +74,13 @@ public interface IGeometryBakingContext {
@Nullable
ResourceLocation getRenderTypeHint();
/**
* @return a hint of the {@linkplain net.minecraft.client.GraphicsStatus#FAST fast graphics} render type this model
* should use. Custom loaders may ignore this.
*/
@Nullable
default ResourceLocation getRenderTypeFastHint() { return null; }
/**
* Queries the visibility of a component of this model.
*
@@ -89,4 +96,20 @@ public interface IGeometryBakingContext {
default RenderTypeGroup getRenderType(ResourceLocation name) {
return NamedRenderTypeManager.get(name);
}
/**
* {@return a {@link RenderTypeGroup} from the {@link #getRenderTypeHint()}, or the empty group if not found.}
*/
default RenderTypeGroup getRenderType() {
var hint = getRenderTypeHint();
return hint == null ? RenderTypeGroup.EMPTY : getRenderType(hint);
}
/**
* {@return a {@link RenderTypeGroup} from the {@link #getRenderTypeHint()}, or the empty group if not found.}
*/
default RenderTypeGroup getRenderTypeFast() {
var hint = getRenderTypeFastHint();
return hint == null ? RenderTypeGroup.EMPTY : getRenderType(hint);
}
}
@@ -25,10 +25,8 @@ public abstract class SimpleUnbakedGeometry<T extends SimpleUnbakedGeometry<T>>
public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function<Material, TextureAtlasSprite> spriteGetter, ModelState modelState) {
TextureAtlasSprite particle = spriteGetter.apply(context.getMaterial("particle"));
var renderTypeHint = context.getRenderTypeHint();
var renderTypes = renderTypeHint != null ? context.getRenderType(renderTypeHint) : RenderTypeGroup.EMPTY;
IModelBuilder<?> builder = IModelBuilder.of(context.useAmbientOcclusion(), context.useBlockLight(), context.isGui3d(),
context.getTransforms(), particle, renderTypes);
context.getTransforms(), particle, context.getRenderType(), context.getRenderTypeFast());
addQuads(context, builder, baker, spriteGetter, modelState);
@@ -51,6 +51,8 @@ public class StandaloneGeometryBakingContext implements IGeometryBakingContext {
private final Transformation rootTransform;
@Nullable
private final ResourceLocation renderTypeHint;
@Nullable
private final ResourceLocation renderTypeFastHint;
private final BiPredicate<String, Boolean> visibilityTest;
private StandaloneGeometryBakingContext(
@@ -60,6 +62,18 @@ public class StandaloneGeometryBakingContext implements IGeometryBakingContext {
ItemTransforms transforms, Transformation rootTransform,
@Nullable ResourceLocation renderTypeHint,
BiPredicate<String, Boolean> visibilityTest
) {
this(modelName, materialCheck, materialLookup, isGui3d, useBlockLight, useAmbientOcclusion, transforms, rootTransform, renderTypeHint, null, visibilityTest);
}
private StandaloneGeometryBakingContext(
ResourceLocation modelName, Predicate<String> materialCheck,
Function<String, Material> materialLookup, boolean isGui3d,
boolean useBlockLight, boolean useAmbientOcclusion,
ItemTransforms transforms, Transformation rootTransform,
@Nullable ResourceLocation renderTypeHint,
@Nullable ResourceLocation renderTypeFastHint,
BiPredicate<String, Boolean> visibilityTest
) {
this.modelName = modelName;
this.materialCheck = materialCheck;
@@ -70,6 +84,7 @@ public class StandaloneGeometryBakingContext implements IGeometryBakingContext {
this.transforms = transforms;
this.rootTransform = rootTransform;
this.renderTypeHint = renderTypeHint;
this.renderTypeFastHint = renderTypeFastHint;
this.visibilityTest = visibilityTest;
}
@@ -119,6 +134,12 @@ public class StandaloneGeometryBakingContext implements IGeometryBakingContext {
return renderTypeHint;
}
@Nullable
@Override
public ResourceLocation getRenderTypeFastHint() {
return renderTypeFastHint;
}
@Override
public boolean isComponentVisible(String component, boolean fallback) {
return visibilityTest.test(component, fallback);
@@ -143,6 +164,8 @@ public class StandaloneGeometryBakingContext implements IGeometryBakingContext {
private Transformation rootTransform = Transformation.identity();
@Nullable
private ResourceLocation renderTypeHint;
@Nullable
private ResourceLocation renderTypeFastHint;
private BiPredicate<String, Boolean> visibilityTest = (c, def) -> def;
private Builder() { }
@@ -156,6 +179,7 @@ public class StandaloneGeometryBakingContext implements IGeometryBakingContext {
this.transforms = parent.getTransforms();
this.rootTransform = parent.getRootTransform();
this.renderTypeHint = parent.getRenderTypeHint();
this.renderTypeFastHint = parent.getRenderTypeFastHint();
this.visibilityTest = parent::isComponentVisible;
}
@@ -205,6 +229,12 @@ public class StandaloneGeometryBakingContext implements IGeometryBakingContext {
return this;
}
public Builder withRenderTypeHint(ResourceLocation renderTypeHint, ResourceLocation renderTypeFastHint) {
this.renderTypeHint = renderTypeHint;
this.renderTypeFastHint = renderTypeFastHint;
return this;
}
@SuppressWarnings("deprecation")
public Builder withVisibleComponents(Object2BooleanMap<String> parts) {
this.visibilityTest = parts::getOrDefault;
@@ -212,7 +242,7 @@ public class StandaloneGeometryBakingContext implements IGeometryBakingContext {
}
public StandaloneGeometryBakingContext build(ResourceLocation modelName) {
return new StandaloneGeometryBakingContext(modelName, materialCheck, materialLookup, isGui3d, useBlockLight, useAmbientOcclusion, transforms, rootTransform, renderTypeHint, visibilityTest);
return new StandaloneGeometryBakingContext(modelName, materialCheck, materialLookup, isGui3d, useBlockLight, useAmbientOcclusion, transforms, rootTransform, renderTypeHint, renderTypeFastHint, visibilityTest);
}
}
}
@@ -98,6 +98,8 @@ public class ForgeConfig {
public final BooleanValue showLoadWarnings;
public final BooleanValue allowMipmapLowering;
Client(ForgeConfigSpec.Builder builder) {
builder.comment("Client only settings, mostly things related to rendering")
.push("client");
@@ -112,6 +114,11 @@ public class ForgeConfig {
.translation("forge.configgui.showLoadWarnings")
.define("showLoadWarnings", true);
allowMipmapLowering = builder
.comment("When enabled, Forge will allow mipmaps to be lowered in real-time. This is the default behavior in vanilla. Use this if you experience issues with resource packs that use textures lower than 8x8.")
.translation("forge.configgui.allowMipmapLowering")
.define("allowMipmapLowering", false);
builder.pop();
}
@@ -120,6 +127,10 @@ public class ForgeConfig {
public final boolean showLoadWarnings() {
return clientSpec.isLoaded() ? showLoadWarnings.get() : showLoadWarnings.getDefault();
}
public final boolean allowMipmapLowering() {
return clientSpec.isLoaded() ? allowMipmapLowering.get() : allowMipmapLowering.getDefault();
}
}
static final ForgeConfigSpec clientSpec;
@@ -94,7 +94,7 @@ public class ForgeInternalHandler {
@SubscribeEvent
public void onResourceReload(AddReloadListenerEvent event) {
INSTANCE = new LootModifierManager(event.getRegistryAccess());
INSTANCE = new LootModifierManager(event.getRegistries());
event.addListener(INSTANCE);
}
@@ -5,49 +5,130 @@
package net.minecraftforge.common.extensions;
import java.util.Arrays;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Supplier;
import net.minecraft.ChatFormatting;
import net.minecraft.Util;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import org.jetbrains.annotations.Nullable;
import com.mojang.authlib.GameProfile;
import io.netty.channel.embedded.EmbeddedChannel;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTestAssertException;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.network.Connection;
import net.minecraft.network.ConnectionProtocol;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.Style;
import net.minecraft.network.protocol.PacketFlow;
import net.minecraft.network.protocol.game.GameProtocols;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.CommonListenerCookie;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraftforge.common.ForgeI18n;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Event;
public interface IForgeGameTestHelper {
private GameTestHelper self() {
return (GameTestHelper)this;
return (GameTestHelper) this;
}
default void say(String message) {
this.say(message, Style.EMPTY);
}
default void say(String message, Style style) {
this.say(this.getMessage(message).withStyle(style));
}
default void say(String message, ChatFormatting style) {
this.say(this.getMessage(message).withStyle(style));
}
default void say(String message, int color) {
this.say(this.getMessage(message).withColor(color));
}
default void say(Component component) {
this.self().getLevel().players().forEach(p -> p.sendSystemMessage(component));
}
private MutableComponent getMessage(String message) {
return ForgeI18n.getPattern(message) != null ? Component.translatable(message) : Component.literal(message);
}
default void assertTrue(boolean value, Supplier<String> message) {
if (!value)
throw new GameTestAssertException(message.get());
if (!value)
throw new GameTestAssertException(message.get());
}
default void assertFalse(boolean value, Supplier<String> message) {
if (value)
throw new GameTestAssertException(message.get());
if (value)
throw new GameTestAssertException(message.get());
}
default <N> void assertValueEqual(N expected, N actual, String name, String message) {
this.assertValueEqual(expected, actual, name, () -> message);
}
default <N> void assertValueEqual(N expected, N actual, String name, Supplier<String> message) {
if (!Objects.equals(expected, actual))
throw new GameTestAssertException("%s -- Expected %s to be %s, but was %s".formatted(message.get(), name, expected, actual));
}
default <N> void assertValueEqual(N[] expected, N[] actual, String name, String message) {
this.assertValueEqual(expected, actual, name, () -> message);
}
default <N> void assertValueEqual(N[] expected, N[] actual, String name, Supplier<String> message) {
if (!Objects.deepEquals(expected, actual))
throw new GameTestAssertException("%s -- Expected %s to be %s, but was %s".formatted(message.get(), name, Arrays.toString(expected), Arrays.toString(actual)));
}
default <N> void assertValueNotEqual(N expected, N actual, String name, String message) {
this.assertValueNotEqual(expected, actual, name, () -> message);
}
default <N> void assertValueNotEqual(N expected, N actual, String name, Supplier<String> message) {
if (Objects.equals(expected, actual))
throw new GameTestAssertException("%s -- Expected %s to NOT be %s, but was".formatted(message.get(), name, expected));
}
default <N> void assertValueNotEqual(N[] expected, N[] actual, String name, String message) {
this.assertValueNotEqual(expected, actual, name, () -> message);
}
default <N> void assertValueNotEqual(N[] expected, N[] actual, String name, Supplier<String> message) {
if (!Objects.deepEquals(expected, actual))
throw new GameTestAssertException("%s -- Expected %s to NOT be %s, but was".formatted(message.get(), name, Arrays.toString(expected)));
}
default <E> Registry<E> registryLookup(ResourceKey<? extends Registry<? extends E>> registryKey) {
return this.self().getLevel().registryAccess().lookupOrThrow(registryKey);
}
default ServerPlayer makeMockServerPlayer() {
var level = self().getLevel();
var cookie = CommonListenerCookie.createInitial(new GameProfile(UUID.randomUUID(), "test-mock-player"), false);
var player = new ServerPlayer(level.getServer(), level, cookie.gameProfile(), cookie.clientInformation()) {
public boolean isSpectator() {
return false;
}
public boolean isSpectator() {
return false;
}
public boolean isCreative() {
return true;
}
public boolean isCreative() {
return true;
}
};
var connection = new Connection(PacketFlow.SERVERBOUND);
@SuppressWarnings("unused") // The constructor has side effects
@@ -59,4 +140,232 @@ public interface IForgeGameTestHelper {
connection.setupInboundProtocol(info, listener);
return player;
}
/**
* Registers an event listener that will be unregistered when the test is finished running.
*/
default <E extends Event> void addEventListener(Consumer<E> consumer) {
MinecraftForge.EVENT_BUS.addListener(consumer);
self().addCleanup(success -> MinecraftForge.EVENT_BUS.unregister(consumer));
}
/**
* Registers an event listener that will be unregistered when the test is finished running.
*/
default void registerEventListener(Object handler) {
MinecraftForge.EVENT_BUS.register(handler);
self().addCleanup(success -> MinecraftForge.EVENT_BUS.unregister(handler));
}
/**
* Creates a floor of stone blocks at the bottom of the test area.
*/
default void makeFloor() {
makeFloor(Blocks.STONE);
}
/**
* Creates a floor of the specified block under the test area.
*/
default void makeFloor(Block block) {
makeFloor(block, -1);
}
/**
* Creates a floor of the specified block at the specified height.
*/
default void makeFloor(Block block, int height) {
var bounds = self().getBounds();
var pos = new BlockPos.MutableBlockPos();
for (int x = 0; x < (int) bounds.getXsize(); x++) {
for (int y = 0; y < (int) bounds.getZsize(); y++) {
pos.set(x, height, y);
if (self().getBlockState(pos).is(Blocks.AIR))
self().setBlock(pos, block);
}
}
}
default <T> Flag<T> flag(String name) {
return new Flag<>(name);
}
default IntFlag intFlag(String name) {
return new IntFlag(name);
}
default IntFlag intFlag(String name, int value) {
return this.intFlag(name, (long) value);
}
default IntFlag intFlag(String name, long value) {
return Util.make(new IntFlag(name), flag -> flag.set(value));
}
default BoolFlag boolFlag(String name) {
return new BoolFlag(name);
}
public static class Flag<T> {
private final String name;
@Nullable
protected T value = null;
public Flag(String name) {
this.name = name;
}
public void set(T value) {
this.value = value;
}
@Nullable
public T get() {
return this.value;
}
public void assertUnset() {
this.assertUnset((Supplier<String>) null);
}
public void assertUnset(String message) {
this.assertUnset(message != null ? () -> message : null);
}
public void assertUnset(Supplier<String> message) {
if (this.value != null) {
String s = message != null ? message.get() + " -- " : "";
throw new GameTestAssertException(s + "Expected " + name + " to be null, but was " + this.value);
}
}
public void assertSet() {
this.assertSet((Supplier<String>) null);
}
public void assertSet(String message) {
this.assertSet(message != null ? () -> message : null);
}
public void assertSet(Supplier<String> message) {
if (this.value == null) {
String s = message != null ? message.get() + " -- " : "";
throw new GameTestAssertException(s + "Flag " + name + " was never set");
}
}
public void assertEquals(T expected) {
this.assertEquals(expected, (Supplier<String>) null);
}
public void assertEquals(T expected, String message) {
this.assertEquals(expected, message != null ? () -> message : null);
}
public void assertEquals(T expected, Supplier<String> message) {
assertSet(message);
if (expected != null && !expected.equals(this.value)) {
String s = message != null ? message.get() + " -- " : "";
throw new GameTestAssertException(s + "Expected " + name + " to be " + expected + ", but was " + this.value);
}
}
}
public static class IntFlag extends Flag<Long> {
public IntFlag(String name) {
super(name);
}
public void set(long value) {
super.set(value);
}
public void increment() {
this.decrement(1L);
}
public void increment(int amount) {
this.decrement((long) amount);
}
public void increment(long amount) {
if (this.value != null)
this.set(this.value + amount);
}
public void decrement() {
this.decrement(1L);
}
public void decrement(int amount) {
this.decrement((long) amount);
}
public void decrement(long amount) {
if (this.value != null)
this.set(this.value - amount);
}
public byte getByte() {
return this.value == null ? -1 : this.value.byteValue();
}
public int getInt() {
return this.value == null ? -1 : this.value.intValue();
}
public long getLong() {
return this.value == null ? -1 : this.value.longValue();
}
public void assertEquals(int expected) {
super.assertEquals((long) expected);
}
public void assertEquals(long expected) {
super.assertEquals(expected);
}
public void assertEquals(int expected, String message) {
super.assertEquals((long) expected, message);
}
public void assertEquals(long expected, String message) {
super.assertEquals(expected, message);
}
public void assertEquals(int expected, Supplier<String> message) {
super.assertEquals((long) expected, message);
}
public void assertEquals(long expected, Supplier<String> message) {
super.assertEquals(expected, message);
}
}
public static class BoolFlag extends Flag<Boolean> {
public BoolFlag(String name) {
super(name);
}
public void set(boolean value) {
super.set(value);
}
public boolean getBool() {
return this.value != null && this.value;
}
public void assertEquals(boolean expected) {
super.assertEquals(expected);
}
public void assertEquals(boolean expected, String message) {
super.assertEquals(expected, message);
}
public void assertEquals(boolean expected, Supplier<String> message) {
super.assertEquals(expected, message);
}
}
}
@@ -7,6 +7,8 @@ package net.minecraftforge.common.extensions;
import java.util.function.Consumer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.item.*;
@@ -31,6 +33,7 @@ import net.minecraft.world.level.Level;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.common.ToolAction;
import net.minecraftforge.common.ToolActions;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.registries.IForgeRegistry;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -459,7 +462,7 @@ public interface IForgeItem {
* @return True if this ItemStack can disable the shield in question.
*/
default boolean canDisableShield(ItemStack stack, ItemStack shield, LivingEntity entity, LivingEntity attacker) {
return this instanceof AxeItem;
return attacker.canDisableShield();
}
/**
@@ -481,6 +484,28 @@ public interface IForgeItem {
*/
default void onHorseArmorTick(ItemStack stack, Level level, Mob horse) { }
/**
* Called when this item is to be damaged, such as use a tool being used or a shield blocking damage. The damage
* parameter has not yet been processed, so enchantments are not taken into account.
*
* @param stack The processed amount of damage the item will take
* @param damage The amount of damage the item will take before processing
* @param level The level where the damage is taking place
* @param player The player holding the item
* @param canBreak If the item can break from this damage instance ({@code true} if this is called from
* {@link ItemStack#hurtAndBreak(int, ServerLevel, ServerPlayer, Consumer)}, {@code false} if from
* {@link ItemStack#hurtWithoutBreaking(int, Player)})
* @param onBroken The callback for when an item is broken (use this if you plan on cancelling damage that will
* break an item)
* @return The amount of damage the item should take, after processing
*
* @apiNote If the item stack is not {@linkplain ItemStack#isDamageableItem() damageable} or the player
* {@linkplain Player#hasInfiniteMaterials() has infinite materials}, this method will not be called.
*/
default int damageItem(ItemStack stack, int damage, ServerLevel level, @Nullable ServerPlayer player, boolean canBreak, Consumer<Item> onBroken) {
return damage;
}
/**
* Called when an item entity for this stack is destroyed. Note: The {@link ItemStack} can be retrieved from the item entity.
*
@@ -587,4 +612,12 @@ public interface IForgeItem {
default boolean canGrindstoneRepair(ItemStack stack) {
return false;
}
/**
* @return The Default Capability Provider for this item, if any, accounting for the given itemstack.
*/
@Nullable
default ICapabilityProvider getCapabilityProvider(ItemStack stack) {
return null;
}
}
@@ -5,6 +5,8 @@
package net.minecraftforge.common.extensions;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.monster.Monster;
import net.minecraft.world.entity.player.Inventory;
@@ -32,6 +34,8 @@ import net.minecraftforge.common.ToolActions;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.Consumer;
/*
* Extension added to ItemStack that bounces to ItemSack sensitive Item methods. Typically this is just for convince.
*/
@@ -377,6 +381,28 @@ public interface IForgeItemStack {
return self().getItem().getSweepHitBox(self(), player, target);
}
/**
* Called when this item is to be damaged, such as use a tool being used or a shield blocking damage. The damage
* parameter has not yet been processed, so enchantments are not taken into account.
*
* @param damage The amount of damage the item will take before processing
* @param level The level where the damage is taking place
* @param player The player holding the item
* @param canBreak If the item can break from this damage instance ({@code true} if this is called from
* {@link ItemStack#hurtAndBreak(int, ServerLevel, ServerPlayer, Consumer)}, {@code false} if from
* {@link ItemStack#hurtWithoutBreaking(int, Player)})
* @param onBroken The callback for when an item is broken (use this if you plan on cancelling damage that will
* break an item)
* @return The amount of damage the item should take
*
* @apiNote If the item stack is not {@linkplain ItemStack#isDamageableItem() damageable} or the player
* {@linkplain Player#hasInfiniteMaterials() has infinite materials}, this method will not be called.
* @see IForgeItem#damageItem(ItemStack, int, ServerLevel, ServerPlayer, boolean, Consumer)
*/
default int damageItem(int damage, ServerLevel level, @Nullable ServerPlayer player, boolean canBreak, Consumer<Item> onBroken) {
return self().getItem().damageItem(self(), damage, level, player, canBreak, onBroken);
}
/**
* Called when an item entity for this stack is destroyed. Note: The {@link ItemStack} can be retrieved from the item entity.
*
@@ -0,0 +1,27 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.common.extensions;
import net.minecraft.world.level.storage.LevelSummary;
public interface IForgeLevelSummary {
private LevelSummary self() {
return (LevelSummary) this;
}
/**
* Checks if the Forge lifecycle of this level is experimental. This is used to render the experimental warning
* tooltip on the level select screen.
*
* @return {@code true} if the level is experimental
*/
default boolean isLifecycleExperimental() {
// NOTE: Because CorruptedLevelSummary and SymlinkLevelSummary can have null settings, we need to check it
var settings = this.self().getSettings();
return settings != null && settings.getLifecycle().equals(com.mojang.serialization.Lifecycle.experimental());
}
}
@@ -6,7 +6,10 @@
package net.minecraftforge.event;
import com.google.common.collect.ImmutableList;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.LayeredRegistryAccess;
import net.minecraft.core.RegistryAccess;
import net.minecraft.server.RegistryLayer;
import net.minecraft.server.ReloadableServerResources;
import net.minecraft.server.packs.resources.PreparableReloadListener;
import net.minecraft.server.packs.resources.ResourceManager;
@@ -29,10 +32,31 @@ import java.util.concurrent.Executor;
public class AddReloadListenerEvent extends Event {
private final List<PreparableReloadListener> listeners = new ArrayList<>();
private final ReloadableServerResources serverResources;
private final HolderLookup.Provider registries;
@Deprecated(forRemoval = true, since = "1.21.4")
private final RegistryAccess registryAccess;
public AddReloadListenerEvent(ReloadableServerResources serverResources, RegistryAccess registryAccess) {
/** @deprecated Does not provide additional context. Use {@link #AddReloadListenerEvent(ReloadableServerResources, HolderLookup.Provider, RegistryAccess)} instead. */
@Deprecated(forRemoval = true, since = "1.21.4")
public AddReloadListenerEvent(
ReloadableServerResources serverResources,
RegistryAccess registryAccess
) {
this(
serverResources,
registryAccess,
registryAccess
);
}
public AddReloadListenerEvent(
ReloadableServerResources serverResources,
HolderLookup.Provider registries,
@Deprecated(forRemoval = true, since = "1.21.4") RegistryAccess registryAccess
) {
this.serverResources = serverResources;
this.registries = registries;
this.registryAccess = registryAccess;
}
@@ -62,21 +86,27 @@ public class AddReloadListenerEvent extends Event {
return serverResources.getConditionContext();
}
/**
* @return A holder lookup provider containing the registries with updated tags.
*
* @see net.minecraft.server.ReloadableServerRegistries.LoadResult#lookupWithUpdatedTags()
*/
public HolderLookup.Provider getRegistries() {
return registries;
}
/**
* Provides access to the loaded registries associated with these server resources.
* All built-in and dynamic registries are loaded and frozen by this point.
* @return The RegistryAccess context for the currently active reload.
* @deprecated Does not contain updated tags. Use {@link #getRegistries()} instead.
*/
@Deprecated(forRemoval = true, since = "1.21.4")
public RegistryAccess getRegistryAccess() {
return registryAccess;
}
private static class WrappedStateAwareListener implements PreparableReloadListener {
private final PreparableReloadListener wrapped;
private WrappedStateAwareListener(final PreparableReloadListener wrapped) {
this.wrapped = wrapped;
}
private record WrappedStateAwareListener(PreparableReloadListener wrapped) implements PreparableReloadListener {
@Override
public CompletableFuture<Void> reload(final PreparationBarrier stage, final ResourceManager resourceManager, final Executor backgroundExecutor, final Executor gameExecutor) {
@@ -85,5 +115,10 @@ public class AddReloadListenerEvent extends Event {
else
return CompletableFuture.completedFuture(null);
}
@Override
public String getName() {
return wrapped.getName();
}
}
}
@@ -14,7 +14,10 @@ import java.util.Optional;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.LayeredRegistryAccess;
import net.minecraft.core.component.DataComponentMap;
import net.minecraft.server.RegistryLayer;
import net.minecraft.world.entity.*;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.chunk.storage.SerializableChunkData;
@@ -712,10 +715,15 @@ public final class ForgeEventFactory {
return fire(new SleepFinishedTimeEvent(level, newTime, minTime)).getNewTime();
}
@Deprecated(forRemoval = true, since = "1.21.4")
public static List<PreparableReloadListener> onResourceReload(ReloadableServerResources serverResources, RegistryAccess registryAccess) {
return fire(new AddReloadListenerEvent(serverResources, registryAccess)).getListeners();
}
public static List<PreparableReloadListener> onResourceReload(ReloadableServerResources serverResources, HolderLookup.Provider lookupProvider, @Deprecated(forRemoval = true, since = "1.21.4") RegistryAccess registryAccess) {
return fire(new AddReloadListenerEvent(serverResources, lookupProvider, registryAccess)).getListeners();
}
public static void onCommandRegister(CommandDispatcher<CommandSourceStack> dispatcher, Commands.CommandSelection environment, CommandBuildContext context) {
post(new RegisterCommandsEvent(dispatcher, environment, context));
}
@@ -5,6 +5,7 @@
package net.minecraftforge.internal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.ObjIntConsumer;
@@ -17,6 +18,9 @@ import net.minecraftforge.fml.ModList;
import net.minecraftforge.versions.forge.ForgeVersion;
import net.minecraftforge.versions.mcp.MCPVersion;
/**
* Controls the title screen brandings for the game.
*/
public final class BrandingControl {
private BrandingControl() {}
@@ -25,13 +29,29 @@ public final class BrandingControl {
private static void computeBranding() {
if (brandings == null) {
brandings = List.of(
"Forge " + ForgeVersion.getVersion(),
"Minecraft " + MCPVersion.getMCVersion(),
"MCP " + MCPVersion.getMCPVersion(),
ForgeI18n.parseMessage("fml.menu.loadingmods", ModList.get().size())
);
brandingsNoMC = brandings.subList(1, brandings.size());
var list = new ArrayList<String>();
// Forge
var forge = ForgeVersion.getVersion().split("-", 2);
// I don't want to use VersionChecker to check for this, so I'm just going to use the version string.
// We only have Forge Betas on the "XX.0.XX" versions anyways.
boolean beta = "0".equals(forge[0].split("\\.")[1]);
var name = beta ? "§eForge Beta§f " : "Forge ";
list.add(name + forge[0] + " (" + ForgeI18n.parseMessage("fml.menu.loadingmods", ModList.get().size()) + ")");
// Extra forge version info (like branch)
if (forge.length > 1) list.add("Branch " + forge[1]);
// TODO [Forge][FML] When FML is rewritten, add its version here behind a config value to show it (debugBrandingVersions)
// this is how to check if we are in ForgeDev:
// FMLLoader.launcherHandlerName().startsWith("forge_dev")
// Minecraft
list.add("Minecraft " + MCPVersion.getMCVersion());
brandings = List.copyOf(list);
brandingsNoMC = brandings.subList(0, brandings.size() - 1);
}
}
@@ -43,6 +63,11 @@ public final class BrandingControl {
return reverse ? brandingsNoMC.reversed() : brandingsNoMC;
}
/**
* Gets the branding lines to display over the copyright line. This is usually a message when Forge has an update.
*
* @return The branding lines to display
*/
public static List<String> getOverCopyrightBrandings() {
final class LazyInit {
private static final List<String> INSTANCE = ForgeHooksClient.forgeStatusLine == null
@@ -55,22 +80,46 @@ public final class BrandingControl {
return LazyInit.INSTANCE;
}
/**
* Iterates over each branding line, passing the line and its index to the consumer.
*
* @param includeMC Whether to include the Minecraft version line
* @param reverse Whether to iterate in reverse order
* @param lineConsumer The consumer to accept each line and its index
*/
public static void forEachLine(boolean includeMC, boolean reverse, ObjIntConsumer<String> lineConsumer) {
var brandings = getBrandings(includeMC, reverse);
for (int idx = 0; idx < brandings.size(); idx++)
lineConsumer.accept(brandings.get(idx), idx);
}
/**
* Iterates over each branding line that should be displayed above the copyright line, passing the line and its
* index to the consumer.
*
* @param lineConsumer The consumer to accept each line and its index
*/
public static void forEachAboveCopyrightLine(ObjIntConsumer<String> lineConsumer) {
var overCopyrightBrandings = getOverCopyrightBrandings();
for (int idx = 0; idx < overCopyrightBrandings.size(); idx++)
lineConsumer.accept(overCopyrightBrandings.get(idx), idx);
}
/**
* Gets the branding to use in place of the default {@code "vanilla"} branding.
*
* @return The branding to use
*/
public static String getBranding() {
return "forge";
}
/**
* The reload listener for the branding control. On reload, the brandings are recomputed in
* {@link #computeBranding()}.
*
* @return The reload listener
*/
public static ResourceManagerReloadListener resourceManagerReloadListener() {
return BrandingControl::onResourceManagerReload;
}
@@ -16,7 +16,19 @@ import java.lang.annotation.Target;
/**
* ObjectHolder can be used to automatically populate public static final fields with entries
* from the registry. These values can then be referred within mod code directly.
* @deprecated Use {@link DeferredRegister} or {@link RegistryObject} instead. Refer to the MDK for more detailed examples.
* <br>
* Example usage of alternatives:
* <pre>{@code
* // To register something
* public static final DeferredRegister<Item> ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MODID);
* public static final RegistryObject<Item> EXAMPLE_ITEM = ITEMS.register("example_item", () -> new Item(new Item.Properties()));
*
* // To get a RegistryObject that you didn't register yourself:
* public static final RegistryObject<Block> DIRT = RegistryObject.create(ResourceLocation.withDefaultNamespace("dirt"), ForgeRegistries.BLOCKS);
* }</pre>
*/
@Deprecated(since = "1.21.4", forRemoval = true)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ObjectHolder
@@ -89,6 +89,9 @@ public class ObjectHolderRegistry
.filter(a -> OBJECT_HOLDER.equals(a.annotationType()) || MOD.equals(a.annotationType()))
.toList();
if (annotations.stream().noneMatch(a -> OBJECT_HOLDER.equals(a.annotationType())))
return; // No object holders found, skip the rest of the processing
Map<Type, String> classModIds = Maps.newHashMap();
Map<Type, Class<?>> classCache = Maps.newHashMap();
@@ -141,8 +141,6 @@
"commands.config.getwithtype": "Config for %s of type %s found at %s",
"commands.config.noconfig": "Config for %s of type %s not found",
"forge.update.beta.1": "%sWARNING: %sForge Beta",
"forge.update.beta.2": "Major issues may arise, verify before reporting.",
"forge.update.newversion": "New Forge version available: %s",
"forge.menu.updatescreen.title": "Mod Update",
@@ -185,6 +183,8 @@
"forge.configgui.selectiveResourceReloadEnabled": "Enable Selective Resource Loading",
"forge.configgui.showLoadWarnings.tooltip": "When enabled, Forge will show any warnings that occurred during loading.",
"forge.configgui.showLoadWarnings": "Show Load Warnings",
"forge.configgui.allowMipmapLowering.tooltip": "When enabled, Forge will allow mipmaps to be lowered in real-time. This is the default behavior in vanilla. Use this if you experience issues with resource packs that use textures lower than 8x8.",
"forge.configgui.allowMipmapLowering": "Allow mipmap lowering",
"forge.configgui.disableVersionCheck.tooltip": "Set to true to disable Forge version check mechanics. Forge queries a small json file on our server for version information. For more details see the ForgeVersion class in our github.",
"forge.configgui.disableVersionCheck": "Disable Forge Version Check",
@@ -1,3 +1,8 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
var ASMAPI = Java.type('net.minecraftforge.coremod.api.ASMAPI')
function initializeCoreMod() {
@@ -1,25 +1,144 @@
[
"net/minecraft/server/commands/RaidCommand",
"net/minecraft/server/commands/SummonCommand",
"net/minecraft/world/entity/EntityType",
"net/minecraft/world/entity/ai/village/VillageSiege",
"net/minecraft/world/entity/animal/frog/Tadpole",
"net/minecraft/world/entity/animal/horse/SkeletonTrapGoal",
"net/minecraft/world/entity/monster/Evoker$EvokerSummonSpellGoal",
"net/minecraft/world/entity/monster/Spider",
"net/minecraft/world/entity/monster/Strider",
"net/minecraft/world/entity/monster/Zombie",
"net/minecraft/world/entity/monster/ZombieVillager",
"net/minecraft/world/entity/npc/CatSpawner",
"net/minecraft/world/entity/npc/Villager",
"net/minecraft/world/entity/raid/Raid",
"net/minecraft/world/level/NaturalSpawner",
"net/minecraft/world/level/block/entity/trialspawner/TrialSpawner",
"net/minecraft/world/level/levelgen/PatrolSpawner",
"net/minecraft/world/level/levelgen/PhantomSpawner",
"net/minecraft/world/level/levelgen/structure/structures/OceanMonumentPieces$OceanMonumentPiece",
"net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces$OceanRuinPiece",
"net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece",
"net/minecraft/world/level/levelgen/structure/structures/WoodlandMansionPieces$WoodlandMansionPiece",
"net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate"
{
"class": "net/minecraft/server/commands/RaidCommand",
"methods": [
"spawnLeader(Lnet/minecraft/commands/CommandSourceStack;)I"
]
},
{
"class": "net/minecraft/server/commands/SummonCommand",
"methods": [
"createEntity(Lnet/minecraft/commands/CommandSourceStack;Lnet/minecraft/core/Holder$Reference;Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/nbt/CompoundTag;Z)Lnet/minecraft/world/entity/Entity;"
]
},
{
"class": "net/minecraft/world/entity/EntityType",
"methods": [
"create(Lnet/minecraft/server/level/ServerLevel;Ljava/util/function/Consumer;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/entity/EntitySpawnReason;ZZ)Lnet/minecraft/world/entity/Entity;"
]
},
{
"class": "net/minecraft/world/entity/ai/village/VillageSiege",
"methods": [
"trySpawn(Lnet/minecraft/server/level/ServerLevel;)V"
]
},
{
"class": "net/minecraft/world/entity/animal/frog/Tadpole",
"methods": [
]
},
{
"class": "net/minecraft/world/entity/animal/horse/SkeletonTrapGoal",
"methods": [
"createHorse(Lnet/minecraft/world/DifficultyInstance;)Lnet/minecraft/world/entity/animal/horse/AbstractHorse;",
"createSkeleton(Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/animal/horse/AbstractHorse;)Lnet/minecraft/world/entity/monster/Skeleton;"
]
},
{
"class": "net/minecraft/world/entity/monster/Evoker$EvokerSummonSpellGoal",
"methods": [
"performSpellCasting()V"
]
},
{
"class": "net/minecraft/world/entity/monster/Spider",
"methods": [
"finalizeSpawn(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;"
]
},
{
"class": "net/minecraft/world/entity/monster/Strider",
"methods": [
"spawnJockey(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/Mob;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;"
]
},
{
"class": "net/minecraft/world/entity/monster/Zombie",
"methods": [
"hurtServer(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/damagesource/DamageSource;F)Z",
"finalizeSpawn(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;"
]
},
{
"class": "net/minecraft/world/entity/monster/ZombieVillager",
"methods": [
]
},
{
"class": "net/minecraft/world/entity/npc/CatSpawner",
"methods": [
"spawnCat(Lnet/minecraft/core/BlockPos;Lnet/minecraft/server/level/ServerLevel;)I"
]
},
{
"class": "net/minecraft/world/entity/npc/Villager",
"methods": [
"getBreedOffspring(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/AgeableMob;)Lnet/minecraft/world/entity/npc/Villager;"
]
},
{
"class": "net/minecraft/world/entity/raid/Raid",
"methods": [
"joinRaid(ILnet/minecraft/world/entity/raid/Raider;Lnet/minecraft/core/BlockPos;Z)V"
]
},
{
"class": "net/minecraft/world/level/NaturalSpawner",
"methods": [
"spawnCategoryForPosition(Lnet/minecraft/world/entity/MobCategory;Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/level/chunk/ChunkAccess;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/NaturalSpawner$SpawnPredicate;Lnet/minecraft/world/level/NaturalSpawner$AfterSpawnCallback;)V",
"spawnMobsForChunkGeneration(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/core/Holder;Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/util/RandomSource;)V"
]
},
{
"class": "net/minecraft/world/level/block/entity/trialspawner/TrialSpawner",
"methods": [
"spawnMob(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/core/BlockPos;)Ljava/util/Optional;"
]
},
{
"class": "net/minecraft/world/level/levelgen/PatrolSpawner",
"methods": [
"spawnPatrolMember(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/core/BlockPos;Lnet/minecraft/util/RandomSource;Z)Z"
]
},
{
"class": "net/minecraft/world/level/levelgen/PhantomSpawner",
"methods": [
"tick(Lnet/minecraft/server/level/ServerLevel;ZZ)I"
]
},
{
"class": "net/minecraft/world/level/levelgen/structure/structures/OceanMonumentPieces$OceanMonumentPiece",
"methods": [
"spawnElder(Lnet/minecraft/world/level/WorldGenLevel;Lnet/minecraft/world/level/levelgen/structure/BoundingBox;III)V"
]
},
{
"class": "net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces$OceanRuinPiece",
"methods": [
"handleDataMarker(Ljava/lang/String;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/util/RandomSource;Lnet/minecraft/world/level/levelgen/structure/BoundingBox;)V"
]
},
{
"class": "net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece",
"methods": [
"postProcess(Lnet/minecraft/world/level/WorldGenLevel;Lnet/minecraft/world/level/StructureManager;Lnet/minecraft/world/level/chunk/ChunkGenerator;Lnet/minecraft/util/RandomSource;Lnet/minecraft/world/level/levelgen/structure/BoundingBox;Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/core/BlockPos;)V",
"spawnCat(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/level/levelgen/structure/BoundingBox;)V"
]
},
{
"class": "net/minecraft/world/level/levelgen/structure/structures/WoodlandMansionPieces$WoodlandMansionPiece",
"methods": [
"handleDataMarker(Ljava/lang/String;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/util/RandomSource;Lnet/minecraft/world/level/levelgen/structure/BoundingBox;)V"
]
},
{
"class": "net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate",
"methods": [
]
}
]
@@ -1,77 +1,114 @@
var ASMAPI = Java.type('net.minecraftforge.coremod.api.ASMAPI');
var Opcodes = Java.type('org.objectweb.asm.Opcodes');
var Handle = Java.type('org.objectweb.asm.Handle');
var MethodInsnNode = Java.type('org.objectweb.asm.tree.MethodInsnNode');
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
function finalizeSpawnNode(node){
return new MethodInsnNode(
Opcodes.INVOKESTATIC,
"net/minecraftforge/event/ForgeEventFactory",
"onFinalizeSpawn",
"(Lnet/minecraft/world/entity/Mob;Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;",
false);
}
'use strict';
function contains(list, target) {
for(var i = 0; i < list.length; i++) {
if(list[i] == target) return true;
}
return false;
}
const ASMAPI = Java.type('net.minecraftforge.coremod.api.ASMAPI');
const Opcodes = Java.type('org.objectweb.asm.Opcodes');
function search(className, node, replacements) {
for(var i = 0; i < replacements.length; i++){
var r = replacements[i];
if(contains(r.targets, className) && node.getOpcode() == r.opcode && node.name == r.name && node.desc == r.desc) {
return r;
// we can't run ASMAPI.loadData in the global context, so we do it here
// this function is called inside of initializeCoreMod
const replacements = [];
function initReplacements() {
ASMAPI.log('DEBUG', 'Gathering Forge method redirector replacements');
replacements.push({
// finalizeSpawn redirection to ForgeEventFactory.onFinalizeSpawn
'type': ASMAPI.MethodType.VIRTUAL,
'name': 'finalizeSpawn',
'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;',
'targets': ASMAPI.loadData('coremods/finalize_spawn_targets.json'),
'factory': function(insn) {
return ASMAPI.buildMethodCall(
ASMAPI.MethodType.STATIC,
'net/minecraftforge/event/ForgeEventFactory',
'onFinalizeSpawn',
'(Lnet/minecraft/world/entity/Mob;Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;');
}
}
return null;
});
}
var replacements = [
{
'opcode': Opcodes.INVOKEVIRTUAL,
'name': ASMAPI.mapMethod('m_6518_'),
'desc': '(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/MobSpawnType;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;',
'targets': 'coremods/finalize_spawn_targets.json',
'factory': finalizeSpawnNode
}
];
function initializeCoreMod() {
var mergedTargets = [];
for(var i = 0; i < replacements.length; i++){
var r = replacements[i];
r.targets = ASMAPI.loadData(r.targets);
for(var k = 0; k < r.targets.length; k++){
mergedTargets.push(r.targets[k]);
}
}
initReplacements();
return {
'forge_method_redirector': {
'target': {
'type': 'CLASS',
'names': function(listofclasses) {
return mergedTargets
}
'names': getTargets
},
'transformer': function(classNode) {
var methods = classNode.methods;
var count = 0;
for(var i = 0; i < methods.size(); i++){
var instr = methods.get(i).instructions;
for(var ix = 0; ix < instr.size(); ix++){
var node = instr.get(ix);
var temp = search(classNode.name, node, replacements);
if (temp != null) {
instr.set(node, temp.factory(node));
}
}
'transformer': applyMethodRedirects
}
}
}
function getTargets(classes) {
const mergedTargets = [];
for (let replacement of replacements) {
for (let target of replacement.targets) {
mergedTargets.push(target.class);
}
}
return mergedTargets;
}
function applyMethodRedirects(clazz) {
for (let replacement of replacements) {
for (let methodString of getClassTargetMethods(clazz, replacement)) {
// cut up the string since we've put both the name and desc as the target
const splitPos = methodString.indexOf('(');
const methodName = methodString.substring(0, splitPos);
const methodDesc = methodString.substring(splitPos);
const method = ASMAPI.findMethodNode(clazz, methodName, methodDesc);
// if we can't find the method, get out now!
if (method === null) {
ASMAPI.log('ERROR', 'Failed to redirect method call for {}! Method {} not found in class {}! This is a Forge bug, and is likely due to a Minecraft update changing something.', replacement.name, methodString, clazz.name);
continue;
}
for (let insn of method.instructions) {
if (shouldReplace(insn, replacement)) {
const redirection = replacement.factory(insn);
ASMAPI.log('DEBUG', 'Redirecting method call {}{} to {}{} inside of {}.{}', insn.name, insn.desc, redirection.name, redirection.desc, clazz.name, method.name);
ASMAPI.insertInsn(method, insn, redirection, ASMAPI.InsertMode.REMOVE_ORIGINAL);
}
return classNode;
}
}
}
}
return clazz;
}
/* HELPER FUNCTIONS FOR TARGET SEARCHING */
function shouldReplace(insn, replacement) {
return insn.getOpcode() === replacement.type.toOpcode()
&& insn.name === replacement.name
&& insn.desc === replacement.desc;
}
function getClassTargetMethods(clazz, replacement) {
for (let t of replacement.targets) {
if (t.class === clazz.name) {
const targets = [];
// declared methods
for (let method of t.methods) {
targets.push(method);
}
// synthetic methods
for (let method of clazz.methods) {
if ((method.access & Opcodes.ACC_SYNTHETIC) != 0) {
targets.push(method.name + method.desc);
}
}
return targets;
}
}
}
@@ -4,7 +4,7 @@
{
"condition": "minecraft:match_tool",
"predicate": {
"items": "minecraft:shears"
"items": "#c:tools/shear"
}
},
{
@@ -0,0 +1,7 @@
{
"variants": {
"": {
"model": "model_render_type:block/old_leaves"
}
}
}
@@ -0,0 +1,7 @@
{
"parent": "minecraft:block/leaves",
"render_type": "minecraft:cutout_mipped",
"textures": {
"all": "minecraft:block/oak_leaves"
}
}
@@ -0,0 +1,3 @@
{
"parent": "model_render_type:block/old_leaves"
}
@@ -0,0 +1,124 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.debug.client;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.ItemBlockRenderTypes;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.core.registries.Registries;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.LeavesBlock;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.material.MapColor;
import net.minecraft.world.level.material.PushReaction;
import net.minecraftforge.client.model.data.ModelData;
import net.minecraftforge.client.model.generators.BlockStateProvider;
import net.minecraftforge.client.model.generators.ItemModelProvider;
import net.minecraftforge.data.event.GatherDataEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.gametest.GameTestHolder;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.RegistryObject;
import net.minecraftforge.test.BaseTestMod;
@Mod(ModelRenderLayerTest.MODID)
@GameTestHolder("forge." + ModelRenderLayerTest.MODID)
public class ModelRenderLayerTest extends BaseTestMod {
public static final String MODID = "model_render_type";
private static final DeferredRegister<Block> BLOCKS = DeferredRegister.create(Registries.BLOCK, MODID);
private static final String OLD_LEAVES_NAME = "old_leaves";
private static final BlockBehaviour.StatePredicate FALSE = (state, level, pos) -> false;
public static final RegistryObject<LeavesBlock> OLD_LEAVES = BLOCKS.register(OLD_LEAVES_NAME,
() -> new LeavesBlock(
BlockBehaviour.Properties.of()
.setId(BLOCKS.key(OLD_LEAVES_NAME))
.mapColor(MapColor.PLANT)
.strength(0.2F)
.randomTicks()
.sound(SoundType.GRASS)
.noOcclusion()
.isSuffocating(FALSE)
.isViewBlocking(FALSE)
.ignitedByLava()
.pushReaction(PushReaction.DESTROY)
.isRedstoneConductor(FALSE)
)
);
private static final DeferredRegister<Item> ITEMS = DeferredRegister.create(Registries.ITEM, MODID);
public static final RegistryObject<BlockItem> OLD_LEAVES_ITEM = ITEMS.register(OLD_LEAVES_NAME, () -> new BlockItem(OLD_LEAVES.get(), new Item.Properties().setId(ITEMS.key(OLD_LEAVES_NAME))));
public ModelRenderLayerTest(FMLJavaModLoadingContext context) {
super(context);
testItem(lookup -> new ItemStack(OLD_LEAVES_ITEM.get()));
}
@SubscribeEvent
public void runData(GatherDataEvent event) {
event.getGenerator().addProvider(event.includeClient(), new BlockStates(event));
event.getGenerator().addProvider(event.includeClient(), new ItemModels(event));
}
@GameTest(template = "forge:empty3x3x3")
public static void old_leaves_render_type(GameTestHelper helper) {
var manager = Minecraft.getInstance().getModelManager();
var state = OLD_LEAVES.get().defaultBlockState();
var model = manager.getBlockModelShaper().getBlockModel(state);
var random = helper.getLevel().random;
boolean originalRenderState = ItemBlockRenderTypes.isFancy();
helper.addCleanup(passed -> ItemBlockRenderTypes.setFancy(originalRenderState));
ItemBlockRenderTypes.setFancy(true);
var layer = model.getRenderTypes(state, random, ModelData.EMPTY);
helper.assertTrue(layer.contains(RenderType.cutoutMipped()), "Block model does not contain the correct render type for fancy graphics. Expected: cutout_mipped");
ItemBlockRenderTypes.setFancy(false);
layer = model.getRenderTypes(state, random, ModelData.EMPTY);
helper.assertTrue(layer.contains(RenderType.solid()), "Block model does not contain the correct render type for fast graphics. Expected: solid");
helper.succeed();
}
public static class BlockStates extends BlockStateProvider {
public BlockStates(GatherDataEvent event) {
super(event.getGenerator().getPackOutput(), MODID, event.getExistingFileHelper());
}
@Override
protected void registerStatesAndModels() {
this.simpleBlock(
OLD_LEAVES.get(),
this.models()
.leaves(OLD_LEAVES_NAME, blockTexture(Blocks.OAK_LEAVES))
// only define the single render type. this test checks if "solid" is implicitly the fast graphics render type
.renderType("cutout_mipped")
);
}
}
private static class ItemModels extends ItemModelProvider {
public ItemModels(GatherDataEvent event) {
super(event.getGenerator().getPackOutput(), MODID, event.getExistingFileHelper());
}
@Override
protected void registerModels() {
this.withExistingParent(OLD_LEAVES_NAME, OLD_LEAVES.getKey().location().withPrefix(BLOCK_FOLDER + '/'));
}
}
}
@@ -0,0 +1,166 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.debug.gameplay.item;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.minecraft.core.Direction;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.core.registries.Registries;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.ExtraCodecs;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.EnergyStorage;
import net.minecraftforge.energy.IEnergyStorage;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.gametest.GameTestHolder;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.RegistryObject;
import net.minecraftforge.test.BaseTestMod;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
@GameTestHolder("forge." + ItemCapabilityTest.MOD_ID)
@Mod(ItemCapabilityTest.MOD_ID)
public class ItemCapabilityTest extends BaseTestMod {
public static final String MOD_ID = "item_caps";
public static final DeferredRegister<DataComponentType<?>> DATA_COMPONENTS = DeferredRegister.create(Registries.DATA_COMPONENT_TYPE, MOD_ID);
public static final RegistryObject<DataComponentType<Integer>> STORAGE = DATA_COMPONENTS.register("energy_storage", () ->
DataComponentType.<Integer>builder()
.persistent(ExtraCodecs.POSITIVE_INT)
.networkSynchronized(ByteBufCodecs.VAR_INT)
.build()
);
public ItemCapabilityTest(FMLJavaModLoadingContext context) {
super(context);
}
@SubscribeEvent
public static void onEvent(AttachCapabilitiesEvent<ItemStack> event) {
if (event.getObject().getItem() == Items.COPPER_INGOT) {
event.addCapability(
ResourceLocation.fromNamespaceAndPath("forge", "test"),
new MyProvider(event.getObject())
);
}
}
@GameTest(template = "forge:empty3x3x3")
public static void testItemCap(GameTestHelper helper) {
helper.registerEventListener(ItemCapabilityTest.class);
ItemStack stack = new ItemStack(Items.COPPER_INGOT);
AtomicReference<IEnergyStorage> storageAtomicReference = new AtomicReference<>();
stack.getCapability(ForgeCapabilities.ENERGY).ifPresent(storage -> {
storage.receiveEnergy(1, false);
storageAtomicReference.set(storage);
});
helper.assertTrue(
storageAtomicReference.get() != null,
"Unable to find ForgeCapabilities.ENERGY Capability"
);
var registry = RegistryFriendlyByteBuf.decorator(helper.getLevel().registryAccess()).apply(new FriendlyByteBuf(Unpooled.buffer()));
ItemStack.STREAM_CODEC.encode(registry, stack);
var stackOverWire = ItemStack.STREAM_CODEC.decode(registry);
AtomicReference<IEnergyStorage> storage = new AtomicReference<>();
stackOverWire.getCapability(ForgeCapabilities.ENERGY).ifPresent(storage::set);
helper.assertValueEqual(
storage.get() == null ? -1 : storage.get().getEnergyStored(),
11,
"Value did not Sync to client"
);
helper.succeed();
}
public static final class MyEnergyStorage extends EnergyStorage {
private final Consumer<EnergyStorage> consumer;
public MyEnergyStorage(int capacity, Consumer<EnergyStorage> consumer) {
super(capacity);
this.consumer = consumer;
}
public MyEnergyStorage(int capacity, int maxTransfer, Consumer<EnergyStorage> consumer) {
super(capacity, maxTransfer);
this.consumer = consumer;
}
public MyEnergyStorage(int capacity, int maxReceive, int maxExtract, Consumer<EnergyStorage> consumer) {
super(capacity, maxReceive, maxExtract);
this.consumer = consumer;
}
public MyEnergyStorage(int capacity, int maxReceive, int maxExtract, int energy, Consumer<EnergyStorage> consumer) {
super(capacity, maxReceive, maxExtract, energy);
this.consumer = consumer;
}
static <T, V> T doAndReturn(T value, V value2, Consumer<V> consumer) {
consumer.accept(value2);
return value;
}
@Override
public int receiveEnergy(int maxReceive, boolean simulate) {
return doAndReturn(super.receiveEnergy(maxReceive, simulate), this, consumer);
}
@Override
public int extractEnergy(int maxExtract, boolean simulate) {
return doAndReturn(super.extractEnergy(maxExtract, simulate), this, consumer);
}
}
public static final class MyProvider implements ICapabilityProvider {
private final ItemStack stack;
private final EnergyStorage storage;
private final LazyOptional<EnergyStorage> storageLazyOptional;
public MyProvider(ItemStack stack) {
this.stack = stack;
this.storage = new MyEnergyStorage(1000, 10, 10, stack.getOrDefault(STORAGE.get(), 10), storage -> {
stack.set(STORAGE.get(), storage.getEnergyStored());
});
this.storageLazyOptional = LazyOptional.of(() -> storage);
}
@Override
public @NotNull <T> LazyOptional<T> getCapability(@NotNull Capability<T> cap, @Nullable Direction side) {
if (cap == ForgeCapabilities.ENERGY) {
return storageLazyOptional.cast();
}
return LazyOptional.empty();
}
}
}
@@ -0,0 +1,151 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.debug.gameplay.item;
import net.minecraft.commands.arguments.EntityAnchorArgument;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.damagesource.DamageTypes;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.ShieldItem;
import net.minecraft.world.level.GameType;
import net.minecraftforge.event.entity.living.LivingEntityUseItemEvent;
import net.minecraftforge.event.entity.player.PlayerDestroyItemEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.gametest.GameTestHolder;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
import net.minecraftforge.test.BaseTestMod;
import org.jetbrains.annotations.Nullable;
import java.util.function.Consumer;
@Mod(PreventItemDamageTest.MOD_ID)
@GameTestHolder("forge." + PreventItemDamageTest.MOD_ID)
public class PreventItemDamageTest extends BaseTestMod {
static final String MOD_ID = "prevent_item_damage";
private static final DeferredRegister<Item> ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MOD_ID);
private static final RegistryObject<FakeShieldItem> FAKE_SHIELD = ITEMS.register("fake_shield", FakeShieldItem::new);
public PreventItemDamageTest(FMLJavaModLoadingContext context) {
super(context);
this.testItem(lookup -> FAKE_SHIELD.get().getDefaultInstance());
}
@GameTest(template = "forge:empty3x3x3")
public static void player_fake_shield_took_modified_damage(GameTestHelper helper) {
helper.makeFloor();
// setup player
var player = helper.makeMockPlayer(GameType.SURVIVAL);
// setup shield
var shield = FAKE_SHIELD.get().getDefaultInstance();
// setup events
var firedLivingEntityUseItem = helper.boolFlag("fired LivingEntityUseItemEvent");
var firedPlayerDestroyItem = helper.boolFlag("fired PlayerDestroyItemEvent");
helper.<LivingEntityUseItemEvent.Start>addEventListener(event -> {
if (event.getEntity() != player) return;
if (event.getItem() != shield)
helper.fail("Player is using an item, but it's not the fake shield! Check the game test impl.");
// Artificially pass 5 seconds from start of the shield
// This is because the first 5 ticks, the player is still vulnerable
firedLivingEntityUseItem.set(true);
event.setDuration(event.getDuration() - 100);
});
helper.<PlayerDestroyItemEvent>addEventListener(event -> {
if (event.getEntity() != player) return;
if (event.getOriginal() != shield)
helper.fail("Player destroyed an item, but it's not the fake shield! Check the game test impl.");
firedPlayerDestroyItem.set(true);
});
// start using shield
player.setItemInHand(InteractionHand.MAIN_HAND, shield);
player.startUsingItem(InteractionHand.MAIN_HAND);
int initialDamage = shield.getDamageValue();
// setup enemy
var enemy = helper.spawnWithNoFreeWill(EntityType.HUSK, new BlockPos(2, 0, 2));
player.lookAt(EntityAnchorArgument.Anchor.EYES, enemy.position());
// hit the player
var attack = helper.registryLookup(Registries.DAMAGE_TYPE).getOrThrow(DamageTypes.MOB_ATTACK);
var damage = new DamageSource(attack, enemy) {
@Override
public boolean scalesWithDifficulty() {
return false;
}
};
player.hurtServer(helper.getLevel(), damage, 5.0F);
// ok, run the tests
firedLivingEntityUseItem.assertEquals(true);
firedPlayerDestroyItem.assertEquals(true);
helper.assertValueEqual(initialDamage + 1, shield.getDamageValue(), "shield damage value", "Fake shield did not take precisely 1 damage! Check IForgeItem#damageItem.");
helper.assertValueEqual(player.getItemInHand(InteractionHand.MAIN_HAND), shield, "player shield", "Fake shield was removed from player's hand! Check Player#hurtCurrentlyUsedShield.");
helper.assertValueNotEqual(player.getUseItem(), shield, "player use item", "Player should not be using the shield! The onBreak callback was never invoked. Check FakeShieldItem or IForgeItem#damageItem.");
helper.succeed();
}
@GameTest(template = "forge:empty3x3x3")
public static void fake_shield_damage_item_impl(GameTestHelper helper) {
helper.makeFloor();
// setup player
var player = helper.makeMockServerPlayer();
// setup shield
var shield = FAKE_SHIELD.get().getDefaultInstance();
int initialDamage = shield.getDamageValue();
// test hurt and break
var damaged = helper.<Item>flag("damaged shield");
shield.hurtAndBreak(1, helper.getLevel(), player, damaged::set);
damaged.assertEquals(FAKE_SHIELD.get(), "Fake shield was not damaged! Check IForgeItem#damageItem.");
helper.assertValueEqual(initialDamage + 1, shield.getDamageValue(), "shield damage value", "Fake shield did not take precisely 1 damage! Check IForgeItem#damageItem.");
// test hurt without breaking
initialDamage = shield.getDamageValue();
shield.hurtWithoutBreaking(1, player);
helper.assertValueEqual(initialDamage, shield.getDamageValue(), "shield damage value", "Fake shield took damage even though hurtWithoutBreak test should set damage taken to 0! Check FakeShieldItem or IForgeItem#damageItem.");
// finished
helper.succeed();
}
private static final class FakeShieldItem extends ShieldItem {
public FakeShieldItem() {
super(new Item.Properties().setId(ITEMS.key("fake_shield")).durability(10));
}
@Override
public int damageItem(ItemStack stack, int damage, ServerLevel level, @Nullable ServerPlayer player, boolean canBreak, Consumer<Item> onBroken) {
if (canBreak) {
onBroken.accept(this);
return 1;
}
return 0;
}
}
}
@@ -0,0 +1,88 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.debug.gameplay.item;
import net.minecraft.Util;
import net.minecraft.commands.arguments.EntityAnchorArgument;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.damagesource.DamageTypes;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.GameType;
import net.minecraftforge.event.entity.living.LivingEntityUseItemEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.gametest.GameTestHolder;
import net.minecraftforge.test.BaseTestMod;
import java.util.function.Function;
@Mod(ShieldDisablingTest.MOD_ID)
@GameTestHolder("forge." + ShieldDisablingTest.MOD_ID)
public final class ShieldDisablingTest extends BaseTestMod {
public static final String MOD_ID = "shield_disabling";
public ShieldDisablingTest(FMLJavaModLoadingContext context) {
super(context);
}
@GameTest(template = "forge:empty3x3x3")
public static void player_shield_disabled_by_axe(GameTestHelper helper) {
player_shield_disabled_common(helper, h -> Util.make(
h.spawnWithNoFreeWill(EntityType.HUSK, new BlockPos(2, 0, 2)),
enemy -> enemy.setItemInHand(InteractionHand.MAIN_HAND, new ItemStack(Items.IRON_AXE))
));
}
@GameTest(template = "forge:empty3x3x3")
public static void player_shield_disabled_by_warden(GameTestHelper helper) {
player_shield_disabled_common(helper, h -> h.spawnWithNoFreeWill(EntityType.WARDEN, new BlockPos(2, 0, 2)));
}
private static void player_shield_disabled_common(GameTestHelper helper, Function<GameTestHelper, LivingEntity> enemyGetter) {
helper.makeFloor();
// setup player
var player = helper.makeMockPlayer(GameType.SURVIVAL);
helper.<LivingEntityUseItemEvent.Start>addEventListener(event -> {
// Artificially pass 5 seconds from start of the shield
// This is because the first 5 ticks, the player is still vulnerable
if (event.getEntity() == player) {
event.setDuration(event.getDuration() - 100);
}
});
// start using shield
var shield = Items.SHIELD.getDefaultInstance();
player.setItemInHand(InteractionHand.MAIN_HAND, shield);
player.startUsingItem(InteractionHand.MAIN_HAND);
// setup enemy
var enemy = enemyGetter.apply(helper);
player.lookAt(EntityAnchorArgument.Anchor.EYES, enemy.position());
// hit the player
var attack = helper.registryLookup(Registries.DAMAGE_TYPE).getOrThrow(DamageTypes.MOB_ATTACK);
var damage = new DamageSource(attack, enemy) {
@Override
public boolean scalesWithDifficulty() {
return false;
}
};
player.hurtServer(helper.getLevel(), damage, 5.0F);
// shield on cooldown?
helper.assertTrue(player.getCooldowns().isOnCooldown(shield), "shield should be on cooldown");
helper.succeed();
}
}
@@ -0,0 +1,73 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.debug.gameplay.level;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.server.level.ChunkLevel;
import net.minecraft.server.level.FullChunkStatus;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.TicketType;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.gametest.GameTestHolder;
import net.minecraftforge.test.BaseTestMod;
import org.jetbrains.annotations.Nullable;
@GameTestHolder("forge." + ForcedChunkLoadingTest.MOD_ID)
@Mod(ForcedChunkLoadingTest.MOD_ID)
public class ForcedChunkLoadingTest extends BaseTestMod {
static final String MOD_ID = "forced_chunk_loading";
public ForcedChunkLoadingTest(FMLJavaModLoadingContext context) {
super(context);
}
private static final int MAX_CHUNK_LOCATION_ATTEMPTS = 5;
@GameTest(template = "forge:empty3x3x3")
public static void force_far_away_chunk(GameTestHelper helper) {
var random = RandomSource.create();
var level = helper.getLevel();
var chunkSource = level.getChunkSource();
// try to find a far away chunk that is not already ticking
int attempts;
var origin = helper.absolutePos(BlockPos.ZERO);
ChunkAccess chunk = null;
for (attempts = 0; attempts < MAX_CHUNK_LOCATION_ATTEMPTS && chunk == null; attempts++) {
int x = random.nextInt(1, 10) * 10000;
int z = random.nextInt(1, 10) * 10000;
chunk = level.getChunk(origin.offset(x, 0, z));
// If the chunk is already ticking, we can't force it
chunk = chunkSource.isPositionTicking(chunk.getPos().toLong()) ? null : chunk;
}
if (chunk == null) {
helper.fail("Failed to find a far away chunk that is not already ticking after " + MAX_CHUNK_LOCATION_ATTEMPTS + " attempts");
return;
} else if (attempts > 1) {
helper.say("WARNING: Finding a far away chunk took " + attempts + " attempts.", ChatFormatting.YELLOW);
}
var pos = chunk.getPos();
helper.say("Attempting to force far away chunk: " + pos, ChatFormatting.YELLOW);
chunkSource.chunkMap.getDistanceManager().addRegionTicket(TicketType.FORCED, pos, ChunkLevel.byStatus(FullChunkStatus.FULL), pos, true);
helper.runAfterDelay(20, () -> {
helper.assertTrue(chunkSource.chunkMap.getDistanceManager().shouldForceTicks(pos.toLong()), "Chunk is not ticketed as a forced loaded chunk");
helper.assertTrue(chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(pos.toLong()), "Forced chunk cannot tick entities");
helper.assertTrue(chunkSource.chunkMap.getDistanceManager().inBlockTickingRange(pos.toLong()), "Forced chunk cannot tick blocks");
helper.assertTrue(chunkSource.hasChunk(pos.x, pos.z), "Chunk is not loaded despite being ticketed as a force loaded chunk");
helper.succeed();
});
}
}
@@ -54,6 +54,7 @@ import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import net.minecraft.world.level.storage.loot.predicates.LootItemBlockStatePropertyCondition;
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
import net.minecraft.world.level.storage.loot.predicates.MatchTool;
import net.minecraftforge.common.Tags;
import net.minecraftforge.common.crafting.conditions.IConditionBuilder;
import net.minecraftforge.common.data.DatapackBuiltinEntriesProvider;
import net.minecraftforge.common.data.GlobalLootModifierProvider;
@@ -231,7 +232,8 @@ public class GlobalLootModifiersTest extends BaseTestMod {
// This has no game test because it relies on random number generation and I can't be bothered to try and force that right now.
add("wheat_harvest", new WheatSeedsConverterModifier(
new LootItemCondition[] {
MatchTool.toolMatches(ItemPredicate.Builder.item().of(items, Items.SHEARS)).build(),
// the #c:tools_shear tag is used here to test parsing of tags in LootModiferManager
MatchTool.toolMatches(ItemPredicate.Builder.item().of(items, Tags.Items.TOOLS_SHEAR)).build(),
LootItemBlockStatePropertyCondition.hasBlockStateProperties(Blocks.WHEAT).build()
},
3, Items.WHEAT_SEEDS, Items.WHEAT)
@@ -0,0 +1,110 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.debug.gameplay.redstone;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BarrelBlockEntity;
import net.minecraft.world.level.block.entity.ChestBlockEntity;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.test.BaseTestMod;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.gametest.GameTestHolder;
/**
* Redstone update orders are important in many contraptions as there are pistons and other things that could conflict with each other.
* These tests verify just that, two pistons pushing the same block.
*
* Partial verification/fix for https://github.com/MinecraftForge/MinecraftForge/issues/9973
*/
@Mod(UpdateOrder.MODID)
@GameTestHolder("forge." + UpdateOrder.MODID)
public class UpdateOrder extends BaseTestMod {
public static final String MODID = "update_order";
private static final int PISTON_DELAY = 4;
public UpdateOrder(FMLJavaModLoadingContext context) {
super(context);
}
@GameTest(template = "update_order:update_order", rotationSteps = 0)
public static void south(GameTestHelper helper) {
var chestPos = new BlockPos(0, 1, 0);
helper.assertBlockPresent(Blocks.CHEST, chestPos);
var chest = helper.<ChestBlockEntity>getBlockEntity(chestPos);
chest.setItem(0, new ItemStack(Items.DIRT));
helper.runAfterDelay(PISTON_DELAY, () -> {
var expectedPos = new BlockPos(2, 1, 3);
helper.assertBlockPresent(Blocks.WHITE_WOOL, expectedPos);
helper.succeed();
});
}
@GameTest(template = "update_order:update_order", rotationSteps = 1)
public static void west(GameTestHelper helper) {
var chestPos = new BlockPos(0, 1, 0);
helper.assertBlockPresent(Blocks.CHEST, chestPos);
var chest = helper.<ChestBlockEntity>getBlockEntity(chestPos);
chest.setItem(0, new ItemStack(Items.DIRT));
helper.runAfterDelay(PISTON_DELAY, () -> {
var expectedPos = new BlockPos(2, 1, 3);
helper.assertBlockPresent(Blocks.WHITE_WOOL, expectedPos);
helper.succeed();
});
}
@GameTest(template = "update_order:update_order", rotationSteps = 2)
public static void north(GameTestHelper helper) {
var chestPos = new BlockPos(0, 1, 0);
helper.assertBlockPresent(Blocks.CHEST, chestPos);
var chest = helper.<ChestBlockEntity>getBlockEntity(chestPos);
chest.setItem(0, new ItemStack(Items.DIRT));
helper.runAfterDelay(4, () -> {
var expectedPos = new BlockPos(3, 1, 2);
helper.assertBlockPresent(Blocks.WHITE_WOOL, expectedPos);
helper.succeed();
});
}
@GameTest(template = "update_order:update_order", rotationSteps = 3)
public static void east(GameTestHelper helper) {
var chestPos = new BlockPos(0, 1, 0);
helper.assertBlockPresent(Blocks.CHEST, chestPos);
var chest = helper.<ChestBlockEntity>getBlockEntity(chestPos);
chest.setItem(0, new ItemStack(Items.DIRT));
helper.runAfterDelay(PISTON_DELAY, () -> {
var expectedPos = new BlockPos(2, 1, 3);
helper.assertBlockPresent(Blocks.WHITE_WOOL, expectedPos);
helper.succeed();
});
}
@GameTest(template = "update_order:upward_update")
public static void up(GameTestHelper helper) {
var chestPos = new BlockPos(0, 1, 2);
var expectedPos = new BlockPos(2, 1, 0);
var unexpectedPos = new BlockPos(0, 4, 0);
helper.assertBlockPresent(Blocks.BARREL, chestPos);
helper.assertBlockPresent(Blocks.AIR, expectedPos);
helper.assertBlockPresent(Blocks.AIR, unexpectedPos);
var chest = helper.<BarrelBlockEntity>getBlockEntity(chestPos);
chest.setItem(0, new ItemStack(Items.DIRT));
helper.runAfterDelay(10, () -> { // There are a lot of things happening, give it a few ticks
helper.assertBlockNotPresent(Blocks.WHITE_WOOL, unexpectedPos);
helper.assertBlockPresent(Blocks.WHITE_WOOL, expectedPos);
helper.succeed();
});
}
}
@@ -0,0 +1,81 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.debug.gameplay.villager;
import net.minecraft.core.Holder;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.npc.Villager;
import net.minecraft.world.entity.npc.VillagerTrades;
import net.minecraft.world.entity.npc.VillagerType;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Biomes;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.gametest.GameTestHolder;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.RegistryObject;
import net.minecraftforge.test.BaseTestMod;
import java.util.Map;
@GameTestHolder("forge." + VillagerTypeTestMod.MOD_ID)
@Mod(VillagerTypeTestMod.MOD_ID)
public class VillagerTypeTestMod extends BaseTestMod {
public static final String MOD_ID = "villager_type_test";
private static final DeferredRegister<VillagerType> VILLAGER_TYPES = DeferredRegister.create(Registries.VILLAGER_TYPE, MOD_ID);
private static final RegistryObject<VillagerType> TEST_VILLAGER_TYPE = VILLAGER_TYPES.register("test_villager_type", () -> new VillagerType("test_villager_type"));
public VillagerTypeTestMod(FMLJavaModLoadingContext context) {
super(context);
context.getModEventBus().addListener(this::onCommonSetup);
}
private void onCommonSetup(final FMLCommonSetupEvent event) {
event.enqueueWork(() -> VillagerType.registerBiomeType(Biomes.PLAINS, TEST_VILLAGER_TYPE.get()));
}
@GameTest(template = "forge:empty3x3x3")
public static void biome_type(GameTestHelper helper) {
RegistryAccess access = helper.getLevel().registryAccess();
VillagerType type = access.lookupOrThrow(Registries.VILLAGER_TYPE).getValue(TEST_VILLAGER_TYPE.getId());
if (type == null)
helper.fail("Failed to find test_villager_type");
helper.assertValueEqual(type, TEST_VILLAGER_TYPE.get(), "Loaded entry does not contain expected value");
Holder<Biome> biome = access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.PLAINS);
helper.assertValueEqual(VillagerType.byBiome(biome), TEST_VILLAGER_TYPE.get(), "VillagerType.byBiome did not return the expected value");
helper.succeed();
}
/** Test verifies NPE not thrown when looking up a villager type in the trade that doesn't contain that type.*/
@GameTest(template = "forge:empty3x3x3")
public static void emeralds_for_villager_type(GameTestHelper helper) {
var trade = new VillagerTrades.EmeraldsForVillagerTypeItem(1, 12, 30, Map.of(TEST_VILLAGER_TYPE.get(), Items.DIRT));
var rnd = helper.getLevel().getRandom();
// Should be a successful trade for dirt for our test villager
var test = new Villager(EntityType.VILLAGER, helper.getLevel(), TEST_VILLAGER_TYPE.get());
var test_offer = trade.getOffer(test, rnd);
helper.assertFalse(test_offer == null, "Failed to retreive trade value for test profession");
helper.assertValueEqual(test_offer.getItemCostA().itemStack().getItem(), Items.DIRT, "Offer did not return the expected item");
var plains = new Villager(EntityType.VILLAGER, helper.getLevel(), VillagerType.PLAINS);
// This will NPE on unpatched code, we need to test that it returns null correctly
var plains_offer = trade.getOffer(plains, rnd);
helper.assertTrue(plains_offer == null, "Offer should not be available for a plains villager");
helper.succeed();
}
}