Improve the casting around SharedApi.currentWorld
This commit is contained in:
+9
-4
@@ -18,6 +18,7 @@ import com.seibel.lod.core.util.*;
|
||||
import com.seibel.lod.core.util.math.Vec3d;
|
||||
import com.seibel.lod.core.util.math.Vec3f;
|
||||
import com.seibel.lod.core.util.math.Vec3i;
|
||||
import com.seibel.lod.core.world.AbstractDhWorld;
|
||||
import com.seibel.lod.core.wrapperInterfaces.block.IBlockStateWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
@@ -166,18 +167,22 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
|
||||
*/
|
||||
private static DhApiResult<DhApiTerrainDataPoint[]> getTerrainDataColumnArray(IDhApiLevelWrapper levelWrapper, DhLodPos requestedColumnPos, Integer nullableBlockYPos)
|
||||
{
|
||||
if (SharedApi.currentWorld == null)
|
||||
AbstractDhWorld currentWorld = SharedApi.getAbstractDhWorld();
|
||||
if (currentWorld == null)
|
||||
{
|
||||
return DhApiResult.createFail("Unable to get terrain data before the world has loaded.");
|
||||
}
|
||||
if (!(levelWrapper instanceof ILevelWrapper coreLevelWrapper))
|
||||
|
||||
if (!ILevelWrapper.class.isInstance(levelWrapper))
|
||||
{
|
||||
// custom level wrappers aren't supported,
|
||||
// the API user must get a level wrapper from our code somewhere
|
||||
return DhApiResult.createFail("Unsupported [" + IDhApiLevelWrapper.class.getSimpleName() + "] implementation, only the core class [" + IDhLevel.class.getSimpleName() + "] is a valid parameter.");
|
||||
return DhApiResult.createFail("Unsupported ["+IDhApiLevelWrapper.class.getSimpleName()+"] implementation, only the core class ["+IDhLevel.class.getSimpleName()+"] is a valid parameter.");
|
||||
}
|
||||
ILevelWrapper coreLevelWrapper = (ILevelWrapper) levelWrapper;
|
||||
|
||||
IDhLevel level = SharedApi.currentWorld.getLevel(coreLevelWrapper);
|
||||
|
||||
IDhLevel level = currentWorld.getLevel(coreLevelWrapper);
|
||||
if (level == null)
|
||||
{
|
||||
return DhApiResult.createFail("Unable to get terrain data before the world has loaded.");
|
||||
|
||||
@@ -101,7 +101,7 @@ public class ClientApi
|
||||
LOGGER.info("Client on ClientOnly mode connecting.");
|
||||
}
|
||||
|
||||
SharedApi.currentWorld = new DhClientWorld();
|
||||
SharedApi.setDhWorld(new DhClientWorld());
|
||||
}
|
||||
|
||||
public void onClientOnlyDisconnected()
|
||||
@@ -111,15 +111,15 @@ public class ClientApi
|
||||
LOGGER.info("Client on ClientOnly mode disconnecting.");
|
||||
}
|
||||
|
||||
SharedApi.currentWorld.close();
|
||||
SharedApi.currentWorld = null;
|
||||
SharedApi.getAbstractDhWorld().close();
|
||||
SharedApi.setDhWorld(null);
|
||||
}
|
||||
|
||||
public void clientChunkLoadEvent(IChunkWrapper chunk, IClientLevelWrapper level)
|
||||
{
|
||||
if (SharedApi.getEnvironment() == EWorldEnvironment.Client_Only)
|
||||
{
|
||||
IDhLevel dhLevel = SharedApi.currentWorld.getLevel(level);
|
||||
IDhLevel dhLevel = SharedApi.getAbstractDhWorld().getLevel(level);
|
||||
if (dhLevel != null)
|
||||
{
|
||||
dhLevel.updateChunk(chunk);
|
||||
@@ -145,9 +145,10 @@ public class ClientApi
|
||||
LOGGER.info("Client level "+level+" unloading.");
|
||||
}
|
||||
|
||||
if (SharedApi.currentWorld != null)
|
||||
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
|
||||
if (world != null)
|
||||
{
|
||||
SharedApi.currentWorld.unloadLevel(level);
|
||||
world.unloadLevel(level);
|
||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(level));
|
||||
}
|
||||
}
|
||||
@@ -159,9 +160,10 @@ public class ClientApi
|
||||
LOGGER.info("Client level "+level+" loading.");
|
||||
}
|
||||
|
||||
if (SharedApi.currentWorld != null)
|
||||
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
|
||||
if (world != null)
|
||||
{
|
||||
SharedApi.currentWorld.getOrLoadLevel(level);
|
||||
world.getOrLoadLevel(level);
|
||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(level));
|
||||
}
|
||||
}
|
||||
@@ -208,9 +210,10 @@ public class ClientApi
|
||||
ConfigBasedLogger.updateAll();
|
||||
ConfigBasedSpamLogger.updateAll(doFlush);
|
||||
|
||||
if (SharedApi.currentWorld instanceof IDhClientWorld)
|
||||
IDhClientWorld clientWorld = SharedApi.getIDhClientWorld();
|
||||
if (clientWorld != null)
|
||||
{
|
||||
((IDhClientWorld) SharedApi.currentWorld).clientTick();
|
||||
clientWorld.clientTick();
|
||||
}
|
||||
profiler.pop();
|
||||
}
|
||||
@@ -246,8 +249,8 @@ public class ClientApi
|
||||
|
||||
//FIXME: Improve class hierarchy of DhWorld, IClientWorld, IServerWorld to fix all this hard casting
|
||||
// (also in RenderUtil)
|
||||
AbstractDhWorld dhWorld = SharedApi.currentWorld;
|
||||
IDhClientLevel level = (IDhClientLevel) dhWorld.getOrLoadLevel(levelWrapper);
|
||||
IDhClientWorld dhClientWorld = SharedApi.getIDhClientWorld();
|
||||
IDhClientLevel level = dhClientWorld.getOrLoadClientLevel(levelWrapper);
|
||||
|
||||
if (prefLoggerEnabled)
|
||||
{
|
||||
|
||||
@@ -24,19 +24,16 @@ import com.seibel.lod.api.methods.events.abstractEvents.DhApiLevelSaveEvent;
|
||||
import com.seibel.lod.api.methods.events.abstractEvents.DhApiLevelUnloadEvent;
|
||||
import com.seibel.lod.core.DependencyInjection.ApiEventInjector;
|
||||
import com.seibel.lod.core.level.IDhLevel;
|
||||
import com.seibel.lod.core.world.AbstractDhWorld;
|
||||
import com.seibel.lod.core.world.DhClientServerWorld;
|
||||
import com.seibel.lod.core.world.DhServerWorld;
|
||||
import com.seibel.lod.core.world.IDhServerWorld;
|
||||
import com.seibel.lod.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.lod.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.lod.core.wrapperInterfaces.IVersionConstants;
|
||||
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IServerLevelWrapper;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
|
||||
/**
|
||||
* This holds the methods that should be called by the host mod loader (Fabric,
|
||||
* Forge, etc.). Specifically server events.
|
||||
@@ -65,9 +62,9 @@ public class ServerApi
|
||||
|
||||
public void serverTickEvent()
|
||||
{
|
||||
if (SharedApi.currentWorld instanceof IDhServerWorld)
|
||||
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
|
||||
if (serverWorld != null)
|
||||
{
|
||||
IDhServerWorld serverWorld = (IDhServerWorld) SharedApi.currentWorld;
|
||||
serverWorld.serverTick();
|
||||
this.lastWorldGenTickDelta--;
|
||||
if (this.lastWorldGenTickDelta <= 0)
|
||||
@@ -89,25 +86,18 @@ public class ServerApi
|
||||
LOGGER.info("Server World loading with (dedicated?:{})", isDedicatedEnvironment);
|
||||
}
|
||||
|
||||
if (isDedicatedEnvironment)
|
||||
{
|
||||
SharedApi.currentWorld = new DhServerWorld();
|
||||
}
|
||||
else
|
||||
{
|
||||
SharedApi.currentWorld = new DhClientServerWorld();
|
||||
}
|
||||
SharedApi.setDhWorld(isDedicatedEnvironment ? new DhServerWorld() : new DhClientServerWorld());
|
||||
}
|
||||
|
||||
public void serverUnloadEvent()
|
||||
{
|
||||
if (ENABLE_EVENT_LOGGING)
|
||||
{
|
||||
LOGGER.info("Server World "+SharedApi.currentWorld+" unloading");
|
||||
LOGGER.info("Server World "+SharedApi.getAbstractDhWorld()+" unloading");
|
||||
}
|
||||
|
||||
SharedApi.currentWorld.close();
|
||||
SharedApi.currentWorld = null;
|
||||
SharedApi.getAbstractDhWorld().close();
|
||||
SharedApi.setDhWorld(null);
|
||||
}
|
||||
|
||||
public void serverLevelLoadEvent(IServerLevelWrapper level)
|
||||
@@ -117,9 +107,10 @@ public class ServerApi
|
||||
LOGGER.info("Server Level {} loading", level);
|
||||
}
|
||||
|
||||
if (SharedApi.currentWorld != null)
|
||||
AbstractDhWorld serverWorld = SharedApi.getAbstractDhWorld();
|
||||
if (serverWorld != null)
|
||||
{
|
||||
SharedApi.currentWorld.getOrLoadLevel(level);
|
||||
serverWorld.getOrLoadLevel(level);
|
||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(level));
|
||||
}
|
||||
}
|
||||
@@ -130,9 +121,10 @@ public class ServerApi
|
||||
LOGGER.info("Server Level {} unloading", level);
|
||||
}
|
||||
|
||||
if (SharedApi.currentWorld != null)
|
||||
AbstractDhWorld serverWorld = SharedApi.getAbstractDhWorld();
|
||||
if (serverWorld != null)
|
||||
{
|
||||
SharedApi.currentWorld.unloadLevel(level);
|
||||
serverWorld.unloadLevel(level);
|
||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(level));
|
||||
}
|
||||
}
|
||||
@@ -142,14 +134,15 @@ public class ServerApi
|
||||
{
|
||||
if (ENABLE_EVENT_LOGGING)
|
||||
{
|
||||
LOGGER.info("Server world {} saving", SharedApi.currentWorld);
|
||||
LOGGER.info("Server world "+SharedApi.getAbstractDhWorld()+" saving");
|
||||
}
|
||||
|
||||
if (SharedApi.currentWorld instanceof IDhServerWorld)
|
||||
AbstractDhWorld serverWorld = SharedApi.getAbstractDhWorld();
|
||||
if (serverWorld != null)
|
||||
{
|
||||
SharedApi.currentWorld.saveAndFlush();
|
||||
serverWorld.saveAndFlush();
|
||||
|
||||
for (IDhLevel level : SharedApi.currentWorld.getAllLoadedLevels())
|
||||
for (IDhLevel level : serverWorld.getAllLoadedLevels())
|
||||
{
|
||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelSaveEvent.class, new DhApiLevelSaveEvent.EventParam(level.getLevelWrapper()));
|
||||
}
|
||||
@@ -158,7 +151,7 @@ public class ServerApi
|
||||
|
||||
public void serverChunkLoadEvent(IChunkWrapper chunk, ILevelWrapper level)
|
||||
{
|
||||
IDhLevel dhLevel = SharedApi.currentWorld.getLevel(level);
|
||||
IDhLevel dhLevel = SharedApi.getAbstractDhWorld().getLevel(level);
|
||||
if (dhLevel != null)
|
||||
{
|
||||
dhLevel.updateChunk(chunk);
|
||||
@@ -166,7 +159,7 @@ public class ServerApi
|
||||
}
|
||||
public void serverChunkSaveEvent(IChunkWrapper chunk, ILevelWrapper level)
|
||||
{
|
||||
IDhLevel dhLevel = SharedApi.currentWorld.getLevel(level);
|
||||
IDhLevel dhLevel = SharedApi.getAbstractDhWorld().getLevel(level);
|
||||
if (dhLevel != null)
|
||||
{
|
||||
dhLevel.updateChunk(chunk);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package com.seibel.lod.core.api.internal;
|
||||
|
||||
import com.seibel.lod.core.Initializer;
|
||||
import com.seibel.lod.core.world.EWorldEnvironment;
|
||||
import com.seibel.lod.core.world.AbstractDhWorld;
|
||||
import com.seibel.lod.core.world.*;
|
||||
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
|
||||
|
||||
public class SharedApi
|
||||
{
|
||||
public static IMinecraftSharedWrapper MC;
|
||||
public static AbstractDhWorld currentWorld;
|
||||
|
||||
private static AbstractDhWorld currentWorld;
|
||||
|
||||
|
||||
|
||||
@@ -18,4 +18,17 @@ public class SharedApi
|
||||
|
||||
public static EWorldEnvironment getEnvironment() { return (currentWorld == null) ? null : currentWorld.environment; }
|
||||
|
||||
|
||||
public static void setDhWorld(AbstractDhWorld newWorld) { currentWorld = newWorld; }
|
||||
|
||||
public static AbstractDhWorld getAbstractDhWorld() { return currentWorld; }
|
||||
|
||||
/** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhClientServerWorld} */
|
||||
public static DhClientServerWorld getDhClientServerWorld() { return (currentWorld != null && DhClientServerWorld.class.isInstance(currentWorld)) ? (DhClientServerWorld) currentWorld : null; }
|
||||
|
||||
/** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhClientWorld} or {@link DhClientServerWorld} */
|
||||
public static IDhClientWorld getIDhClientWorld() { return (currentWorld != null && IDhClientWorld.class.isInstance(currentWorld)) ? (IDhClientWorld) currentWorld : null; }
|
||||
/** returns null if the {@link SharedApi#currentWorld} isn't a {@link DhServerWorld} or {@link DhClientServerWorld} */
|
||||
public static IDhServerWorld getIDhServerWorld() { return (currentWorld != null && IDhServerWorld.class.isInstance(currentWorld)) ? (IDhServerWorld) currentWorld : null; }
|
||||
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ public class DhClientServerLevel implements IDhClientLevel, IDhServerLevel
|
||||
return;
|
||||
}
|
||||
|
||||
if (renderState.quadtree.blockViewDistance != Config.Client.Graphics.Quality.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH)
|
||||
if (renderState.quadtree.blockRenderDistance != Config.Client.Graphics.Quality.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH)
|
||||
{
|
||||
if (!this.renderStateRef.compareAndSet(renderState, null))
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.seibel.lod.api.interfaces.render.IDhApiRenderProxy;
|
||||
import com.seibel.lod.api.objects.DhApiResult;
|
||||
import com.seibel.lod.core.api.internal.SharedApi;
|
||||
import com.seibel.lod.core.level.IDhLevel;
|
||||
import com.seibel.lod.core.world.AbstractDhWorld;
|
||||
|
||||
/**
|
||||
* Used to interact with Distant Horizons' rendering systems.
|
||||
@@ -24,14 +25,15 @@ public class DhApiRenderProxy implements IDhApiRenderProxy
|
||||
public DhApiResult<Boolean> clearRenderDataCache()
|
||||
{
|
||||
// make sure this is a valid time to run the method
|
||||
if (SharedApi.currentWorld == null)
|
||||
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
|
||||
if (world == null)
|
||||
{
|
||||
return DhApiResult.createFail("No world loaded");
|
||||
}
|
||||
|
||||
|
||||
// clear the render caches for each level
|
||||
Iterable<? extends IDhLevel> loadedLevels = SharedApi.currentWorld.getAllLoadedLevels();
|
||||
Iterable<? extends IDhLevel> loadedLevels = world.getAllLoadedLevels();
|
||||
for (IDhLevel level : loadedLevels)
|
||||
{
|
||||
if (level != null)
|
||||
|
||||
@@ -50,7 +50,7 @@ public class LodQuadTree implements AutoCloseable
|
||||
|
||||
private final MovableGridRingList<LodRenderSection>[] renderSectionRingLists;
|
||||
|
||||
public final int blockViewDistance;
|
||||
public final int blockRenderDistance;
|
||||
private final ILodRenderSourceProvider renderSourceProvider;
|
||||
|
||||
private final IDhClientLevel level; //FIXME: Proper hierarchy to remove this reference!
|
||||
@@ -72,7 +72,7 @@ public class LodQuadTree implements AutoCloseable
|
||||
DetailDistanceUtil.updateSettings(); //TODO: Move this to somewhere else
|
||||
this.level = level;
|
||||
this.renderSourceProvider = provider;
|
||||
this.blockViewDistance = viewDistance;
|
||||
this.blockRenderDistance = viewDistance;
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -179,17 +179,23 @@ public class RenderUtil
|
||||
int vanillaBlockRenderedDistance = MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH;
|
||||
|
||||
float nearClipPlane;
|
||||
if (Config.Client.Advanced.lodOnlyMode.get()) {
|
||||
if (Config.Client.Advanced.lodOnlyMode.get())
|
||||
{
|
||||
nearClipPlane = 0.1f;
|
||||
} else if (Config.Client.Graphics.AdvancedGraphics.useExtendedNearClipPlane.get()) {
|
||||
}
|
||||
else if (Config.Client.Graphics.AdvancedGraphics.useExtendedNearClipPlane.get())
|
||||
{
|
||||
nearClipPlane = Math.min(vanillaBlockRenderedDistance - LodUtil.CHUNK_WIDTH, (float) 8 * LodUtil.CHUNK_WIDTH); // allow a max near clip plane of 8 chunks
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
nearClipPlane = 16f;
|
||||
}
|
||||
|
||||
// modify the based on the player's FOV
|
||||
double fov = MC_RENDER.getFov(partialTicks);
|
||||
double aspectRatio = (double) MC_RENDER.getScreenWidth() / MC_RENDER.getScreenHeight();
|
||||
|
||||
return (float) (nearClipPlane
|
||||
/ Math.sqrt(1d + MathUtil.pow2(Math.tan(fov / 180d * Math.PI / 2d))
|
||||
* (MathUtil.pow2(aspectRatio) + 1d)));
|
||||
@@ -204,23 +210,26 @@ public class RenderUtil
|
||||
public static boolean shouldLodsRender(ILevelWrapper levelWrapper)
|
||||
{
|
||||
if (!MC.playerExists())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (levelWrapper == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AbstractDhWorld dhWorld = SharedApi.currentWorld;
|
||||
if (dhWorld == null)
|
||||
IDhClientWorld clientWorld = SharedApi.getIDhClientWorld();
|
||||
if (clientWorld == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(SharedApi.currentWorld instanceof IDhClientWorld))
|
||||
return false; // don't attempt to render server worlds
|
||||
|
||||
//FIXME: Improve class hierarchy of DhWorld, IClientWorld, IServerWorld to fix all this hard casting
|
||||
// (also in ClientApi)
|
||||
IDhClientLevel level = (IDhClientLevel) dhWorld.getOrLoadLevel(levelWrapper);
|
||||
IDhClientLevel level = clientWorld.getOrLoadClientLevel(levelWrapper);
|
||||
if (level == null)
|
||||
{
|
||||
return false; //Level is not ready yet.
|
||||
}
|
||||
|
||||
if (MC_RENDER.playerHasBlindnessEffect())
|
||||
{
|
||||
@@ -231,7 +240,9 @@ public class RenderUtil
|
||||
}
|
||||
|
||||
if (MC_RENDER.getLightmapWrapper() == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -16,24 +16,19 @@ import java.util.concurrent.CompletableFuture;
|
||||
* Represents an entire world (aka server) and
|
||||
* contains every level in that world.
|
||||
*/
|
||||
public abstract class AbstractDhWorld implements Closeable
|
||||
public abstract class AbstractDhWorld implements IDhWorld, Closeable
|
||||
{
|
||||
protected static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
|
||||
|
||||
public final EWorldEnvironment environment;
|
||||
|
||||
protected AbstractDhWorld(EWorldEnvironment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
public abstract IDhLevel getOrLoadLevel(ILevelWrapper wrapper);
|
||||
|
||||
public abstract IDhLevel getLevel(ILevelWrapper wrapper);
|
||||
public abstract Iterable<? extends IDhLevel> getAllLoadedLevels();
|
||||
|
||||
public abstract void unloadLevel(ILevelWrapper wrapper);
|
||||
public abstract CompletableFuture<Void> saveAndFlush();
|
||||
|
||||
@Override
|
||||
public abstract void close();
|
||||
|
||||
|
||||
|
||||
protected AbstractDhWorld(EWorldEnvironment environment) { this.environment = environment; }
|
||||
|
||||
|
||||
// remove the "throws IOException"
|
||||
@Override
|
||||
public abstract void close();
|
||||
|
||||
}
|
||||
|
||||
@@ -38,12 +38,12 @@ public class DhApiWorldProxy implements IDhApiWorldProxy
|
||||
|
||||
|
||||
@Override
|
||||
public boolean worldLoaded() { return SharedApi.currentWorld != null; }
|
||||
public boolean worldLoaded() { return SharedApi.getAbstractDhWorld() != null; }
|
||||
|
||||
@Override
|
||||
public IDhApiLevelWrapper getSinglePlayerLevel()
|
||||
{
|
||||
if (SharedApi.currentWorld == null)
|
||||
if (SharedApi.getAbstractDhWorld() == null)
|
||||
{
|
||||
throw new IllegalStateException(NO_WORLD_EXCEPTION_STRING);
|
||||
}
|
||||
@@ -63,14 +63,14 @@ public class DhApiWorldProxy implements IDhApiWorldProxy
|
||||
@Override
|
||||
public Iterable<IDhApiLevelWrapper> getAllLoadedLevelWrappers()
|
||||
{
|
||||
if (SharedApi.currentWorld == null)
|
||||
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
|
||||
if (world == null)
|
||||
{
|
||||
throw new IllegalStateException(NO_WORLD_EXCEPTION_STRING);
|
||||
}
|
||||
|
||||
|
||||
ArrayList<IDhApiLevelWrapper> returnList = new ArrayList<>();
|
||||
for (IDhLevel dhLevel : SharedApi.currentWorld.getAllLoadedLevels())
|
||||
for (IDhLevel dhLevel : world.getAllLoadedLevels())
|
||||
{
|
||||
returnList.add(dhLevel.getLevelWrapper());
|
||||
}
|
||||
@@ -80,14 +80,14 @@ public class DhApiWorldProxy implements IDhApiWorldProxy
|
||||
@Override
|
||||
public Iterable<IDhApiLevelWrapper> getAllLoadedLevelsForDimensionType(IDhApiDimensionTypeWrapper dimensionTypeWrapper)
|
||||
{
|
||||
if (SharedApi.currentWorld == null)
|
||||
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
|
||||
if (world == null)
|
||||
{
|
||||
throw new IllegalStateException(NO_WORLD_EXCEPTION_STRING);
|
||||
}
|
||||
|
||||
|
||||
ArrayList<IDhApiLevelWrapper> returnList = new ArrayList<>();
|
||||
for (IDhLevel dhLevel : SharedApi.currentWorld.getAllLoadedLevels())
|
||||
for (IDhLevel dhLevel : world.getAllLoadedLevels())
|
||||
{
|
||||
ILevelWrapper levelWrapper = dhLevel.getLevelWrapper();
|
||||
if (levelWrapper.getDimensionType().equals(dimensionTypeWrapper))
|
||||
@@ -101,16 +101,16 @@ public class DhApiWorldProxy implements IDhApiWorldProxy
|
||||
@Override
|
||||
public Iterable<IDhApiLevelWrapper> getAllLoadedLevelsWithDimensionNameLike(String dimensionName)
|
||||
{
|
||||
if (SharedApi.currentWorld == null)
|
||||
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
|
||||
if (world == null)
|
||||
{
|
||||
throw new IllegalStateException(NO_WORLD_EXCEPTION_STRING);
|
||||
}
|
||||
|
||||
|
||||
String soughtDimName = dimensionName.toLowerCase();
|
||||
|
||||
ArrayList<IDhApiLevelWrapper> returnList = new ArrayList<>();
|
||||
for (IDhLevel dhLevel : SharedApi.currentWorld.getAllLoadedLevels())
|
||||
for (IDhLevel dhLevel : world.getAllLoadedLevels())
|
||||
{
|
||||
ILevelWrapper levelWrapper = dhLevel.getLevelWrapper();
|
||||
String levelDimName = levelWrapper.getDimensionType().getDimensionName().toLowerCase();
|
||||
|
||||
@@ -21,20 +21,28 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
|
||||
private final HashMap<ILevelWrapper, DhClientServerLevel> levelObjMap;
|
||||
private final HashSet<DhClientServerLevel> dhLevels;
|
||||
public final LocalSaveStructure saveStructure;
|
||||
|
||||
public ExecutorService dhTickerThread = LodUtil.makeSingleThreadPool("DHTickerThread", 2);
|
||||
public EventLoop eventLoop = new EventLoop(dhTickerThread, this::_clientTick); //TODO: Rate-limit the loop
|
||||
public F3Screen.DynamicMessage f3Msg;
|
||||
|
||||
public DhClientServerWorld() {
|
||||
public EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick); //TODO: Rate-limit the loop
|
||||
|
||||
public F3Screen.DynamicMessage f3Message;
|
||||
|
||||
|
||||
|
||||
public DhClientServerWorld()
|
||||
{
|
||||
super(EWorldEnvironment.Client_Server);
|
||||
saveStructure = new LocalSaveStructure();
|
||||
levelObjMap = new HashMap<>();
|
||||
dhLevels = new HashSet<>();
|
||||
LOGGER.info("Started DhWorld of type {}", environment);
|
||||
f3Msg = new F3Screen.DynamicMessage(() ->
|
||||
LodUtil.formatLog("{} World with {} levels", environment, dhLevels.size()));
|
||||
this.saveStructure = new LocalSaveStructure();
|
||||
this.levelObjMap = new HashMap<>();
|
||||
this.dhLevels = new HashSet<>();
|
||||
|
||||
LOGGER.info("Started DhWorld of type "+this.environment);
|
||||
|
||||
this.f3Message = new F3Screen.DynamicMessage(() -> LodUtil.formatLog(this.environment+" World with "+this.dhLevels.size()+" levels"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public DhClientServerLevel getOrLoadLevel(ILevelWrapper wrapper)
|
||||
{
|
||||
@@ -56,9 +64,13 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
|
||||
IClientLevelWrapper clientSide = (IClientLevelWrapper) levelWrapper;
|
||||
IServerLevelWrapper serverSide = clientSide.tryGetServerSideWrapper();
|
||||
LodUtil.assertTrue(serverSide != null);
|
||||
|
||||
DhClientServerLevel level = this.levelObjMap.get(serverSide);
|
||||
if (level == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
level.startRenderer(clientSide);
|
||||
return level;
|
||||
});
|
||||
@@ -90,38 +102,42 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
|
||||
}
|
||||
}
|
||||
|
||||
private void _clientTick() {
|
||||
private void _clientTick()
|
||||
{
|
||||
//LOGGER.info("Client world tick with {} levels", levels.size());
|
||||
dhLevels.forEach(DhClientServerLevel::clientTick);
|
||||
this.dhLevels.forEach(DhClientServerLevel::clientTick);
|
||||
}
|
||||
|
||||
public void clientTick() {
|
||||
public void clientTick()
|
||||
{
|
||||
//LOGGER.info("Client world tick");
|
||||
eventLoop.tick();
|
||||
this.eventLoop.tick();
|
||||
}
|
||||
|
||||
public void serverTick() {
|
||||
dhLevels.forEach(DhClientServerLevel::serverTick);
|
||||
}
|
||||
public void serverTick() { this.dhLevels.forEach(DhClientServerLevel::serverTick); }
|
||||
|
||||
public void doWorldGen() {
|
||||
dhLevels.forEach(DhClientServerLevel::doWorldGen);
|
||||
}
|
||||
public void doWorldGen() { this.dhLevels.forEach(DhClientServerLevel::doWorldGen); }
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> saveAndFlush() {
|
||||
return CompletableFuture.allOf(dhLevels.stream().map(DhClientServerLevel::save).toArray(CompletableFuture[]::new));
|
||||
}
|
||||
public CompletableFuture<Void> saveAndFlush()
|
||||
{
|
||||
return CompletableFuture.allOf(this.dhLevels.stream().map(DhClientServerLevel::save).toArray(CompletableFuture[]::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
saveAndFlush().join();
|
||||
for (DhClientServerLevel level : dhLevels) {
|
||||
LOGGER.info("Unloading level " + level.serverLevel.getDimensionType().getDimensionName());
|
||||
level.close();
|
||||
}
|
||||
levelObjMap.clear();
|
||||
eventLoop.close();
|
||||
LOGGER.info("Closed DhWorld of type {}", environment);
|
||||
}
|
||||
public void close()
|
||||
{
|
||||
this.saveAndFlush().join();
|
||||
|
||||
for (DhClientServerLevel level : this.dhLevels)
|
||||
{
|
||||
LOGGER.info("Unloading level " + level.serverLevel.getDimensionType().getDimensionName());
|
||||
level.close();
|
||||
}
|
||||
|
||||
this.levelObjMap.clear();
|
||||
this.eventLoop.close();
|
||||
LOGGER.info("Closed DhWorld of type "+this.environment);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,80 +20,111 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
|
||||
{
|
||||
private final HashMap<IClientLevelWrapper, DhClientLevel> levels;
|
||||
public final ClientOnlySaveStructure saveStructure;
|
||||
|
||||
public ExecutorService dhTickerThread = LodUtil.makeSingleThreadPool("DHTickerThread", 2);
|
||||
public EventLoop eventLoop = new EventLoop(dhTickerThread, this::_clientTick);
|
||||
|
||||
public DhClientWorld() {
|
||||
super(EWorldEnvironment.Client_Only);
|
||||
saveStructure = new ClientOnlySaveStructure();
|
||||
levels = new HashMap<>();
|
||||
LOGGER.info("Started DhWorld of type {}", environment);
|
||||
}
|
||||
|
||||
public EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick);
|
||||
|
||||
|
||||
|
||||
public DhClientWorld()
|
||||
{
|
||||
super(EWorldEnvironment.Client_Only);
|
||||
this.saveStructure = new ClientOnlySaveStructure();
|
||||
this.levels = new HashMap<>();
|
||||
LOGGER.info("Started DhWorld of type "+this.environment);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public DhClientLevel getOrLoadLevel(ILevelWrapper wrapper) {
|
||||
if (!(wrapper instanceof IClientLevelWrapper)) return null;
|
||||
public DhClientLevel getOrLoadLevel(ILevelWrapper wrapper)
|
||||
{
|
||||
if (!(wrapper instanceof IClientLevelWrapper))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return levels.computeIfAbsent((IClientLevelWrapper) wrapper, (w) -> {
|
||||
File level = saveStructure.tryGetOrCreateLevelFolder(wrapper);
|
||||
if (level == null) return null;
|
||||
return new DhClientLevel(saveStructure, w);
|
||||
return this.levels.computeIfAbsent((IClientLevelWrapper) wrapper, (clientLevelWrapper) ->
|
||||
{
|
||||
File level = this.saveStructure.tryGetOrCreateLevelFolder(wrapper);
|
||||
if (level == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new DhClientLevel(this.saveStructure, clientLevelWrapper);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public DhClientLevel getLevel(ILevelWrapper wrapper) {
|
||||
if (!(wrapper instanceof IClientLevelWrapper)) return null;
|
||||
return levels.get(wrapper);
|
||||
public DhClientLevel getLevel(ILevelWrapper wrapper)
|
||||
{
|
||||
if (!(wrapper instanceof IClientLevelWrapper))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.levels.get(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<? extends IDhLevel> getAllLoadedLevels()
|
||||
{
|
||||
return levels.values();
|
||||
}
|
||||
public Iterable<? extends IDhLevel> getAllLoadedLevels() { return this.levels.values(); }
|
||||
|
||||
@Override
|
||||
public void unloadLevel(ILevelWrapper wrapper) {
|
||||
if (!(wrapper instanceof IClientLevelWrapper)) return;
|
||||
if (levels.containsKey(wrapper)) {
|
||||
LOGGER.info("Unloading level {} ", levels.get(wrapper));
|
||||
levels.remove(wrapper).close();
|
||||
public void unloadLevel(ILevelWrapper wrapper)
|
||||
{
|
||||
if (!(wrapper instanceof IClientLevelWrapper))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.levels.containsKey(wrapper))
|
||||
{
|
||||
LOGGER.info("Unloading level "+this.levels.get(wrapper));
|
||||
this.levels.remove(wrapper).close();
|
||||
}
|
||||
}
|
||||
|
||||
private void _clientTick() {
|
||||
int newViewDistance = Config.Client.Graphics.Quality.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH;
|
||||
Iterator<DhClientLevel> iterator = levels.values().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
DhClientLevel level = iterator.next();
|
||||
if (level.tree.blockViewDistance != newViewDistance) {
|
||||
level.close();
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
DetailDistanceUtil.updateSettings();
|
||||
levels.values().forEach(DhClientLevel::clientTick);
|
||||
}
|
||||
private void _clientTick()
|
||||
{
|
||||
int newBlockRenderDistance = Config.Client.Graphics.Quality.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH;
|
||||
|
||||
Iterator<DhClientLevel> iterator = this.levels.values().iterator();
|
||||
while (iterator.hasNext())
|
||||
{
|
||||
DhClientLevel level = iterator.next();
|
||||
if (level.tree.blockRenderDistance != newBlockRenderDistance)
|
||||
{
|
||||
level.close();
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
DetailDistanceUtil.updateSettings();
|
||||
this.levels.values().forEach(DhClientLevel::clientTick);
|
||||
}
|
||||
|
||||
public void clientTick() {
|
||||
eventLoop.tick();
|
||||
public void clientTick() { this.eventLoop.tick(); }
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> saveAndFlush()
|
||||
{
|
||||
return CompletableFuture.allOf(this.levels.values().stream().map(DhClientLevel::save).toArray(CompletableFuture[]::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> saveAndFlush() {
|
||||
return CompletableFuture.allOf(levels.values().stream().map(DhClientLevel::save).toArray(CompletableFuture[]::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
saveAndFlush().join();
|
||||
for (DhClientLevel level : levels.values()) {
|
||||
public void close()
|
||||
{
|
||||
this.saveAndFlush().join();
|
||||
for (DhClientLevel level : this.levels.values())
|
||||
{
|
||||
LOGGER.info("Unloading level " + level.level.getDimensionType().getDimensionName());
|
||||
level.close();
|
||||
}
|
||||
levels.clear();
|
||||
eventLoop.close();
|
||||
LOGGER.info("Closed DhWorld of type {}", environment);
|
||||
|
||||
this.levels.clear();
|
||||
this.eventLoop.close();
|
||||
LOGGER.info("Closed DhWorld of type "+this.environment);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.seibel.lod.core.world;
|
||||
import com.seibel.lod.core.level.DhServerLevel;
|
||||
import com.seibel.lod.core.level.IDhLevel;
|
||||
import com.seibel.lod.core.file.structure.LocalSaveStructure;
|
||||
import com.seibel.lod.core.level.IDhServerLevel;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IServerLevelWrapper;
|
||||
@@ -13,71 +14,91 @@ import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
|
||||
{
|
||||
private final HashMap<IServerLevelWrapper, DhServerLevel> levels;
|
||||
public final LocalSaveStructure saveStructure;
|
||||
|
||||
public DhServerWorld() {
|
||||
super(EWorldEnvironment.Server_Only);
|
||||
saveStructure = new LocalSaveStructure();
|
||||
levels = new HashMap<>();
|
||||
LOGGER.info("Started DhWorld of type {}", environment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DhServerLevel getOrLoadLevel(ILevelWrapper wrapper) {
|
||||
if (!(wrapper instanceof IServerLevelWrapper)) return null;
|
||||
return levels.computeIfAbsent((IServerLevelWrapper) wrapper, (w) -> {
|
||||
File levelFile = saveStructure.tryGetOrCreateLevelFolder(wrapper);
|
||||
LodUtil.assertTrue(levelFile != null);
|
||||
return new DhServerLevel(saveStructure, w);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public DhServerLevel getLevel(ILevelWrapper wrapper) {
|
||||
if (!(wrapper instanceof IServerLevelWrapper)) return null;
|
||||
return levels.get(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<? extends IDhLevel> getAllLoadedLevels()
|
||||
{
|
||||
return levels.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unloadLevel(ILevelWrapper wrapper) {
|
||||
if (!(wrapper instanceof IServerLevelWrapper)) return;
|
||||
if (levels.containsKey(wrapper)) {
|
||||
LOGGER.info("Unloading level {} ", levels.get(wrapper));
|
||||
levels.remove(wrapper).close();
|
||||
}
|
||||
}
|
||||
|
||||
public void serverTick() {
|
||||
levels.values().forEach(DhServerLevel::serverTick);
|
||||
}
|
||||
|
||||
public void doWorldGen() {
|
||||
levels.values().forEach(DhServerLevel::doWorldGen);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> saveAndFlush() {
|
||||
return CompletableFuture.allOf(levels.values().stream().map(DhServerLevel::save).toArray(CompletableFuture[]::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
for (DhServerLevel level : levels.values()) {
|
||||
LOGGER.info("Unloading level " + level.level.getDimensionType().getDimensionName());
|
||||
level.close();
|
||||
}
|
||||
levels.clear();
|
||||
LOGGER.info("Closed DhWorld of type {}", environment);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private final HashMap<IServerLevelWrapper, DhServerLevel> levels;
|
||||
public final LocalSaveStructure saveStructure;
|
||||
|
||||
|
||||
|
||||
public DhServerWorld()
|
||||
{
|
||||
super(EWorldEnvironment.Server_Only);
|
||||
|
||||
this.saveStructure = new LocalSaveStructure();
|
||||
this.levels = new HashMap<>();
|
||||
|
||||
LOGGER.info("Started "+DhServerWorld.class.getSimpleName()+" of type "+this.environment);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public DhServerLevel getOrLoadLevel(ILevelWrapper wrapper)
|
||||
{
|
||||
if (!(wrapper instanceof IServerLevelWrapper))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.levels.computeIfAbsent((IServerLevelWrapper) wrapper, (w) ->
|
||||
{
|
||||
File levelFile = this.saveStructure.tryGetOrCreateLevelFolder(wrapper);
|
||||
LodUtil.assertTrue(levelFile != null);
|
||||
return new DhServerLevel(this.saveStructure, w);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public DhServerLevel getLevel(ILevelWrapper wrapper)
|
||||
{
|
||||
if (!(wrapper instanceof IServerLevelWrapper))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.levels.get(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<? extends IDhLevel> getAllLoadedLevels() { return this.levels.values(); }
|
||||
|
||||
@Override
|
||||
public void unloadLevel(ILevelWrapper wrapper)
|
||||
{
|
||||
if (!(wrapper instanceof IServerLevelWrapper))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.levels.containsKey(wrapper))
|
||||
{
|
||||
LOGGER.info("Unloading level {} ", this.levels.get(wrapper));
|
||||
this.levels.remove(wrapper).close();
|
||||
}
|
||||
}
|
||||
|
||||
public void serverTick() { this.levels.values().forEach(DhServerLevel::serverTick); }
|
||||
|
||||
public void doWorldGen() { this.levels.values().forEach(DhServerLevel::doWorldGen); }
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> saveAndFlush()
|
||||
{
|
||||
return CompletableFuture.allOf(this.levels.values().stream().map(DhServerLevel::save).toArray(CompletableFuture[]::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
for (DhServerLevel level : this.levels.values())
|
||||
{
|
||||
LOGGER.info("Unloading level " + level.level.getDimensionType().getDimensionName());
|
||||
level.close();
|
||||
}
|
||||
|
||||
this.levels.clear();
|
||||
LOGGER.info("Closed DhWorld of type "+this.environment);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package com.seibel.lod.core.world;
|
||||
|
||||
/**
|
||||
* Client_Only,
|
||||
* Client_Server,
|
||||
* Server_Only
|
||||
*/
|
||||
public enum EWorldEnvironment
|
||||
{
|
||||
Client_Only,
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
package com.seibel.lod.core.world;
|
||||
|
||||
public interface IDhClientWorld
|
||||
import com.seibel.lod.core.level.IDhClientLevel;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
|
||||
public interface IDhClientWorld extends IDhWorld
|
||||
{
|
||||
void clientTick();
|
||||
|
||||
default IDhClientLevel getOrLoadClientLevel(ILevelWrapper levelWrapper) { return (IDhClientLevel) this.getOrLoadLevel(levelWrapper); }
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
package com.seibel.lod.core.world;
|
||||
|
||||
public interface IDhServerWorld
|
||||
import com.seibel.lod.core.level.IDhServerLevel;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
|
||||
/** Used both for dedicated server and singleplayer worlds */
|
||||
public interface IDhServerWorld extends IDhWorld
|
||||
{
|
||||
void serverTick();
|
||||
void doWorldGen();
|
||||
|
||||
default IDhServerLevel getOrLoadServerLevel(ILevelWrapper levelWrapper) { return (IDhServerLevel) this.getOrLoadLevel(levelWrapper); }
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.seibel.lod.core.world;
|
||||
|
||||
import com.seibel.lod.core.level.IDhLevel;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface IDhWorld
|
||||
{
|
||||
|
||||
IDhLevel getOrLoadLevel(ILevelWrapper levelWrapper);
|
||||
IDhLevel getLevel(ILevelWrapper wrapper);
|
||||
Iterable<? extends IDhLevel> getAllLoadedLevels();
|
||||
|
||||
void unloadLevel(ILevelWrapper levelWrapper);
|
||||
|
||||
CompletableFuture<Void> saveAndFlush();
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user