Revert "Move neoforge to lifecycle"

This reverts commit 32c835bc8f11981c73aaab7200496a493ccf51eb.
This commit is contained in:
Zoe Gidiere 2024-09-19 21:06:42 -06:00
parent 32c835bc8f
commit c2cf4e85f1
9 changed files with 569 additions and 4 deletions

View File

@ -36,6 +36,6 @@ tasks {
} }
architectury { architectury {
common("fabric", "neoforge") common("fabric")
minecraft = Versions.Mod.minecraft minecraft = Versions.Mod.minecraft
} }

View File

@ -17,9 +17,6 @@ dependencies {
implementation(project(path = ":platforms:mixin-common", configuration = "namedElements")) { isTransitive = false } implementation(project(path = ":platforms:mixin-common", configuration = "namedElements")) { isTransitive = false }
"developmentNeoForge"(project(path = ":platforms:mixin-common", configuration = "namedElements")) { isTransitive = false } "developmentNeoForge"(project(path = ":platforms:mixin-common", configuration = "namedElements")) { isTransitive = false }
shaded(project(path = ":platforms:mixin-common", configuration = "transformProductionNeoForge")) { isTransitive = false } shaded(project(path = ":platforms:mixin-common", configuration = "transformProductionNeoForge")) { isTransitive = false }
implementation(project(path = ":platforms:mixin-lifecycle", configuration = "namedElements")) { isTransitive = false }
"developmentNeoForge"(project(path = ":platforms:mixin-lifecycle", configuration = "namedElements")) { isTransitive = false }
shaded(project(path = ":platforms:mixin-lifecycle", configuration = "transformProductionNeoForge")) { isTransitive = false }
minecraft("com.mojang", "minecraft", Versions.Mod.minecraft) minecraft("com.mojang", "minecraft", Versions.Mod.minecraft)
mappings( mappings(

View File

@ -0,0 +1,167 @@
package com.dfsek.terra.neoforge;
import net.minecraftforge.fml.loading.FMLLoader;
import org.burningwave.core.classes.Classes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicReference;
import java.util.jar.JarFile;
import java.util.stream.Stream;
import com.dfsek.terra.AbstractPlatform;
import com.dfsek.terra.api.addon.bootstrap.BootstrapAddonClassLoader;
/**
* Forge is really wacky and screws with class resolution in the addon loader. Loading every single Terra class *manually* on startup
* fixes it. If you know of a better way to fix this, PLEASE let us know.
*/
public final class AwfulForgeHacks {
private static final Logger LOGGER = LoggerFactory.getLogger(AwfulForgeHacks.class);
/**
* Forge tampers with code source to make the *normal* way of getting the current JAR file useless, so this awful hack is
* needed.
*
* <code>
* Class.class.getProtectionDomain()
* .getCodeSource()
* .getLocation()
* .toURI()
* .getPath()
* </code>
*/
public static JarFile getTerraJar() throws IOException {
LOGGER.info("Scanning for Terra JAR...");
return Files.walk(Path.of(System.getProperty("user.dir"), "mods"), 1, FileVisitOption.FOLLOW_LINKS)
.filter(it -> it.getFileName().toString().endsWith(".jar"))
.peek(path -> LOGGER.info("Found mod: {}", path))
.map(Path::toFile)
.flatMap(path -> {
try {
return Stream.of(new JarFile(path));
} catch(IOException e) {
LOGGER.error("Malformed mod JAR: {}: {}", path, e);
return Stream.of();
}
})
.filter(jar -> jar
.stream()
.anyMatch(entry -> entry
.getName()
.equals(ForgeEntryPoint.class.getName().replace('.', '/') + ".class")))
.findFirst()
.orElseThrow(() -> new IllegalStateException("Could not find Terra JAR"));
}
public static void loadAllTerraClasses() {
if(FMLLoader.isProduction()) {
try(JarFile jar = getTerraJar()) {
jar.stream()
.forEach(jarEntry -> {
if(jarEntry.getName().startsWith("com/dfsek/terra/neoforge/mixin")
|| jarEntry.getName().startsWith("com/dfsek/terra/mod/mixin")) {
return;
}
if(jarEntry.getName().endsWith(".class")) {
String name = jarEntry.getName().replace('/', '.');
name = name.substring(0, name.length() - 6);
try {
Class.forName(name);
} catch(ClassNotFoundException | NoClassDefFoundError e) {
LOGGER.warn("Failed to load class {}: {}", name, e);
}
}
});
} catch(IOException e) {
throw new IllegalStateException("Could not load all Terra classes", e);
}
} else {
// Forgive me for what I'm about to do...
LOGGER.warn(
"I felt a great disturbance in the JVM, as if millions of class not found exceptions suddenly cried out in terror and" +
" were suddenly silenced.");
ArrayList<Path> pathsToLoad = new ArrayList<>();
Path terraRoot = Path.of(System.getProperty("user.dir")).getParent().getParent().getParent();
Path commonRoot = terraRoot.resolve("common");
Path implementationRoot = commonRoot.resolve("implementation");
pathsToLoad.add(commonRoot.resolve("api"));
pathsToLoad.add(implementationRoot.resolve("base"));
pathsToLoad.add(implementationRoot.resolve("bootstrap-addon-loader"));
for(Path path : pathsToLoad) {
try {
Path target = path.resolve("build").resolve("classes").resolve("java").resolve("main");
BootstrapAddonClassLoader cl = new BootstrapAddonClassLoader(new URL[]{ path.toUri().toURL() });
Classes.Loaders omegaCL = Classes.Loaders.create();
Files.walk(target, Integer.MAX_VALUE, FileVisitOption.FOLLOW_LINKS)
.filter(it -> it.getFileName().toString().endsWith(".class"))
.map(Path::toFile)
.forEach(it -> {
String name = it.getAbsolutePath().replace(target + "/", "").replace('\\', '.').replace('/', '.');
name = name.substring(0, name.length() - 6);
LOGGER.info("Loading class {}", name);
try {
Class.forName(name);
} catch(ClassNotFoundException e) {
try {
String pathToJar = cl.loadClass(name)
.getProtectionDomain()
.getCodeSource()
.getLocation()
.toURI()
.getPath();
cl.addURL(new URL("jar:file:" + pathToJar + "!/"));
Class newClassLoad = Class.forName(name, true, cl);
omegaCL.loadOrDefine(newClassLoad, AbstractPlatform.class.getClassLoader());
} catch(ClassNotFoundException | URISyntaxException | IOException ex) {
throw new RuntimeException(ex);
}
}
});
} catch(IOException e) {
throw new IllegalStateException("Could not load all Terra classes", e);
}
}
}
}
public enum RegistryStep {
BLOCK,
BIOME,
WORLD_TYPE,
DONE
}
public static class RegistrySanityCheck {
private final AtomicReference<RegistryStep> step = new AtomicReference<>(RegistryStep.BLOCK);
public <T> void progress(RegistryStep expected, Runnable action) {
step.getAndUpdate(s -> {
if(s != expected) {
LOGGER.error("Registry sanity check failed, expected to find {}, found {}", expected, step);
}
action.run();
RegistryStep[] registrySteps = RegistryStep.values();
if(s.ordinal() < registrySteps.length - 1) {
return registrySteps[s.ordinal() + 1];
}
return s;
});
}
}
}

View File

@ -0,0 +1,17 @@
package com.dfsek.terra.neoforge;
import com.dfsek.terra.mod.MinecraftAddon;
import com.dfsek.terra.mod.ModPlatform;
public class ForgeAddon extends MinecraftAddon {
public ForgeAddon(ModPlatform modPlatform) {
super(modPlatform);
}
@Override
public String getID() {
return "terra-forge";
}
}

View File

@ -0,0 +1,82 @@
/*
* This file is part of Terra.
*
* Terra 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.
*
* Terra 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 Terra. If not, see <https://www.gnu.org/licenses/>.
*/
package com.dfsek.terra.neoforge;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.util.Identifier;
import net.minecraft.world.biome.Biome;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventBusSubscriber;
import net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.registries.ForgeRegistries.Keys;
import net.minecraftforge.registries.RegisterEvent;
import net.minecraftforge.registries.RegisterEvent.RegisterHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.dfsek.terra.api.event.events.platform.PlatformInitializationEvent;
import com.dfsek.terra.neoforge.AwfulForgeHacks.RegistrySanityCheck;
import com.dfsek.terra.neoforge.AwfulForgeHacks.RegistryStep;
import com.dfsek.terra.neoforge.util.BiomeUtil;
import com.dfsek.terra.mod.data.Codecs;
@Mod("terra")
@EventBusSubscriber(bus = Bus.MOD)
public class ForgeEntryPoint {
public static final String MODID = "terra";
private static final Logger logger = LoggerFactory.getLogger(ForgeEntryPoint.class);
private static final ForgePlatform TERRA_PLUGIN;
static {
AwfulForgeHacks.loadAllTerraClasses();
TERRA_PLUGIN = new ForgePlatform();
}
private final RegistrySanityCheck sanityCheck = new RegistrySanityCheck();
public ForgeEntryPoint() {
IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
modEventBus.register(this);
}
public static ForgePlatform getPlatform() {
return TERRA_PLUGIN;
}
public static void initialize(RegisterHelper<Biome> helper) {
getPlatform().getEventManager().callEvent(
new PlatformInitializationEvent());
BiomeUtil.registerBiomes(helper);
}
@SubscribeEvent(priority = EventPriority.LOWEST)
public void registerBiomes(RegisterEvent event) {
event.register(Keys.BLOCKS, helper -> sanityCheck.progress(RegistryStep.BLOCK, () -> logger.debug("Block registration detected.")));
event.register(Keys.BIOMES, helper -> sanityCheck.progress(RegistryStep.BIOME, () -> initialize(helper)));
event.register(RegistryKeys.WORLD_PRESET,
helper -> sanityCheck.progress(RegistryStep.WORLD_TYPE, () -> TERRA_PLUGIN.registerWorldTypes(helper::register)));
event.register(RegistryKeys.CHUNK_GENERATOR,
helper -> helper.register(Identifier.of("terra:terra"), Codecs.MINECRAFT_CHUNK_GENERATOR_WRAPPER));
event.register(RegistryKeys.BIOME_SOURCE, helper -> helper.register(Identifier.of("terra:terra"), Codecs.TERRA_BIOME_SOURCE));
}
}

View File

@ -0,0 +1,156 @@
/*
* This file is part of Terra.
*
* Terra 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.
*
* Terra 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 Terra. If not, see <https://www.gnu.org/licenses/>.
*/
package com.dfsek.terra.neoforge;
import ca.solostudios.strata.Versions;
import ca.solostudios.strata.parser.tokenizer.ParseException;
import ca.solostudios.strata.version.Version;
import net.minecraft.MinecraftVersion;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.registry.Registry;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.source.MultiNoiseBiomeSourceParameterList;
import net.minecraft.world.dimension.DimensionType;
import net.minecraft.world.gen.chunk.ChunkGeneratorSettings;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.server.ServerLifecycleHooks;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import com.dfsek.terra.addon.EphemeralAddon;
import com.dfsek.terra.api.addon.BaseAddon;
import com.dfsek.terra.api.util.generic.Lazy;
import com.dfsek.terra.mod.CommonPlatform;
import com.dfsek.terra.mod.ModPlatform;
import com.dfsek.terra.mod.generation.MinecraftChunkGeneratorWrapper;
public class ForgePlatform extends ModPlatform {
private static final Logger LOGGER = LoggerFactory.getLogger(ForgePlatform.class);
private final Lazy<File> dataFolder = Lazy.lazy(() -> new File("./config/Terra"));
public ForgePlatform() {
CommonPlatform.initialize(this);
load();
}
@Override
public MinecraftServer getServer() {
return ServerLifecycleHooks.getCurrentServer();
}
@Override
public boolean reload() {
getTerraConfig().load(this);
getRawConfigRegistry().clear();
boolean succeed = getRawConfigRegistry().loadAll(this);
MinecraftServer server = getServer();
if(server != null) {
server.reloadResources(server.getDataPackManager().getEnabledIds()).exceptionally(throwable -> {
LOGGER.warn("Failed to execute reload", throwable);
return null;
}).join();
//BiomeUtil.registerBiomes();
server.getWorlds().forEach(world -> {
if(world.getChunkManager().getChunkGenerator() instanceof MinecraftChunkGeneratorWrapper chunkGeneratorWrapper) {
getConfigRegistry().get(chunkGeneratorWrapper.getPack().getRegistryKey()).ifPresent(pack -> {
chunkGeneratorWrapper.setPack(pack);
LOGGER.info("Replaced pack in chunk generator for world {}", world);
});
}
});
}
return succeed;
}
@Override
protected Iterable<BaseAddon> platformAddon() {
List<BaseAddon> addons = new ArrayList<>();
super.platformAddon().forEach(addons::add);
String mcVersion = MinecraftVersion.CURRENT.getName();
try {
addons.add(new EphemeralAddon(Versions.parseVersion(mcVersion), "minecraft"));
} catch(ParseException e) {
try {
addons.add(new EphemeralAddon(Versions.parseVersion(mcVersion + ".0"), "minecraft"));
} catch(ParseException ex) {
LOGGER.warn("Failed to parse Minecraft version", e);
}
}
FMLLoader.getLoadingModList().getMods().forEach(mod -> {
String id = mod.getModId();
if(id.equals("terra") || id.equals("minecraft") || id.equals("java")) return;
Version version = Versions.getVersion(mod.getVersion().getMajorVersion(), mod.getVersion().getMinorVersion(),
mod.getVersion().getIncrementalVersion());
addons.add(new EphemeralAddon(version, "forge:" + id));
});
return addons;
}
@Override
public @NotNull String platformName() {
return "Forge";
}
@Override
public @NotNull File getDataFolder() {
return dataFolder.value();
}
@Override
public BaseAddon getPlatformAddon() {
return new ForgeAddon(this);
}
@Override
public Registry<DimensionType> dimensionTypeRegistry() {
return null;
}
@Override
public Registry<Biome> biomeRegistry() {
return null;
}
@Override
public Registry<ChunkGeneratorSettings> chunkGeneratorSettingsRegistry() {
return null;
}
@Override
public Registry<MultiNoiseBiomeSourceParameterList> multiNoiseBiomeSourceParameterListRegistry() {
return null;
}
@Override
public Registry<Enchantment> enchantmentRegistry() {
return null;
}
}

View File

@ -0,0 +1,34 @@
package com.dfsek.terra.neoforge.mixin.lifecycle;
import net.minecraft.registry.RegistryEntryLookup;
import net.minecraft.util.math.noise.DoublePerlinNoiseSampler.NoiseParameters;
import net.minecraft.world.biome.source.util.MultiNoiseUtil.MultiNoiseSampler;
import net.minecraft.world.gen.chunk.ChunkGeneratorSettings;
import net.minecraft.world.gen.noise.NoiseConfig;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.dfsek.terra.mod.util.SeedHack;
/**
* Hack to map noise sampler to seeds
*/
@Mixin(NoiseConfig.class)
public class NoiseConfigMixin {
@Shadow
@Final
private MultiNoiseSampler multiNoiseSampler;
@Inject(method = "<init>(Lnet/minecraft/world/gen/chunk/ChunkGeneratorSettings;Lnet/minecraft/registry/RegistryEntryLookup;J)V",
at = @At("TAIL"))
private void mapMultiNoise(ChunkGeneratorSettings chunkGeneratorSettings, RegistryEntryLookup<NoiseParameters> noiseParametersLookup,
long seed,
CallbackInfo ci) {
SeedHack.register(multiNoiseSampler, seed);
}
}

View File

@ -0,0 +1,22 @@
/*
* This file is part of Terra.
*
* Terra 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.
*
* Terra 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 Terra. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* Mixins that inject behavior into the client/server lifecycle.
*/
package com.dfsek.terra.neoforge.mixin.lifecycle;

View File

@ -0,0 +1,90 @@
package com.dfsek.terra.neoforge.util;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.util.Identifier;
import net.minecraft.village.VillagerType;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegisterEvent.RegisterHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Map;
import java.util.Objects;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.world.biome.Biome;
import com.dfsek.terra.neoforge.ForgeEntryPoint;
import com.dfsek.terra.mod.config.PreLoadCompatibilityOptions;
import com.dfsek.terra.mod.config.ProtoPlatformBiome;
import com.dfsek.terra.mod.config.VanillaBiomeProperties;
import com.dfsek.terra.mod.mixin.access.VillagerTypeAccessor;
import com.dfsek.terra.mod.util.MinecraftUtil;
public final class BiomeUtil {
private static final Logger logger = LoggerFactory.getLogger(BiomeUtil.class);
private BiomeUtil() {
}
public static void registerBiomes(RegisterHelper<net.minecraft.world.biome.Biome> helper) {
logger.info("Registering biomes...");
ForgeEntryPoint.getPlatform().getConfigRegistry().forEach(pack -> { // Register all Terra biomes.
pack.getCheckedRegistry(Biome.class)
.forEach((id, biome) -> registerBiome(biome, pack, id, helper));
});
logger.info("Terra biomes registered.");
}
/**
* Clones a Vanilla biome and injects Terra data to create a Terra-vanilla biome delegate.
*
* @param biome The Terra BiomeBuilder.
* @param pack The ConfigPack this biome belongs to.
*/
private static void registerBiome(Biome biome, ConfigPack pack,
com.dfsek.terra.api.registry.key.RegistryKey id,
RegisterHelper<net.minecraft.world.biome.Biome> helper) {
RegistryEntry<net.minecraft.world.biome.Biome>
vanilla = ForgeRegistries.BIOMES.getHolder(((ProtoPlatformBiome) biome.getPlatformBiome()).getHandle()).orElseThrow();
if(pack.getContext().get(PreLoadCompatibilityOptions.class).useVanillaBiomes()) {
((ProtoPlatformBiome) biome.getPlatformBiome()).setDelegate(vanilla);
} else {
VanillaBiomeProperties vanillaBiomeProperties = biome.getContext().get(VanillaBiomeProperties.class);
net.minecraft.world.biome.Biome minecraftBiome = MinecraftUtil.createBiome(biome,
ForgeRegistries.BIOMES.getDelegateOrThrow(
vanilla.getKey().orElseThrow())
.value(),
vanillaBiomeProperties);
Identifier identifier = Identifier.of("terra", MinecraftUtil.createBiomeID(pack, id));
if(ForgeRegistries.BIOMES.containsKey(identifier)) {
((ProtoPlatformBiome) biome.getPlatformBiome()).setDelegate(ForgeRegistries.BIOMES.getHolder(identifier)
.orElseThrow());
} else {
helper.register(MinecraftUtil.registerKey(identifier).getValue(), minecraftBiome);
((ProtoPlatformBiome) biome.getPlatformBiome()).setDelegate(ForgeRegistries.BIOMES.getHolder(identifier)
.orElseThrow());
}
Map<RegistryKey<net.minecraft.world.biome.Biome>, VillagerType> villagerMap = VillagerTypeAccessor.getBiomeTypeToIdMap();
villagerMap.put(RegistryKey.of(RegistryKeys.BIOME, identifier),
Objects.requireNonNullElse(vanillaBiomeProperties.getVillagerType(),
villagerMap.getOrDefault(vanilla.getKey().orElseThrow(), VillagerType.PLAINS)));
MinecraftUtil.TERRA_BIOME_MAP.computeIfAbsent(vanilla.getKey().orElseThrow().getValue(), i -> new ArrayList<>()).add(
identifier);
}
}
}