diff --git a/build.gradle b/build.gradle
index 60151858..0a774af7 100644
--- a/build.gradle
+++ b/build.gradle
@@ -135,6 +135,9 @@ dependencies {
// ModKit DEV ONLY
implementation('com.github.thedarkcolour:Modkit:e7c1881681')
+ // Core mod
+ jarJar(project(':coremod'))
+
// Oculus + Embeddium OPTIONAL
compileOnly('maven.modrinth:oculus:1.20.1-1.6.9')
compileOnly('maven.modrinth:embeddium:0.3.9+mc1.20.4')
diff --git a/coremod/build.gradle b/coremod/build.gradle
new file mode 100644
index 00000000..c10a99c3
--- /dev/null
+++ b/coremod/build.gradle
@@ -0,0 +1,28 @@
+plugins {
+ id 'java'
+}
+
+java.toolchain.languageVersion = JavaLanguageVersion.of(21)
+
+jar {
+ manifest {
+ attributes(["FMLModType": "LIBRARY"])
+ }
+}
+
+repositories {
+ maven {
+ url = "https://libraries.minecraft.net"
+ metadataSources{
+ mavenPom()
+ }
+ }
+ maven {
+ url = "https://maven.neoforged.net/releases"
+ }
+}
+
+dependencies {
+ compileOnly 'net.neoforged.fancymodloader:loader:4.0.6'
+ compileOnly 'org.jetbrains:annotations:24.1.0'
+}
\ No newline at end of file
diff --git a/coremod/src/main/java/thedarkcolour/exdeorum/coremod/ASMTransformer.java b/coremod/src/main/java/thedarkcolour/exdeorum/coremod/ASMTransformer.java
new file mode 100644
index 00000000..4f15702e
--- /dev/null
+++ b/coremod/src/main/java/thedarkcolour/exdeorum/coremod/ASMTransformer.java
@@ -0,0 +1,162 @@
+/*
+ * Ex Deorum
+ * Copyright (c) 2024 thedarkcolour
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package thedarkcolour.exdeorum.coremod;
+
+import cpw.mods.modlauncher.api.ITransformer;
+import cpw.mods.modlauncher.api.ITransformerVotingContext;
+import cpw.mods.modlauncher.api.TargetType;
+import cpw.mods.modlauncher.api.TransformerVoteResult;
+import net.neoforged.coremod.api.ASMAPI;
+import net.neoforged.neoforgespi.coremod.ICoreMod;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.FieldInsnNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.tree.VarInsnNode;
+
+import java.util.List;
+import java.util.Set;
+
+public class ASMTransformer implements ICoreMod {
+ @Override
+ public Iterable extends ITransformer>> getTransformers() {
+ return List.of(
+ new EndCityStructureTransformer(),
+ new DedicatedServerPropertiesTransformer(),
+ new EndDragonFightTransformer()
+ );
+ }
+
+ private interface MethodTransformer extends ITransformer {
+ @Override
+ default TransformerVoteResult castVote(ITransformerVotingContext context) {
+ return TransformerVoteResult.YES;
+ }
+
+ @Override
+ default TargetType getTargetType() {
+ return TargetType.METHOD;
+ }
+ }
+
+ // inserts a hook into EndCityStructure#findGenerationPoint to fix the position of the city if it is in a void world
+ private static class EndCityStructureTransformer implements MethodTransformer {
+ @Override
+ public Set> targets() {
+ return Set.of(ITransformer.Target.targetMethod(
+ "net.minecraft.world.level.levelgen.structure.structures.EndCityStructure",
+ "findGenerationPoint",
+ "(Lnet/minecraft/world/level/levelgen/structure/Structure$GenerationContext;)Ljava/util/Optional;"
+ ));
+ }
+
+ //
+ @Override
+ public MethodNode transform(MethodNode input, ITransformerVotingContext context) {
+ var insnList = input.instructions;
+
+ for (var i = 0; i < insnList.size(); ++i) {
+ var insn = insnList.get(i);
+
+ // patch before ASTORE 3
+ if (insn.getOpcode() == Opcodes.ASTORE && ((VarInsnNode) insn).var == 3) {
+ insnList.insertBefore(insn, ASMAPI.listOf(
+ new VarInsnNode(Opcodes.ALOAD, 1),
+ new MethodInsnNode(Opcodes.INVOKESTATIC, "thedarkcolour/exdeorum/asm/ASMHooks", "adjustPos", "(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/levelgen/structure/Structure$GenerationContext;)Lnet/minecraft/core/BlockPos;", false)
+ ));
+ ASMAPI.log("INFO", "Successfully patched End City generation for void worlds");
+ return input;
+ }
+ }
+
+ ASMAPI.log("ERROR", "Unable to patch End City generation, void worlds will have no end cities!!!");
+ return input;
+ }
+ }
+
+ // Redirects a field access in the constructor of DedicatedServerProperties from WorldPresets.NORMAL to ASMHooks.overrideDefaultWorldPreset()
+ private static class DedicatedServerPropertiesTransformer implements MethodTransformer {
+ @Override
+ public Set> targets() {
+ return Set.of(ITransformer.Target.targetMethod(
+ "net.minecraft.server.dedicated.DedicatedServerProperties",
+ "",
+ "(Ljava/util/Properties;)V"
+ ));
+ }
+
+ @Override
+ public MethodNode transform(MethodNode input, ITransformerVotingContext context) {
+ var insnList = input.instructions;
+
+ for (var i = 0; i < insnList.size(); ++i) {
+ var insn = insnList.get(i);
+
+ if (insn.getOpcode() == Opcodes.GETSTATIC && (((MethodInsnNode) insn).name.equals("f_226437_") || ((MethodInsnNode) insn).name.equals("NORMAL"))) {
+ var newInsn = new MethodInsnNode(Opcodes.INVOKESTATIC, "thedarkcolour/exdeorum/asm/ASMHooks", "overrideDefaultWorldPreset", "()Lnet/minecraft/resources/ResourceKey;", false);
+ insnList.set(insn, newInsn);
+
+ ASMAPI.log("INFO", "Successfully patched server.properties to use void world type by default");
+ return input;
+ }
+ }
+
+ ASMAPI.log("ERROR", "Unable to patch server.properties, you will have to set \"level-type\" to \"exdeorum:void_world\" manually.");
+ return input;
+ }
+ }
+
+ // Fixes heightmap issues when placing the end portal podium that would only spawn half of the portal
+ // What this patch looks like in code: EndIslandPodium.place(..., this.portalLocation = ASMHooks.prePlaceEndPodium(this.portalLocation))
+ private static class EndDragonFightTransformer implements MethodTransformer {
+ @Override
+ public Set> targets() {
+ return Set.of(ITransformer.Target.targetMethod(
+ "net.minecraft.world.level.dimension.end.EndDragonFight",
+ "spawnExitPortal",
+ "(Z)V"
+ ));
+ }
+
+ @Override
+ public MethodNode transform(MethodNode input, ITransformerVotingContext context) {
+ var insnList = input.instructions;
+
+ // Start at 2 to avoid null getPrevious().getPrevious()
+ for (var i = 2; i < insnList.size(); ++i) {
+ var insn = insnList.get(i);
+
+ if (insn.getOpcode() == Opcodes.ALOAD && ((VarInsnNode) insn).var == 2) {
+ insnList.insertBefore(insn, ASMAPI.listOf(
+ new VarInsnNode(Opcodes.ALOAD, 0),
+ new VarInsnNode(Opcodes.ALOAD, 0),
+ new FieldInsnNode(Opcodes.GETFIELD, "net/minecraft/world/level/dimension/end/EndDragonFight", "portalLocation", "Lnet/minecraft/core/BlockPos;"),
+ new MethodInsnNode(Opcodes.INVOKESTATIC, "thedarkcolour/exdeorum/asm/ASMHooks", "prePlaceEndPodium", "(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/core/BlockPos;", false),
+ new FieldInsnNode(Opcodes.PUTFIELD, "net/minecraft/world/level/dimension/end/EndDragonFight", "portalLocation", "Lnet/minecraft/core/BlockPos;")
+ ));
+ ASMAPI.log("INFO", "Successfully patched end portal.");
+ return input;
+ }
+ }
+
+ ASMAPI.log("ERROR", "Unable to patch End Portal, it will not spawn properly and you will be unable to return to the overworld without cheats.");
+ return input;
+ }
+ }
+}
diff --git a/coremod/src/main/java/thedarkcolour/exdeorum/coremod/package-info.java b/coremod/src/main/java/thedarkcolour/exdeorum/coremod/package-info.java
new file mode 100644
index 00000000..1bcb754f
--- /dev/null
+++ b/coremod/src/main/java/thedarkcolour/exdeorum/coremod/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Ex Deorum
+ * Copyright (c) 2024 thedarkcolour
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+@com.mojang.logging.annotations.MethodsReturnNonnullByDefault
+@javax.annotation.ParametersAreNonnullByDefault
+package thedarkcolour.exdeorum.coremod;
diff --git a/coremod/src/main/resources/META-INF/services/net.neoforged.neoforgespi.coremod.ICoreMod b/coremod/src/main/resources/META-INF/services/net.neoforged.neoforgespi.coremod.ICoreMod
new file mode 100644
index 00000000..8951e244
--- /dev/null
+++ b/coremod/src/main/resources/META-INF/services/net.neoforged.neoforgespi.coremod.ICoreMod
@@ -0,0 +1 @@
+thedarkcolour.exdeorum.coremod.ASMTransformer
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 09ead32f..ea06bbfc 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -8,3 +8,5 @@ pluginManagement {
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0'
}
+
+include('coremod')
diff --git a/src/main/resources/META-INF/coremods.json b/src/main/resources/META-INF/coremods.json
deleted file mode 100644
index 7da1c31b..00000000
--- a/src/main/resources/META-INF/coremods.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "exdeorum": "coremods.js"
-}
\ No newline at end of file
diff --git a/src/main/resources/coremods.js b/src/main/resources/coremods.js
deleted file mode 100644
index 5498e98f..00000000
--- a/src/main/resources/coremods.js
+++ /dev/null
@@ -1,110 +0,0 @@
-// Type definitions
-
-var Opcodes = Java.type('org.objectweb.asm.Opcodes');
-var InsnList = Java.type('org.objectweb.asm.tree.InsnList');
-var VarInsnNode = Java.type('org.objectweb.asm.tree.VarInsnNode');
-var FieldInsnNode = Java.type('org.objectweb.asm.tree.FieldInsnNode');
-var MethodInsnNode = Java.type('org.objectweb.asm.tree.MethodInsnNode');
-var LdcInsnNode = Java.type('org.objectweb.asm.tree.LdcInsnNode');
-var TypeInsnNode = Java.type('org.objectweb.asm.tree.TypeInsnNode');
-var InsnNode = Java.type('org.objectweb.asm.tree.InsnNode');
-
-var ASMAPI = Java.type('net.neoforged.coremod.api.ASMAPI');
-
-function initializeCoreMod() {
- return {
- // inserts a hook into EndCityStructure#findGenerationPoint to fix the position of the city if it is in a void world
- 'EndCityPatch': {
- 'target': {
- 'type': 'METHOD',
- 'class': 'net.minecraft.world.level.levelgen.structure.structures.EndCityStructure',
- 'methodName': 'findGenerationPoint',
- 'methodDesc': '(Lnet/minecraft/world/level/levelgen/structure/Structure$GenerationContext;)Ljava/util/Optional;'
- },
- 'transformer': function (method) {
- var insnList = method.instructions;
-
- for (var i = 0; i < insnList.size(); ++i) {
- var insn = insnList.get(i);
-
- // patch before ASTORE 3
- if (insn.getOpcode() === Opcodes.ASTORE && insn.var === 3) {
- insnList.insertBefore(insn, ASMAPI.listOf(
- new VarInsnNode(Opcodes.ALOAD, 1),
- new MethodInsnNode(Opcodes.INVOKESTATIC, 'thedarkcolour/exdeorum/asm/ASMHooks', 'adjustPos', '(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/levelgen/structure/Structure$GenerationContext;)Lnet/minecraft/core/BlockPos;', false)
- ));
- ASMAPI.log('INFO', 'Successfully patched End City generation for void worlds');
- return method;
- }
- }
-
- ASMAPI.log('ERROR', 'Unable to patch End City generation, void worlds will have no end cities!!!');
- return method;
- }
- },
- // Redirects a field access in the constructor of DedicatedServerProperties from WorldPresets.NORMAL to ASMHooks.overrideDefaultWorldPreset()
- 'DedicatedServerPropertiesPatch': {
- 'target': {
- 'type': 'METHOD',
- 'class': 'net.minecraft.server.dedicated.DedicatedServerProperties',
- 'methodName': '',
- 'methodDesc': '(Ljava/util/Properties;)V'
- },
- 'transformer': function (method) {
- var insnList = method.instructions;
-
- for (var i = 0; i < insnList.size(); ++i) {
- var insn = insnList.get(i);
-
- if (insn.getOpcode() === Opcodes.GETSTATIC && (insn.name.equals('f_226437_') || insn.name.equals('NORMAL'))) {
- var newInsn = new MethodInsnNode(Opcodes.INVOKESTATIC, 'thedarkcolour/exdeorum/asm/ASMHooks', 'overrideDefaultWorldPreset', '()Lnet/minecraft/resources/ResourceKey;', false)
- insnList.set(insn, newInsn);
-
- ASMAPI.log('INFO', 'Successfully patched server.properties to use void world type by default');
- return method;
- }
- }
-
- ASMAPI.log('ERROR', 'Unable to patch server.properties, you will have to set "level-type" to "exdeorum:void_world" manually.');
- return method;
- }
- },
- // Fixes heightmap issues when placing the end portal podium that would only spawn half of the portal
- // What this patch looks like in code: EndIslandPodium.place(..., this.portalLocation = ASMHooks.prePlaceEndPodium(this.portalLocation))
- 'EndPortalPatch': {
- 'target': {
- 'type': 'METHOD',
- 'class': 'net.minecraft.world.level.dimension.end.EndDragonFight',
- 'methodName': 'spawnExitPortal', // spawnExitPortal
- 'methodDesc': '(Z)V'
- },
- 'transformer': function(method) {
- var insnList = method.instructions;
- // Cache the mapped method name
- var randomSourceCreate = ASMAPI.mapMethod('m_216327_');
-
- // Start at 2 to avoid null getPrevious().getPrevious()
- for (var i = 2; i < insnList.size(); ++i) {
- var insn = insnList.get(i);
-
- if (insn.getOpcode() == Opcodes.ALOAD && insn.var == 2) {
- // this.portalLocation = ASMHooks.prePlaceEndPodium(this.portalLocation)
- // f_64072_ maps to portalLocation
- insnList.insertBefore(insn, ASMAPI.listOf(
- new VarInsnNode(Opcodes.ALOAD, 0),
- new VarInsnNode(Opcodes.ALOAD, 0),
- new FieldInsnNode(Opcodes.GETFIELD, 'net/minecraft/world/level/dimension/end/EndDragonFight', 'portalLocation', 'Lnet/minecraft/core/BlockPos;'),
- new MethodInsnNode(Opcodes.INVOKESTATIC, 'thedarkcolour/exdeorum/asm/ASMHooks', 'prePlaceEndPodium', '(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/core/BlockPos;', false),
- new FieldInsnNode(Opcodes.PUTFIELD, 'net/minecraft/world/level/dimension/end/EndDragonFight', 'portalLocation', 'Lnet/minecraft/core/BlockPos;')
- ));
- ASMAPI.log('INFO', 'Successfully patched end portal.');
- return method;
- }
- }
-
- ASMAPI.log('ERROR', 'Unable to patch End Portal, it will not spawn properly and you will be unable to return to the overworld without cheats.');
- return method;
- }
- }
- };
-}
\ No newline at end of file