refactor internal server world gen

This commit is contained in:
James Seibel
2025-11-27 10:44:47 -06:00
parent 350d72b6ec
commit ace1aab42e
10 changed files with 266 additions and 150 deletions
@@ -21,6 +21,7 @@
package com.seibel.distanthorizons.common.wrappers.worldGeneration;
import com.google.common.collect.ImmutableMap;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.common.wrappers.world.ServerLevelWrapper;
@@ -36,7 +37,10 @@ import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.util.ExceptionUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.gridList.ArrayGridList;
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException;
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.ChunkLightStorage;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.worldGeneration.IBatchGeneratorEnvironmentWrapper;
@@ -46,6 +50,7 @@ import java.util.*;
import java.util.concurrent.*;
import java.util.function.Consumer;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.jetbrains.annotations.NotNull;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.step.StepBiomes;
@@ -86,6 +91,11 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm
.fileLevelConfig(Config.Common.Logging.logWorldGenEventToFile)
.build();
public static final DhLogger RATE_LIMITED_LOGGER = new DhLoggerBuilder()
.name("LOD World Gen")
.maxCountPerSecond(1)
.build();
@NotNull
public static final ImmutableMap<EDhApiWorldGenerationStep, Integer> WORLD_GEN_CHUNK_BORDER_NEEDED_BY_GEN_STEP;
public static final int MAX_WORLD_GEN_CHUNK_BORDER_NEEDED;
@@ -284,6 +294,14 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm
// We handle this later, although that handling would need to change if the gen size ever changes.
LodUtil.assertTrue(genEvent.widthInChunks % 2 == 0, "Generation events are expected to be an evan number of chunks wide.");
if (!DhApi.isDhThread()
&& ModInfo.IS_DEV_BUILD)
{
throw new IllegalStateException("Batch world generation should be called from one of DH's world gen thread. Current thread: ["+Thread.currentThread().getName()+"]");
}
int borderSize = MAX_WORLD_GEN_CHUNK_BORDER_NEEDED;
// genEvent.size - 1 converts the even width size to an odd number for MC compatability
int refSize = (genEvent.widthInChunks - 1) + (borderSize * 2);
@@ -308,7 +326,7 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm
// futures to handle getting empty chunks
CompletableFuture<?>[] readFutures =
// the extra radius of 8 is to account for structure references which need a chunk radius of 8
ChunkPosGenStream.getStream(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.widthInChunks, 8)
ChunkPosGenStream.getStream(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.widthInChunks, 8)// TODO
.map((chunkPos) -> this.chunkFileReader.createEmptyOrPreExistingChunkAsync(chunkPos.x, chunkPos.z, chunkSkyLightingByDhPos, chunkBlockLightingByDhPos, generatedChunkByDhPos))
.toArray(CompletableFuture[]::new);
@@ -433,7 +451,7 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm
// submit generated chunks //
//=========================//
Iterator<ChunkPos> iterator = ChunkPosGenStream.getStream(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.widthInChunks, 0).iterator();
Iterator<ChunkPos> iterator = ChunkPosGenStream.getIterator(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.widthInChunks, 0);
while (iterator.hasNext())
{
ChunkPos pos = iterator.next();
@@ -478,7 +496,6 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm
if (chunk instanceof ProtoChunk)
{
ProtoChunk protoChunk = ((ProtoChunk) chunk);
protoChunk.setLightEngine(region.getLightEngine());
}
});
@@ -525,13 +542,13 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm
return;
}
throwIfThreadInterrupted();
// caves can generally be ignored since they aren't generally visible from far away
if (step == EDhApiWorldGenerationStep.CARVERS)
{
return;
}
// throwIfThreadInterrupted();
// // caves can generally be ignored since they aren't generally visible from far away
// if (step == EDhApiWorldGenerationStep.CARVERS)
// {
// return;
// }
throwIfThreadInterrupted();
this.stepFeatures.generateGroup(genEvent.threadedParam, region, GetCutoutFrom(chunkWrappersToGenerate, EDhApiWorldGenerationStep.FEATURES));
}
@@ -574,6 +591,7 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm
{
DhLightingEngine.INSTANCE.bakeChunkBlockLighting(centerChunk, iChunkWrapperList, maxSkyLight);
}
// centerChunk.setIsDhBlockLightCorrect(true);
this.dhServerLevel.updateBeaconBeamsForChunk(centerChunk, iChunkWrapperList);
}
@@ -2,6 +2,7 @@ package com.seibel.distanthorizons.common.wrappers.worldGeneration;
import net.minecraft.world.level.ChunkPos;
import java.util.Iterator;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
@@ -11,10 +12,13 @@ import java.util.stream.StreamSupport;
public class ChunkPosGenStream
{
public static Iterator<ChunkPos> getIterator(int genMinX, int genMinZ, int width, int extraRadius)
{ return getStream(genMinX, genMinZ, width, extraRadius).iterator(); }
/** @param extraRadius in both the positive and negative directions */
public static Stream<ChunkPos> getStream(int genMinX, int genMinZ, int width, int extraRadius)
{ return StreamSupport.stream(new InclusiveChunkPosIterator(genMinX, genMinZ, width, extraRadius), false); }
public static class InclusiveChunkPosIterator extends Spliterators.AbstractSpliterator<ChunkPos>
private static class InclusiveChunkPosIterator extends Spliterators.AbstractSpliterator<ChunkPos>
{
private final int minX;
private final int minZ;
@@ -107,6 +107,13 @@ public class ChunkFileReader implements AutoCloseable
ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
DhChunkPos dhChunkPos = new DhChunkPos(chunkX, chunkZ);
// if (true)
// {
// ChunkAccess newChunk = CreateEmptyChunk(this.params.level, chunkPos);
// generatedChunkByDhPos.put(dhChunkPos, newChunk);
// return CompletableFuture.completedFuture(newChunk);
// }
if (generatedChunkByDhPos.containsKey(dhChunkPos))
{
return CompletableFuture.completedFuture(generatedChunkByDhPos.get(dhChunkPos));
@@ -1,19 +1,24 @@
package com.seibel.distanthorizons.common.wrappers.worldGeneration.internalServer;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.ChunkPosGenStream;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.GenerationEvent;
import com.seibel.distanthorizons.common.wrappers.worldGeneration.GlobalWorldGenParams;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.level.IDhServerLevel;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.util.ExceptionUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IC2meAccessor;
import com.seibel.distanthorizons.coreapi.ModInfo;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ChunkLevel;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.TicketType;
import net.minecraft.world.level.ChunkPos;
@@ -23,8 +28,6 @@ import net.minecraft.world.level.chunk.status.ChunkStatus;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
public class InternalServerGenerator
@@ -39,6 +42,8 @@ public class InternalServerGenerator
.fileLevelConfig(Config.Common.Logging.logWorldGenChunkLoadEventToFile)
.build();
private static final IC2meAccessor C2ME_ACCESSOR = ModAccessorInjector.INSTANCE.get(IC2meAccessor.class);
#if MC_VER < MC_1_21_5
private static final TicketType<ChunkPos> DH_SERVER_GEN_TICKET = TicketType.create("dh_server_gen_ticket", Comparator.comparingLong(ChunkPos::toLong));
#elif MC_VER < MC_1_21_9
@@ -47,6 +52,8 @@ public class InternalServerGenerator
private static final TicketType DH_SERVER_GEN_TICKET = new TicketType(/* timeout, 0 = disabled*/0L, /* flags */TicketType.FLAG_LOADING);
#endif
private static boolean c2meMissingWarningLogged = false;
private final GlobalWorldGenParams params;
private final IDhServerLevel dhServerLevel;
@@ -69,183 +76,197 @@ public class InternalServerGenerator
// generation //
//============//
public void generateChunksViaInternalServer(GenerationEvent genEvent) throws InterruptedException
public void generateChunksViaInternalServer(GenerationEvent genEvent)
{
LinkedBlockingQueue<Runnable> runnableQueue = new LinkedBlockingQueue<>();
this.runValidation();
Map<DhChunkPos, ChunkWrapper> chunkWrappersByDhPos = Collections.synchronizedMap(new HashMap<>());
//===================================//
// create generation queue runnables //
//===================================//
// request each chunk pos from the server
CompletableFuture<?>[] requestFutures =
ChunkPosGenStream.getStream(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.widthInChunks, 0)
.map(chunkPos ->
{
return requestChunkFromServerAsync(this.params.level, chunkPos, true)
.whenCompleteAsync((chunk, throwable) ->
{
// unwrap the CompletionException if necessary
Throwable actualThrowable = throwable;
while (actualThrowable instanceof CompletionException)
{
actualThrowable = actualThrowable.getCause();
}
if (throwable != null)
{
CHUNK_LOAD_LOGGER.warn("DistantHorizons: Couldn't load chunk [" + chunkPos + "] from server, error: [" + actualThrowable.getMessage() + "].", actualThrowable);
}
if (chunk != null)
{
ChunkWrapper chunkWrapper = new ChunkWrapper(chunk, this.dhServerLevel.getLevelWrapper());
chunkWrappersByDhPos.put(new DhChunkPos(chunkPos.x, chunkPos.z), chunkWrapper);
}
}, runnableQueue::add);
})
.toArray(CompletableFuture[]::new);
// handle each generated chunk
CompletableFuture<Void> processGeneratedChunksFuture =
CompletableFuture.allOf(requestFutures)
.whenCompleteAsync((voidObj, throwable) ->
{
// generate chunk lighting using DH's lighting engine
int maxSkyLight = this.dhServerLevel.getServerLevelWrapper().hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT;
ArrayList<IChunkWrapper> generatedChunks = new ArrayList<>(chunkWrappersByDhPos.values());
for (IChunkWrapper iChunkWrapper : generatedChunks)
{
((ChunkWrapper) iChunkWrapper).recalculateDhHeightMapsIfNeeded();
// pre-generated chunks should have lighting but new ones won't
if (!iChunkWrapper.isDhBlockLightingCorrect())
{
DhLightingEngine.INSTANCE.bakeChunkBlockLighting(iChunkWrapper, generatedChunks, maxSkyLight);
}
this.dhServerLevel.updateBeaconBeamsForChunk(iChunkWrapper, generatedChunks);
}
for (IChunkWrapper iChunkWrapper : generatedChunks)
{
genEvent.resultConsumer.accept(iChunkWrapper);
}
}, runnableQueue::add)
.whenCompleteAsync((unused, throwable) ->
{
// cleanup
// release the generated chunks
Iterator<ChunkPos> iterator = ChunkPosGenStream.getStream(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.widthInChunks, 0).iterator();
while (iterator.hasNext())
{
ChunkPos chunkPos = iterator.next();
releaseChunkFromServer(this.params.level, chunkPos, true);
}
});
processGeneratedChunksFuture.whenCompleteAsync((unused, throwable) -> { }, runnableQueue::add); // trigger wakeup
//===============//
// run each step //
//===============//
while (!processGeneratedChunksFuture.isDone())
try
{
try
//=====================//
// create gen requests //
//=====================//
ArrayList<CompletableFuture<ChunkAccess>> getChunkFutureList = new ArrayList<>();
{
Runnable command = runnableQueue.poll(1, TimeUnit.SECONDS);
if (command != null)
Iterator<ChunkPos> chunkPosIterator = ChunkPosGenStream.getIterator(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.widthInChunks, 0);
while (chunkPosIterator.hasNext())
{
command.run();
ChunkPos chunkPos = chunkPosIterator.next();
CompletableFuture<ChunkAccess> requestChunkFuture =
this.requestChunkFromServerAsync(chunkPos)
// log errors if necessary
.whenCompleteAsync(
(chunk, throwable) ->
{
// unwrap the CompletionException if necessary
Throwable actualThrowable = throwable;
while (actualThrowable instanceof CompletionException)
{
actualThrowable = actualThrowable.getCause();
}
if (actualThrowable != null)
{
// ignore expected shutdown exceptions
boolean isShutdownException =
ExceptionUtil.isShutdownException(actualThrowable)
|| actualThrowable.getMessage().contains("Unloaded chunk");
if (!isShutdownException)
{
CHUNK_LOAD_LOGGER.warn("DistantHorizons: Couldn't load chunk [" + chunkPos + "] from server, error: [" + actualThrowable.getMessage() + "].", actualThrowable);
}
}
});
getChunkFutureList.add(requestChunkFuture);
}
}
catch (InterruptedException e)
//==============================//
// wait for generation requests //
//==============================//
// Join-ing each thread will prevent DH from working on anything else
// but will also prevent over-queuing world gen tasks.
// If C2ME is present the CPU will still be well utilized.
ArrayList<IChunkWrapper> chunkWrappers = new ArrayList<>();
for (int i = 0; i < getChunkFutureList.size(); i++)
{
// interrupted, release chunk to server
Iterator<ChunkPos> iterator = ChunkPosGenStream.getStream(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.widthInChunks, 0).iterator();
while (iterator.hasNext())
CompletableFuture<ChunkAccess> getChunkFuture = getChunkFutureList.get(i);
ChunkAccess chunk = getChunkFuture.join();
if (chunk != null)
{
ChunkPos chunkPos = iterator.next();
releaseChunkFromServer(this.params.level, chunkPos, true);
ChunkWrapper chunkWrapper = new ChunkWrapper(chunk, this.dhServerLevel.getLevelWrapper());
chunkWrappers.add(chunkWrapper);
}
}
//==========================//
// process generated chunks //
//==========================//
int maxSkyLight = this.dhServerLevel.getServerLevelWrapper().hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT;
for (int i = 0; i < chunkWrappers.size(); i++)
{
ChunkWrapper chunkWrapper = (ChunkWrapper)chunkWrappers.get(i);
chunkWrapper.recalculateDhHeightMapsIfNeeded();
// pre-generated chunks should have lighting but new ones won't
if (!chunkWrapper.isDhBlockLightingCorrect())
{
DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, chunkWrappers, maxSkyLight);
}
throw e;
this.dhServerLevel.updateBeaconBeamsForChunk(chunkWrapper, chunkWrappers);
genEvent.resultConsumer.accept(chunkWrapper);
}
}
finally
{
// release all chunks from the server to prevent out of memory issues
Iterator<ChunkPos> chunkPosIterator = ChunkPosGenStream.getIterator(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.widthInChunks, 0);
while (chunkPosIterator.hasNext())
{
ChunkPos chunkPos = chunkPosIterator.next();
this.releaseChunkFromServer(this.params.level, chunkPos);
}
}
}
/** @param generateUpToFeatures if false this generate the chunk up to "FULL" status */
private static CompletableFuture<ChunkAccess> requestChunkFromServerAsync(ServerLevel level, ChunkPos pos, boolean generateUpToFeatures)
private void runValidation()
{
// DH thread check
if (!DhApi.isDhThread()
&& ModInfo.IS_DEV_BUILD)
{
throw new IllegalStateException("Internal server generation should be called from one of DH's world gen thread. Current thread: ["+Thread.currentThread().getName()+"]");
}
// C2ME present?
if (C2ME_ACCESSOR == null
&& !c2meMissingWarningLogged)
{
c2meMissingWarningLogged = true;
String c2meWarning = "C2ME missing, \n" +
"low CPU usage and slow world gen speeds expected. \n" +
"DH is set to use MC's internal server for world gen \n" +
"this mode is less efficient unless a mod like C2ME is present."
;
if (Config.Common.Logging.Warning.showSlowWorldGenSettingWarnings.get())
{
String message =
// orange text
"\u00A76" + "Distant Horizons: slow world gen." + "\u00A7r\n" +
c2meWarning;
ClientApi.INSTANCE.showChatMessageNextFrame(message);
}
LOGGER.warn(c2meWarning);
}
}
private CompletableFuture<ChunkAccess> requestChunkFromServerAsync(ChunkPos chunkPos)
{
return CompletableFuture.supplyAsync(() ->
{
int chunkLevel;
#if MC_VER <= MC_1_19_4
// 33 is equivalent to FULL Chunk
chunkLevel = generateUpToFeatures ? 33 + ChunkStatus.getDistance(ChunkStatus.FEATURES) : 33;
#else
// 33 is equivalent to FULL Chunk
chunkLevel = generateUpToFeatures ? ChunkLevel.byStatus(ChunkStatus.FEATURES) : 33;
#endif
ServerLevel level = this.params.level;
#if MC_VER < MC_1_21_5
int chunkLevel = 33; // 33 is equivalent to FULL Chunk
level.getChunkSource().distanceManager.addTicket(DH_SERVER_GEN_TICKET, pos, chunkLevel, pos);
#else
level.getChunkSource().addTicketWithRadius(DH_SERVER_GEN_TICKET, pos, 0);
level.getChunkSource().addTicketWithRadius(DH_SERVER_GEN_TICKET, chunkPos, 0);
#endif
level.getChunkSource().distanceManager.runAllUpdates(level.getChunkSource().chunkMap); // probably not the most optimal to run updates here, but fast enough
ChunkHolder holder = level.getChunkSource().chunkMap.getUpdatingChunkIfPresent(pos.toLong());
if (holder == null)
// probably not the most optimal to run updates here, but fast enough
level.getChunkSource().distanceManager.runAllUpdates(level.getChunkSource().chunkMap);
ChunkHolder chunkHolder = level.getChunkSource().chunkMap.getUpdatingChunkIfPresent(chunkPos.toLong());
if (chunkHolder == null)
{
throw new IllegalStateException("No chunk holder after ticket has been added");
throw new IllegalStateException("No chunk chunkHolder for pos ["+chunkPos+"] after ticket has been added.");
}
#if MC_VER <= MC_1_20_4
return holder.getOrScheduleFuture(ChunkStatus.FEATURES, level.getChunkSource().chunkMap)
return chunkHolder.getOrScheduleFuture(ChunkStatus.FEATURES, level.getChunkSource().chunkMap)
.thenApply(result -> result.left().orElseThrow(() -> new RuntimeException(result.right().get().toString()))); // can throw if the server is shutting down
#elif MC_VER <= MC_1_20_6
return holder.getOrScheduleFuture(ChunkStatus.FEATURES, level.getChunkSource().chunkMap)
return chunkHolder.getOrScheduleFuture(ChunkStatus.FEATURES, level.getChunkSource().chunkMap)
.thenApply(result -> result.orElseThrow(() -> new RuntimeException(result.toString()))); // can throw if the server is shutting down
#else
return holder.scheduleChunkGenerationTask(ChunkStatus.FEATURES, level.getChunkSource().chunkMap)
return chunkHolder.scheduleChunkGenerationTask(ChunkStatus.FEATURES, level.getChunkSource().chunkMap)
.thenApply(result -> result.orElseThrow(() -> new RuntimeException(result.getError()))); // can throw if the server is shutting down
#endif
}, level.getChunkSource().chunkMap.mainThreadExecutor)
.thenCompose(Function.identity());
}, this.params.level.getChunkSource().chunkMap.mainThreadExecutor)
.thenCompose(Function.identity());
}
/** @param chunkWasGeneratedUpToFeatures if false this assumes the chunk was generated to "FULL" status */
private static void releaseChunkFromServer(ServerLevel level, ChunkPos pos, boolean chunkWasGeneratedUpToFeatures)
/**
* mitigates out of memory issues in the vanilla chunk system. <br>
* See: https://github.com/pop4959/Chunky/pull/383
*/
private void releaseChunkFromServer(ServerLevel level, ChunkPos pos)
{
level.getChunkSource().chunkMap.mainThreadExecutor.execute(() ->
{
try
{
int chunkLevel;
#if MC_VER <= MC_1_19_4
// 33 is equivalent to FULL Chunk
chunkLevel = chunkWasGeneratedUpToFeatures ? 33 + ChunkStatus.getDistance(ChunkStatus.FEATURES) : 33;
#else
// 33 is equivalent to FULL Chunk
chunkLevel = chunkWasGeneratedUpToFeatures ? ChunkLevel.byStatus(ChunkStatus.FEATURES) : 33;
#endif
#if MC_VER < MC_1_21_5
int chunkLevel = 33; // 33 is equivalent to FULL Chunk
level.getChunkSource().distanceManager.removeTicket(DH_SERVER_GEN_TICKET, pos, chunkLevel, pos);
#else
level.getChunkSource().removeTicketWithRadius(DH_SERVER_GEN_TICKET, pos, 0);
#endif
// mitigate OOM issues in vanilla chunk system: see https://github.com/pop4959/Chunky/pull/383
level.getChunkSource().chunkMap.tick(() -> false);
#if MC_VER > MC_1_16_5
level.entityManager.tick();
#endif
@@ -47,8 +47,6 @@ import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
#endif
import javax.swing.*;
import java.awt.*;
import java.util.function.Consumer;
/**
@@ -110,6 +108,7 @@ public class FabricMain extends AbstractModInitializer implements ClientModIniti
this.tryCreateModCompatAccessor("starlight", IStarlightAccessor.class, StarlightAccessor::new);
this.tryCreateModCompatAccessor("optifine", IOptifineAccessor.class, OptifineAccessor::new);
this.tryCreateModCompatAccessor("bclib", IBCLibAccessor.class, BCLibAccessor::new);
this.tryCreateModCompatAccessor("c2me", IC2meAccessor.class, C2meAccessor::new);
#if MC_VER >= MC_1_19_4
// 1.19.4 is the lowest version Iris supports DH
this.tryCreateModCompatAccessor("iris", IIrisAccessor.class, IrisAccessor::new);
@@ -0,0 +1,32 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.fabric.wrappers.modAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IC2meAccessor;
public class C2meAccessor implements IC2meAccessor
{
@Override
public String getModName()
{
return "c2me";
}
}
@@ -30,9 +30,11 @@ import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IPluginPacketSender;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IC2meAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IModChecker;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IOptifineAccessor;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.neoforge.wrappers.modAccessor.C2meAccessor;
import com.seibel.distanthorizons.neoforge.wrappers.NeoforgeMinecraftRenderWrapper;
import com.seibel.distanthorizons.neoforge.wrappers.modAccessor.ModChecker;
import com.seibel.distanthorizons.neoforge.wrappers.modAccessor.OptifineAccessor;
@@ -144,6 +146,7 @@ public class NeoforgeMain extends AbstractModInitializer
protected void initializeModCompat()
{
this.tryCreateModCompatAccessor("optifine", IOptifineAccessor.class, OptifineAccessor::new);
this.tryCreateModCompatAccessor("c2me", IC2meAccessor.class, C2meAccessor::new);
#if MC_VER < MC_1_20_6
ModLoadingContext.get().registerExtensionPoint(ConfigScreenHandler.ConfigScreenFactory.class,
@@ -0,0 +1,32 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.neoforge.wrappers.modAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IC2meAccessor;
public class C2meAccessor implements IC2meAccessor
{
@Override
public String getModName()
{
return "c2me";
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ netty_version=4.1.97.Final
# Fabric loader
fabric_loader_version=0.17.3
fabric_api_version=0.135.0+1.21.10
fabric_api_version=0.138.3+1.21.10
modmenu_version=16.0.0-rc.1
starlight_version_fabric=
phosphor_version_fabric=