From 2a8115ec8b83d300887d373312f452531eb1020c Mon Sep 17 00:00:00 2001 From: Kayos Date: Sat, 7 Mar 2026 09:41:38 -0800 Subject: [PATCH] neoforge-1.21.1: runtime fixes and build improvements - Fix ServerTickEvent: register listener for ServerTickEvent.Post instead of abstract ServerTickEvent base class (NeoForge 21.x requirement) - Fix null BlockGetter: replace null with EmptyBlockGetter.INSTANCE in isSolidRender() and propagatesSkylightDown() calls (1.21+ actually uses param) - Fix chunk iteration: replace direct visibleChunkMap field access with getChunks() iteration and getChunkToSend() with getLatestChunk() (1.21.1 API) - Build: fix shadowJar config to properly bundle DynmapCore into fat jar (was producing 80KB hollow jar missing all core classes) - Build: cap Gradle daemon and forked javac heap to prevent OOM on large hosts - Tested: server starts cleanly, Dynmap web UI accessible, maps rendered --- build.gradle | 24 ++++++++++++++++--- gradle.properties | 2 +- .../dynmap/neoforge_1_21_1/DynmapPlugin.java | 11 ++++----- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/build.gradle b/build.gradle index dfffeee6..eb25d5fb 100644 --- a/build.gradle +++ b/build.gradle @@ -30,10 +30,15 @@ minecraft { } } +configurations { + shadow + implementation.extendsFrom shadow +} + dependencies { implementation 'net.neoforged:neoforge:21.1.219' - // DynmapCore and DynmapCoreAPI as local jars (build these separately) - implementation fileTree(dir: 'libs', include: ['*.jar']) + // DynmapCore bundled into the fat jar via shadow config + shadow fileTree(dir: 'libs', include: ['*.jar']) } processResources { @@ -50,7 +55,13 @@ shadowJar { mergeServiceFiles() archiveBaseName = 'Dynmap' + archiveVersion = project.version archiveClassifier = 'neoforge-1.21.1' + + // Exclude signature files that cause security exceptions + exclude 'META-INF/*.RSA' + exclude 'META-INF/*.SF' + exclude 'META-INF/*.DSA' } tasks.jar { @@ -66,4 +77,11 @@ tasks.jar { } } -build.dependsOn(jar) +// Build the fat jar, not the thin one +build.dependsOn(shadowJar) + +// Cap memory on forked javac process (prevents OOM on large NeoForge compile) +tasks.withType(JavaCompile).configureEach { + options.fork = true + options.forkOptions.memoryMaximumSize = '3g' +} diff --git a/gradle.properties b/gradle.properties index 6b6d6983..a9249bb9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx4G +org.gradle.jvmargs=-Xmx2G org.gradle.daemon=false org.gradle.parallel=false # GitHub user and password (workaround for OSSRH beeing sunset - https://central.sonatype.org/pages/ossrh-eol/ diff --git a/src/main/java/org/dynmap/neoforge_1_21_1/DynmapPlugin.java b/src/main/java/org/dynmap/neoforge_1_21_1/DynmapPlugin.java index e34c7b26..b54648a0 100644 --- a/src/main/java/org/dynmap/neoforge_1_21_1/DynmapPlugin.java +++ b/src/main/java/org/dynmap/neoforge_1_21_1/DynmapPlugin.java @@ -245,7 +245,7 @@ public class DynmapPlugin } int lightAtten = 15; try { // Workaround for mods with broken block state logic... - lightAtten = bs.isSolidRender(null, BlockPos.ZERO) ? 15 : (bs.propagatesSkylightDown(null, BlockPos.ZERO) ? 0 : 1); + lightAtten = bs.isSolidRender(net.minecraft.world.level.EmptyBlockGetter.INSTANCE, BlockPos.ZERO) ? 15 : (bs.propagatesSkylightDown(net.minecraft.world.level.EmptyBlockGetter.INSTANCE, BlockPos.ZERO) ? 0 : 1); } catch (Exception x) { Log.warning(String.format("Exception while checking lighting data for block state: %s[%s]", bn, statename)); Log.verboseinfo("Exception: " + x.toString()); @@ -913,7 +913,7 @@ public class DynmapPlugin } @SubscribeEvent - public void tickEvent(ServerTickEvent event) { + public void tickEvent(ServerTickEvent.Post event) { cur_tick_starttime = System.nanoTime(); long elapsed = cur_tick_starttime - lasttick; lasttick = cur_tick_starttime; @@ -1850,13 +1850,10 @@ public class DynmapPlugin for (ServerLevel world : server.getAllLevels()) { ForgeWorld fw = getWorld(world); if (fw == null) continue; - Long2ObjectLinkedOpenHashMap chunks = world.getChunkSource().chunkMap.visibleChunkMap; - for (Entry k : chunks.long2ObjectEntrySet()) { - long key = k.getKey().longValue(); - ChunkHolder ch = k.getValue(); + for (ChunkHolder ch : world.getChunkSource().chunkMap.visibleChunkMap.values()) { ChunkAccess c = null; try { - c = ch.getChunkToSend(); + c = ch.getLatestChunk(); } catch (Exception x) { } if (c == null) continue; ChunkStatus cs = c.getPersistedStatus();