LodNodeBuilder and LodNodeGenWorker now support correctly LodDetail
This commit is contained in:
@@ -18,10 +18,14 @@
|
||||
package com.seibel.lod.builders;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import com.seibel.lod.enums.DistanceGenerationMode;
|
||||
import com.seibel.lod.enums.LodDetail;
|
||||
import com.seibel.lod.handlers.LodConfig;
|
||||
import com.seibel.lod.objects.LodChunk;
|
||||
import com.seibel.lod.objects.LodDataPoint;
|
||||
import com.seibel.lod.objects.LodQuadTreeDimension;
|
||||
@@ -46,9 +50,9 @@ import net.minecraft.world.gen.Heightmap;
|
||||
|
||||
/**
|
||||
* This object is in charge of creating Lod
|
||||
* related objects.
|
||||
* related objects.
|
||||
* (specifically: Lod World, Dimension, Region, and Chunk objects)
|
||||
*
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 8-9-2021
|
||||
*/
|
||||
@@ -63,15 +67,13 @@ public class LodNodeBuilder {
|
||||
* Default size of any LOD regions we use
|
||||
*/
|
||||
public int regionWidth = 5;
|
||||
|
||||
|
||||
public LodNodeBuilder()
|
||||
{
|
||||
|
||||
|
||||
public LodNodeBuilder() {
|
||||
|
||||
}
|
||||
|
||||
public void generateLodNodeAsync(IChunk chunk, LodQuadTreeWorld lodWorld, IWorld world)
|
||||
{
|
||||
public void generateLodNodeAsync(IChunk chunk, LodQuadTreeWorld lodWorld, IWorld world) {
|
||||
if (lodWorld == null || !lodWorld.getIsWorldLoaded())
|
||||
return;
|
||||
|
||||
@@ -86,24 +88,20 @@ public class LodNodeBuilder {
|
||||
try {
|
||||
DimensionType dim = world.dimensionType();
|
||||
|
||||
LodQuadTreeNode node = generateLodNodeFromChunk(chunk);
|
||||
List<LodQuadTreeNode> nodeList = generateLodNodeFromChunk(chunk);
|
||||
|
||||
LodQuadTreeDimension lodDim;
|
||||
|
||||
if (lodWorld.getLodDimension(dim) == null)
|
||||
{
|
||||
if (lodWorld.getLodDimension(dim) == null) {
|
||||
lodDim = new LodQuadTreeDimension(dim, lodWorld, regionWidth);
|
||||
lodWorld.addLodDimension(lodDim);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
lodDim = lodWorld.getLodDimension(dim);
|
||||
}
|
||||
|
||||
lodDim.addNode(node);
|
||||
}
|
||||
catch (IllegalArgumentException | NullPointerException e)
|
||||
{
|
||||
for(LodQuadTreeNode node : nodeList) {
|
||||
lodDim.addNode(node);
|
||||
}
|
||||
} catch (IllegalArgumentException | NullPointerException e) {
|
||||
e.printStackTrace();
|
||||
// if the world changes while LODs are being generated
|
||||
// they will throw errors as they try to access things that no longer
|
||||
@@ -121,46 +119,51 @@ public class LodNodeBuilder {
|
||||
*
|
||||
* @throws IllegalArgumentException thrown if either the chunk or world is null.
|
||||
*/
|
||||
public LodQuadTreeNode generateLodNodeFromChunk(IChunk chunk) throws IllegalArgumentException
|
||||
{
|
||||
public List<LodQuadTreeNode> generateLodNodeFromChunk(IChunk chunk) throws IllegalArgumentException {
|
||||
return generateLodNodeFromChunk(chunk, new LodBuilderConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a LodChunk for a chunk in the given world.
|
||||
*
|
||||
* @throws IllegalArgumentException thrown if either the chunk or world is null.
|
||||
* @return
|
||||
* @throws IllegalArgumentException thrown if either the chunk or world is null.
|
||||
*/
|
||||
public LodQuadTreeNode generateLodNodeFromChunk(IChunk chunk, LodBuilderConfig config) throws IllegalArgumentException
|
||||
{
|
||||
public List<LodQuadTreeNode> generateLodNodeFromChunk(IChunk chunk, LodBuilderConfig config) throws IllegalArgumentException {
|
||||
LodDetail detail = LodDetail.QUAD;
|
||||
List<LodQuadTreeNode> lodNodeList = new ArrayList<>();
|
||||
if (chunk == null)
|
||||
throw new IllegalArgumentException("generateLodFromChunk given a null chunk");
|
||||
|
||||
// TODO startX/Z and endX/Z are relative coordinates
|
||||
// getMin/Max appear to return world block coordinates
|
||||
int startX = 0; //chunk.getPos().getMinBlockX();
|
||||
int startZ = 0; //chunk.getPos().getMinBlockZ();
|
||||
int endX = 15; //chunk.getPos().getMaxBlockX();
|
||||
int endZ = 15; //chunk.getPos().getMaxBlockZ();
|
||||
|
||||
Color color = generateLodColorForArea(chunk, config, startX, startZ, endX, endZ);
|
||||
|
||||
short height;
|
||||
short depth;
|
||||
|
||||
if (!config.useHeightmap)
|
||||
{
|
||||
height = determineHeightPointForArea(chunk.getSections(), startX, startZ, endX, endZ);
|
||||
depth = determineBottomPointForArea(chunk.getSections(), startX, startZ, endX, endZ);
|
||||
}
|
||||
else
|
||||
{
|
||||
height = determineHeightPoint(chunk.getOrCreateHeightmapUnprimed(LodChunk.DEFAULT_HEIGHTMAP), startX, startZ, endX, endZ);
|
||||
depth = 0;
|
||||
}
|
||||
|
||||
return new LodQuadTreeNode(LodQuadTreeNode.CHUNK_LEVEL, chunk.getPos().x, chunk.getPos().z, new LodDataPoint(height, depth, color), DistanceGenerationMode.SERVER);
|
||||
throw new IllegalArgumentException("generateLodFromChunk given a null chunk");
|
||||
for (int x = 0; x < detail.dataPointLengthCount; x++) {
|
||||
for (int z = 0; z < detail.dataPointLengthCount; z++) {
|
||||
int startX = detail.startX[x];
|
||||
int startZ = detail.startZ[z];
|
||||
int endX = detail.endX[x];
|
||||
int endZ = detail.endZ[z];
|
||||
|
||||
// TODO startX/Z and endX/Z are relative coordinates
|
||||
// getMin/Max appear to return world block coordinates
|
||||
|
||||
Color color = generateLodColorForArea(chunk, config, startX, startZ, endX, endZ);
|
||||
|
||||
short height;
|
||||
short depth;
|
||||
|
||||
if (!config.useHeightmap) {
|
||||
height = determineHeightPointForArea(chunk.getSections(), startX, startZ, endX, endZ);
|
||||
depth = determineBottomPointForArea(chunk.getSections(), startX, startZ, endX, endZ);
|
||||
} else {
|
||||
height = determineHeightPoint(chunk.getOrCreateHeightmapUnprimed(LodChunk.DEFAULT_HEIGHTMAP), startX, startZ, endX, endZ);
|
||||
depth = 0;
|
||||
}
|
||||
lodNodeList.add(new LodQuadTreeNode((byte) detail.detailLevel,
|
||||
LodUtil.convertLevelPos(chunk.getPos().getMinBlockX() + startX,0,detail.detailLevel),
|
||||
LodUtil.convertLevelPos(chunk.getPos().getMinBlockZ() + startZ,0,detail.detailLevel) ,
|
||||
new LodDataPoint(height, depth, color), DistanceGenerationMode.SERVER));
|
||||
}
|
||||
}
|
||||
|
||||
return lodNodeList;
|
||||
}
|
||||
|
||||
|
||||
@@ -262,9 +265,8 @@ public class LodNodeBuilder {
|
||||
/**
|
||||
* Find the highest point from the Top
|
||||
*/
|
||||
private short determineHeightPoint(Heightmap heightmap,
|
||||
int startX, int startZ, int endX, int endZ)
|
||||
{
|
||||
private short determineHeightPoint(Heightmap heightmap,
|
||||
int startX, int startZ, int endX, int endZ) {
|
||||
short highest = 0;
|
||||
for (int x = startX; x < endX; x++) {
|
||||
for (int z = startZ; z < endZ; z++) {
|
||||
@@ -278,221 +280,195 @@ public class LodNodeBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the color for the given chunk using biome
|
||||
* water color, foliage color, and grass color.
|
||||
*
|
||||
* @param config_useSolidBlocksInColorGen <br>
|
||||
* If true we look down from the top of the <br>
|
||||
* chunk until we find a non-invisible block, and then use <br>
|
||||
* its color. If false we generate the color immediately for <br>
|
||||
* each x and z.
|
||||
* @param config_useBiomeColors <br>
|
||||
* If true use biome foliage, water, and grass colors, <br>
|
||||
* otherwise only use the block's material color
|
||||
*/
|
||||
private Color generateLodColorForArea(IChunk chunk, LodBuilderConfig config, int startX, int startZ, int endX, int endZ)
|
||||
{
|
||||
ChunkSection[] chunkSections = chunk.getSections();
|
||||
|
||||
int numbOfBlocks = 0;
|
||||
int red = 0;
|
||||
int green = 0;
|
||||
int blue = 0;
|
||||
|
||||
|
||||
for(int x = startX; x < endX; x++)
|
||||
{
|
||||
for(int z = startZ; z < endZ; z++)
|
||||
{
|
||||
boolean foundBlock = false;
|
||||
|
||||
// go top down
|
||||
for(int i = chunkSections.length - 1; !foundBlock && i >= 0; i--)
|
||||
{
|
||||
if( !foundBlock && (chunkSections[i] != null || !config.useSolidBlocksInColorGen))
|
||||
{
|
||||
for(int y = CHUNK_SECTION_HEIGHT - 1; !foundBlock && y >= 0; y--)
|
||||
{
|
||||
int colorInt = 0;
|
||||
BlockState blockState = null;
|
||||
|
||||
if (chunkSections[i] != null)
|
||||
{
|
||||
blockState = chunkSections[i].getBlockState(x, y, z);
|
||||
colorInt = blockState.materialColor.col;
|
||||
}
|
||||
|
||||
if(colorInt == 0 && config.useSolidBlocksInColorGen)
|
||||
{
|
||||
// skip air or invisible blocks
|
||||
continue;
|
||||
}
|
||||
|
||||
if (config.useBiomeColors)
|
||||
{
|
||||
// I have no idea why I need to bit shift to the right, but
|
||||
// if I don't the biomes don't show up correctly.
|
||||
Biome biome = chunk.getBiomes().getNoiseBiome(x >> 2, y + 1 * chunkSections.length >> 2, z >> 2);
|
||||
colorInt = getColorForBiome(x,z, biome);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// the bit shift is equivalent to dividing by 4
|
||||
Biome biome = chunk.getBiomes().getNoiseBiome(x >> 2, y + i * chunkSections.length >> 2, z >> 2);
|
||||
colorInt = getColorForBlock(x,z, blockState, biome);
|
||||
}
|
||||
|
||||
|
||||
Color c = LodUtil.intToColor(colorInt);
|
||||
|
||||
red += c.getRed();
|
||||
green += c.getGreen();
|
||||
blue += c.getBlue();
|
||||
|
||||
numbOfBlocks++;
|
||||
|
||||
|
||||
// we found a valid block, skip to the
|
||||
// next x and z
|
||||
foundBlock = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(numbOfBlocks == 0)
|
||||
numbOfBlocks = 1;
|
||||
|
||||
red /= numbOfBlocks;
|
||||
green /= numbOfBlocks;
|
||||
blue /= numbOfBlocks;
|
||||
|
||||
return new Color(red, green, blue);
|
||||
}
|
||||
* Generate the color for the given chunk using biome
|
||||
* water color, foliage color, and grass color.
|
||||
*
|
||||
* @param config_useSolidBlocksInColorGen <br>
|
||||
* If true we look down from the top of the <br>
|
||||
* chunk until we find a non-invisible block, and then use <br>
|
||||
* its color. If false we generate the color immediately for <br>
|
||||
* each x and z.
|
||||
* @param config_useBiomeColors <br>
|
||||
* If true use biome foliage, water, and grass colors, <br>
|
||||
* otherwise only use the block's material color
|
||||
*/
|
||||
private Color generateLodColorForArea(IChunk chunk, LodBuilderConfig config, int startX, int startZ, int endX, int endZ) {
|
||||
ChunkSection[] chunkSections = chunk.getSections();
|
||||
|
||||
int numbOfBlocks = 0;
|
||||
int red = 0;
|
||||
int green = 0;
|
||||
int blue = 0;
|
||||
|
||||
|
||||
|
||||
for (int x = startX; x < endX; x++) {
|
||||
for (int z = startZ; z < endZ; z++) {
|
||||
boolean foundBlock = false;
|
||||
|
||||
/**
|
||||
// go top down
|
||||
for (int i = chunkSections.length - 1; !foundBlock && i >= 0; i--) {
|
||||
if (!foundBlock && (chunkSections[i] != null || !config.useSolidBlocksInColorGen)) {
|
||||
for (int y = CHUNK_SECTION_HEIGHT - 1; !foundBlock && y >= 0; y--) {
|
||||
int colorInt = 0;
|
||||
BlockState blockState = null;
|
||||
|
||||
if (chunkSections[i] != null) {
|
||||
blockState = chunkSections[i].getBlockState(x, y, z);
|
||||
colorInt = blockState.materialColor.col;
|
||||
}
|
||||
|
||||
if (colorInt == 0 && config.useSolidBlocksInColorGen) {
|
||||
// skip air or invisible blocks
|
||||
continue;
|
||||
}
|
||||
|
||||
if (config.useBiomeColors) {
|
||||
// I have no idea why I need to bit shift to the right, but
|
||||
// if I don't the biomes don't show up correctly.
|
||||
Biome biome = chunk.getBiomes().getNoiseBiome(x >> 2, y + 1 * chunkSections.length >> 2, z >> 2);
|
||||
colorInt = getColorForBiome(x, z, biome);
|
||||
} else {
|
||||
|
||||
// the bit shift is equivalent to dividing by 4
|
||||
Biome biome = chunk.getBiomes().getNoiseBiome(x >> 2, y + i * chunkSections.length >> 2, z >> 2);
|
||||
colorInt = getColorForBlock(x, z, blockState, biome);
|
||||
}
|
||||
|
||||
|
||||
Color c = LodUtil.intToColor(colorInt);
|
||||
|
||||
red += c.getRed();
|
||||
green += c.getGreen();
|
||||
blue += c.getBlue();
|
||||
|
||||
numbOfBlocks++;
|
||||
|
||||
|
||||
// we found a valid block, skip to the
|
||||
// next x and z
|
||||
foundBlock = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (numbOfBlocks == 0)
|
||||
numbOfBlocks = 1;
|
||||
|
||||
red /= numbOfBlocks;
|
||||
green /= numbOfBlocks;
|
||||
blue /= numbOfBlocks;
|
||||
|
||||
return new Color(red, green, blue);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a color int for a given block.
|
||||
*/
|
||||
private int getColorForBlock(int x, int z, BlockState blockState, Biome biome)
|
||||
{
|
||||
int colorInt = 0;
|
||||
|
||||
// block special cases
|
||||
if (blockState == Blocks.AIR.defaultBlockState())
|
||||
{
|
||||
Color tmp = LodUtil.intToColor(biome.getGrassColor(x, z));
|
||||
tmp = tmp.darker();
|
||||
colorInt = LodUtil.colorToInt(tmp);
|
||||
}
|
||||
else if (blockState == Blocks.MYCELIUM.defaultBlockState())
|
||||
{
|
||||
colorInt = MaterialColor.COLOR_LIGHT_GRAY.col;
|
||||
}
|
||||
|
||||
// plant life
|
||||
else if (blockState.getBlock() instanceof LeavesBlock)
|
||||
{
|
||||
Color leafColor = LodUtil.intToColor(biome.getFoliageColor()).darker();
|
||||
colorInt = LodUtil.colorToInt(leafColor);
|
||||
}
|
||||
else if (blockState.getBlock() instanceof GrassBlock ||
|
||||
blockState.getBlock() instanceof AbstractPlantBlock ||
|
||||
blockState.getBlock() instanceof BushBlock ||
|
||||
blockState.getBlock() instanceof IGrowable)
|
||||
{
|
||||
Color tmp = LodUtil.intToColor(biome.getGrassColor(x, z));
|
||||
tmp = tmp.darker();
|
||||
colorInt = LodUtil.colorToInt(tmp);
|
||||
}
|
||||
|
||||
// water
|
||||
else if (blockState.getBlock() == Blocks.WATER)
|
||||
{
|
||||
colorInt = biome.getWaterColor();
|
||||
}
|
||||
|
||||
// everything else
|
||||
else
|
||||
{
|
||||
colorInt = blockState.materialColor.col;
|
||||
}
|
||||
|
||||
return colorInt;
|
||||
}
|
||||
|
||||
private int getColorForBlock(int x, int z, BlockState blockState, Biome biome) {
|
||||
int colorInt = 0;
|
||||
|
||||
// block special cases
|
||||
if (blockState == Blocks.AIR.defaultBlockState()) {
|
||||
Color tmp = LodUtil.intToColor(biome.getGrassColor(x, z));
|
||||
tmp = tmp.darker();
|
||||
colorInt = LodUtil.colorToInt(tmp);
|
||||
} else if (blockState == Blocks.MYCELIUM.defaultBlockState()) {
|
||||
colorInt = MaterialColor.COLOR_LIGHT_GRAY.col;
|
||||
}
|
||||
|
||||
// plant life
|
||||
else if (blockState.getBlock() instanceof LeavesBlock) {
|
||||
Color leafColor = LodUtil.intToColor(biome.getFoliageColor()).darker();
|
||||
colorInt = LodUtil.colorToInt(leafColor);
|
||||
} else if (blockState.getBlock() instanceof GrassBlock ||
|
||||
blockState.getBlock() instanceof AbstractPlantBlock ||
|
||||
blockState.getBlock() instanceof BushBlock ||
|
||||
blockState.getBlock() instanceof IGrowable) {
|
||||
Color tmp = LodUtil.intToColor(biome.getGrassColor(x, z));
|
||||
tmp = tmp.darker();
|
||||
colorInt = LodUtil.colorToInt(tmp);
|
||||
}
|
||||
|
||||
// water
|
||||
else if (blockState.getBlock() == Blocks.WATER) {
|
||||
colorInt = biome.getWaterColor();
|
||||
}
|
||||
|
||||
// everything else
|
||||
else {
|
||||
colorInt = blockState.materialColor.col;
|
||||
}
|
||||
|
||||
return colorInt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a color int for the given biome.
|
||||
*/
|
||||
private int getColorForBiome(int x, int z, Biome biome)
|
||||
{
|
||||
int colorInt = 0;
|
||||
|
||||
switch(biome.getBiomeCategory())
|
||||
{
|
||||
|
||||
case NETHER:
|
||||
colorInt = Blocks.BEDROCK.defaultBlockState().materialColor.col;
|
||||
break;
|
||||
|
||||
case THEEND:
|
||||
colorInt = Blocks.END_STONE.defaultBlockState().materialColor.col;
|
||||
break;
|
||||
|
||||
|
||||
|
||||
case BEACH:
|
||||
case DESERT:
|
||||
colorInt = Blocks.SAND.defaultBlockState().materialColor.col;
|
||||
break;
|
||||
|
||||
case EXTREME_HILLS:
|
||||
colorInt = Blocks.STONE.defaultMaterialColor().col;
|
||||
break;
|
||||
|
||||
case MUSHROOM:
|
||||
colorInt = MaterialColor.COLOR_LIGHT_GRAY.col;
|
||||
break;
|
||||
|
||||
case ICY:
|
||||
colorInt = Blocks.SNOW.defaultMaterialColor().col;
|
||||
break;
|
||||
|
||||
case MESA:
|
||||
colorInt = Blocks.RED_SAND.defaultMaterialColor().col;
|
||||
break;
|
||||
|
||||
case OCEAN:
|
||||
case RIVER:
|
||||
colorInt = biome.getWaterColor();
|
||||
break;
|
||||
|
||||
|
||||
case NONE:
|
||||
case FOREST:
|
||||
case TAIGA:
|
||||
case JUNGLE:
|
||||
case PLAINS:
|
||||
case SAVANNA:
|
||||
case SWAMP:
|
||||
default:
|
||||
Color tmp = LodUtil.intToColor(biome.getGrassColor(x, z));
|
||||
tmp = tmp.darker();
|
||||
colorInt = LodUtil.colorToInt(tmp);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
|
||||
return colorInt;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private int getColorForBiome(int x, int z, Biome biome) {
|
||||
int colorInt = 0;
|
||||
|
||||
switch (biome.getBiomeCategory()) {
|
||||
|
||||
case NETHER:
|
||||
colorInt = Blocks.BEDROCK.defaultBlockState().materialColor.col;
|
||||
break;
|
||||
|
||||
case THEEND:
|
||||
colorInt = Blocks.END_STONE.defaultBlockState().materialColor.col;
|
||||
break;
|
||||
|
||||
|
||||
case BEACH:
|
||||
case DESERT:
|
||||
colorInt = Blocks.SAND.defaultBlockState().materialColor.col;
|
||||
break;
|
||||
|
||||
case EXTREME_HILLS:
|
||||
colorInt = Blocks.STONE.defaultMaterialColor().col;
|
||||
break;
|
||||
|
||||
case MUSHROOM:
|
||||
colorInt = MaterialColor.COLOR_LIGHT_GRAY.col;
|
||||
break;
|
||||
|
||||
case ICY:
|
||||
colorInt = Blocks.SNOW.defaultMaterialColor().col;
|
||||
break;
|
||||
|
||||
case MESA:
|
||||
colorInt = Blocks.RED_SAND.defaultMaterialColor().col;
|
||||
break;
|
||||
|
||||
case OCEAN:
|
||||
case RIVER:
|
||||
colorInt = biome.getWaterColor();
|
||||
break;
|
||||
|
||||
|
||||
case NONE:
|
||||
case FOREST:
|
||||
case TAIGA:
|
||||
case JUNGLE:
|
||||
case PLAINS:
|
||||
case SAVANNA:
|
||||
case SWAMP:
|
||||
default:
|
||||
Color tmp = LodUtil.intToColor(biome.getGrassColor(x, z));
|
||||
tmp = tmp.darker();
|
||||
colorInt = LodUtil.colorToInt(tmp);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
|
||||
return colorInt;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the layer between the given X, Z, and dataIndex
|
||||
* values a valid LOD point?
|
||||
@@ -500,32 +476,20 @@ public class LodNodeBuilder {
|
||||
private boolean isLayerValidLodPoint(
|
||||
ChunkSection[] chunkSections,
|
||||
int sectionIndex, int y,
|
||||
int x, int z)
|
||||
{
|
||||
if (chunkSections[sectionIndex] == null)
|
||||
{
|
||||
int x, int z) {
|
||||
if (chunkSections[sectionIndex] == null) {
|
||||
// this section doesn't have any blocks,
|
||||
// it is not a valid section
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
if (chunkSections[sectionIndex].getBlockState(x, y, z) != null &&
|
||||
chunkSections[sectionIndex].getBlockState(x, y, z).getBlock() != Blocks.AIR)
|
||||
{
|
||||
chunkSections[sectionIndex].getBlockState(x, y, z).getBlock() != Blocks.AIR) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user