Replace Matrix4f (MC) with Mat4f (ours) in LodRenderer
This commit is contained in:
@@ -1,9 +1,14 @@
|
||||
package com.seibel.lod.api;
|
||||
|
||||
import com.seibel.lod.LodMain;
|
||||
import com.seibel.lod.objects.rending.Mat4f;
|
||||
|
||||
import net.minecraft.util.math.vector.Matrix4f;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 11-11-2021
|
||||
*/
|
||||
public class ClientApi
|
||||
{
|
||||
|
||||
@@ -15,9 +20,12 @@ public class ClientApi
|
||||
|
||||
|
||||
|
||||
public void renderLods(Matrix4f mcModelViewMatrix, float partialTicks)
|
||||
public static void renderLods(Mat4f mcModelViewMatrix, float partialTicks)
|
||||
{
|
||||
LodMain.client_proxy.renderLods(mcModelViewMatrix, partialTicks);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@ import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
import com.seibel.lod.LodMain;
|
||||
import com.seibel.lod.api.ClientApi;
|
||||
import com.seibel.lod.objects.rending.Mat4f;
|
||||
import com.seibel.lod.wrappers.McObjectConverter;
|
||||
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.WorldRenderer;
|
||||
@@ -59,6 +61,9 @@ public class MixinWorldRenderer
|
||||
// only render if LODs are enabled and
|
||||
// only render before solid blocks
|
||||
if (renderType.equals(RenderType.solid()))
|
||||
LodMain.client_proxy.renderLods(matrixStackIn, previousPartialTicks);
|
||||
{
|
||||
Mat4f mcModelViewMatrix = McObjectConverter.Convert(matrixStackIn.last().pose());
|
||||
ClientApi.renderLods(mcModelViewMatrix, previousPartialTicks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,33 +2,31 @@ package com.seibel.lod.objects.rending;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import net.minecraft.util.math.vector.Vector3f;
|
||||
|
||||
/**
|
||||
* A (almost) exact copy of Minecraft's 1.16.5
|
||||
* implementation of a 4x4 matrix.
|
||||
*
|
||||
* @author James Seibel
|
||||
*
|
||||
* @version 11-11-2021
|
||||
*/
|
||||
public class Mat4f
|
||||
{
|
||||
protected float m00;
|
||||
protected float m01;
|
||||
protected float m02;
|
||||
protected float m03;
|
||||
protected float m10;
|
||||
protected float m11;
|
||||
protected float m12;
|
||||
protected float m13;
|
||||
protected float m20;
|
||||
protected float m21;
|
||||
protected float m22;
|
||||
protected float m23;
|
||||
protected float m30;
|
||||
protected float m31;
|
||||
protected float m32;
|
||||
protected float m33;
|
||||
private float m00;
|
||||
private float m01;
|
||||
private float m02;
|
||||
private float m03;
|
||||
private float m10;
|
||||
private float m11;
|
||||
private float m12;
|
||||
private float m13;
|
||||
private float m20;
|
||||
private float m21;
|
||||
private float m22;
|
||||
private float m23;
|
||||
private float m30;
|
||||
private float m31;
|
||||
private float m32;
|
||||
private float m33;
|
||||
|
||||
|
||||
public Mat4f()
|
||||
@@ -377,20 +375,18 @@ public class Mat4f
|
||||
this.m33 *= scalar;
|
||||
}
|
||||
|
||||
/* not currently needed/implemented
|
||||
* Also the parameter names should be double checked as they may be incorrect
|
||||
public static Matrix4Float perspective(double fov, float heightWidthRatio, float nearClipPlane, float farClipPlane)
|
||||
public static Mat4f perspective(double fov, float widthHeightRatio, float nearClipPlane, float farClipPlane)
|
||||
{
|
||||
float f = (float) (1.0D / Math.tan(fov * ((float) Math.PI / 180F) / 2.0D));
|
||||
Matrix4Float matrix = new Matrix4Float();
|
||||
matrix.m00 = f / heightWidthRatio;
|
||||
Mat4f matrix = new Mat4f();
|
||||
matrix.m00 = f / widthHeightRatio;
|
||||
matrix.m11 = f;
|
||||
matrix.m22 = (farClipPlane + nearClipPlane) / (nearClipPlane - farClipPlane);
|
||||
matrix.m32 = -1.0F;
|
||||
matrix.m23 = 2.0F * farClipPlane * nearClipPlane / (nearClipPlane - farClipPlane);
|
||||
return matrix;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/* not currently needed/implemented
|
||||
* Also the parameter names should be double checked as they may be incorrect
|
||||
@@ -409,11 +405,21 @@ public class Mat4f
|
||||
}
|
||||
*/
|
||||
|
||||
public void translate(Vector3f vec)
|
||||
/**
|
||||
* TODO: what kind of translation is this?
|
||||
* and how is this different from "multiplyTranslationMatrix"?
|
||||
*/
|
||||
public void translate(Vec3f vec)
|
||||
{
|
||||
this.m03 += vec.x();
|
||||
this.m13 += vec.y();
|
||||
this.m23 += vec.z();
|
||||
this.m03 += vec.x;
|
||||
this.m13 += vec.y;
|
||||
this.m23 += vec.z;
|
||||
}
|
||||
|
||||
/** originally "translate" from Minecraft's MatrixStack */
|
||||
public void multiplyTranslationMatrix(double x, double y, double z)
|
||||
{
|
||||
multiply(createTranslateMatrix((float)x, (float)y, (float)z));
|
||||
}
|
||||
|
||||
public Mat4f copy()
|
||||
@@ -466,6 +472,11 @@ public class Mat4f
|
||||
m33 = values[15];
|
||||
}
|
||||
|
||||
public Mat4f(FloatBuffer buffer)
|
||||
{
|
||||
this(buffer.array());
|
||||
}
|
||||
|
||||
public void set(Mat4f mat)
|
||||
{
|
||||
this.m00 = mat.m00;
|
||||
|
||||
@@ -2,16 +2,23 @@ package com.seibel.lod.objects.rending;
|
||||
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
|
||||
/**
|
||||
* A (almost) exact copy of Minecraft's 1.16.5
|
||||
* implementation of a 3 element vector.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 11-11-2021
|
||||
*/
|
||||
public class Vec3f
|
||||
{
|
||||
public static Vec3f XN = new Vec3f(-1.0F, 0.0F, 0.0F);
|
||||
public static Vec3f XP = new Vec3f(1.0F, 0.0F, 0.0F);
|
||||
public static Vec3f YN = new Vec3f(0.0F, -1.0F, 0.0F);
|
||||
public static Vec3f YP = new Vec3f(0.0F, 1.0F, 0.0F);
|
||||
public static Vec3f ZN = new Vec3f(0.0F, 0.0F, -1.0F);
|
||||
public static Vec3f ZP = new Vec3f(0.0F, 0.0F, 1.0F);
|
||||
public static Vec3f XNeg = new Vec3f(-1.0F, 0.0F, 0.0F);
|
||||
public static Vec3f XPos = new Vec3f(1.0F, 0.0F, 0.0F);
|
||||
public static Vec3f YNeg = new Vec3f(0.0F, -1.0F, 0.0F);
|
||||
public static Vec3f YPos = new Vec3f(0.0F, 1.0F, 0.0F);
|
||||
public static Vec3f ZNeg = new Vec3f(0.0F, 0.0F, -1.0F);
|
||||
public static Vec3f ZPos = new Vec3f(0.0F, 0.0F, 1.0F);
|
||||
|
||||
|
||||
public float x;
|
||||
public float y;
|
||||
public float z;
|
||||
@@ -130,7 +137,7 @@ public class Vec3f
|
||||
}
|
||||
else
|
||||
{
|
||||
float f1 = MathHelper.fastInvSqrt(squaredSum);
|
||||
float f1 = LodUtil.fastInvSqrt(squaredSum);
|
||||
this.x *= f1;
|
||||
this.y *= f1;
|
||||
this.z *= f1;
|
||||
|
||||
@@ -24,7 +24,6 @@ import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
import com.seibel.lod.builders.bufferBuilding.LodBufferBuilder;
|
||||
import com.seibel.lod.builders.lodBuilding.LodBuilder;
|
||||
import com.seibel.lod.builders.worldGeneration.LodGenWorker;
|
||||
@@ -34,6 +33,7 @@ import com.seibel.lod.enums.DistanceGenerationMode;
|
||||
import com.seibel.lod.objects.lod.LodDimension;
|
||||
import com.seibel.lod.objects.lod.LodWorld;
|
||||
import com.seibel.lod.objects.lod.RegionPos;
|
||||
import com.seibel.lod.objects.rending.Mat4f;
|
||||
import com.seibel.lod.render.LodRenderer;
|
||||
import com.seibel.lod.util.DataPointUtil;
|
||||
import com.seibel.lod.util.DetailDistanceUtil;
|
||||
@@ -43,7 +43,6 @@ import com.seibel.lod.wrappers.MinecraftWrapper;
|
||||
import com.seibel.lod.wrappers.Chunk.ChunkWrapper;
|
||||
|
||||
import net.minecraft.profiler.IProfiler;
|
||||
import net.minecraft.util.math.vector.Matrix4f;
|
||||
import net.minecraft.util.text.StringTextComponent;
|
||||
import net.minecraftforge.client.event.InputEvent;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
@@ -101,7 +100,7 @@ public class ClientProxy
|
||||
//==============//
|
||||
|
||||
/** Do any setup that is required to draw LODs and then tell the LodRenderer to draw. */
|
||||
public void renderLods(MatrixStack mcModelViewMatrix, float partialTicks)
|
||||
public void renderLods(Mat4f mcModelViewMatrix, float partialTicks)
|
||||
{
|
||||
// comment out when creating a release
|
||||
// applyConfigOverrides();
|
||||
@@ -135,7 +134,7 @@ public class ClientProxy
|
||||
// reset it after drawing the LODs
|
||||
float[] mcProjMatrixRaw = new float[16];
|
||||
GL15.glGetFloatv(GL15.GL_PROJECTION_MATRIX, mcProjMatrixRaw);
|
||||
Matrix4f mcProjectionMatrix = new Matrix4f(mcProjMatrixRaw);
|
||||
Mat4f mcProjectionMatrix = new Mat4f(mcProjMatrixRaw);
|
||||
// OpenGl outputs their matrices in col,row form instead of row,col
|
||||
// (or maybe vice versa I have no idea :P)
|
||||
mcProjectionMatrix.transpose();
|
||||
|
||||
@@ -26,7 +26,6 @@ import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.NVFogDistance;
|
||||
|
||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
import com.seibel.lod.builders.bufferBuilding.LodBufferBuilder;
|
||||
import com.seibel.lod.builders.bufferBuilding.LodBufferBuilder.VertexBuffersAndOffset;
|
||||
import com.seibel.lod.config.LodConfig;
|
||||
@@ -38,6 +37,7 @@ import com.seibel.lod.enums.GpuUploadMethod;
|
||||
import com.seibel.lod.handlers.ReflectionHandler;
|
||||
import com.seibel.lod.objects.lod.LodDimension;
|
||||
import com.seibel.lod.objects.lod.RegionPos;
|
||||
import com.seibel.lod.objects.rending.Mat4f;
|
||||
import com.seibel.lod.objects.rending.NearFarFogSettings;
|
||||
import com.seibel.lod.proxy.ClientProxy;
|
||||
import com.seibel.lod.proxy.GlProxy;
|
||||
@@ -45,6 +45,7 @@ import com.seibel.lod.render.shader.LodShaderProgram;
|
||||
import com.seibel.lod.util.DetailDistanceUtil;
|
||||
import com.seibel.lod.util.LevelPosUtil;
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
import com.seibel.lod.wrappers.McObjectConverter;
|
||||
import com.seibel.lod.wrappers.MinecraftWrapper;
|
||||
import com.seibel.lod.wrappers.Block.BlockPosWrapper;
|
||||
import com.seibel.lod.wrappers.Chunk.ChunkPosWrapper;
|
||||
@@ -55,7 +56,6 @@ import net.minecraft.client.renderer.vertex.VertexBuffer;
|
||||
import net.minecraft.potion.Effects;
|
||||
import net.minecraft.profiler.IProfiler;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.util.math.vector.Matrix4f;
|
||||
import net.minecraft.util.math.vector.Vector3d;
|
||||
import net.minecraft.util.math.vector.Vector3f;
|
||||
|
||||
@@ -157,7 +157,7 @@ public class LodRenderer
|
||||
* @param mcProjectionMatrix
|
||||
* @param partialTicks how far into the current tick this method was called.
|
||||
*/
|
||||
public void drawLODs(LodDimension lodDim, MatrixStack mcModelViewMatrix, Matrix4f mcProjectionMatrix, float partialTicks, IProfiler newProfiler)
|
||||
public void drawLODs(LodDimension lodDim, Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfiler newProfiler)
|
||||
{
|
||||
//=================================//
|
||||
// determine if LODs should render //
|
||||
@@ -248,7 +248,7 @@ public class LodRenderer
|
||||
int currentProgram = GL20.glGetInteger(GL20.GL_CURRENT_PROGRAM);
|
||||
|
||||
|
||||
Matrix4f modelViewMatrix = offsetTheModelViewMatrix(mcModelViewMatrix, partialTicks);
|
||||
Mat4f modelViewMatrix = offsetTheModelViewMatrix(mcModelViewMatrix, partialTicks);
|
||||
vanillaBlockRenderedDistance = mc.getRenderDistance() * LodUtil.CHUNK_WIDTH;
|
||||
// required for setupFog and setupProjectionMatrix
|
||||
if (mc.getClientLevel().dimensionType().hasCeiling())
|
||||
@@ -257,7 +257,7 @@ public class LodRenderer
|
||||
farPlaneBlockDistance = LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH;
|
||||
|
||||
|
||||
Matrix4f projectionMatrix = createProjectionMatrix(mcProjectionMatrix, vanillaBlockRenderedDistance, partialTicks);
|
||||
Mat4f projectionMatrix = createProjectionMatrix(mcProjectionMatrix, vanillaBlockRenderedDistance, partialTicks);
|
||||
|
||||
|
||||
// commented out until we can add shaders to handle lighting
|
||||
@@ -543,12 +543,8 @@ public class LodRenderer
|
||||
* (since AxisAlignedBoundingBoxes (LODs) use doubles and thus have a higher
|
||||
* accuracy vs the model view matrix, which only uses floats)
|
||||
*/
|
||||
private Matrix4f offsetTheModelViewMatrix(MatrixStack mcModelViewMatrix, float partialTicks)
|
||||
private Mat4f offsetTheModelViewMatrix(Mat4f mcModelViewMatrix, float partialTicks)
|
||||
{
|
||||
// duplicate the last matrix
|
||||
mcModelViewMatrix.pushPose();
|
||||
|
||||
|
||||
// get all relevant camera info
|
||||
ActiveRenderInfo camera = mc.getGameRenderer().getMainCamera();
|
||||
Vector3d projectedView = camera.getPosition();
|
||||
@@ -559,16 +555,9 @@ public class LodRenderer
|
||||
BlockPosWrapper bufferPos = vbosCenter.getWorldPosition();
|
||||
double xDiff = projectedView.x - bufferPos.getX();
|
||||
double zDiff = projectedView.z - bufferPos.getZ();
|
||||
mcModelViewMatrix.translate(-xDiff, -projectedView.y, -zDiff);
|
||||
mcModelViewMatrix.multiplyTranslationMatrix(-xDiff, -projectedView.y, -zDiff);
|
||||
|
||||
|
||||
|
||||
// get the modified model view matrix
|
||||
Matrix4f lodModelViewMatrix = mcModelViewMatrix.last().pose();
|
||||
// remove the lod ModelViewMatrix
|
||||
mcModelViewMatrix.popPose();
|
||||
|
||||
return lodModelViewMatrix;
|
||||
return mcModelViewMatrix;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -577,30 +566,30 @@ public class LodRenderer
|
||||
* @param vanillaBlockRenderedDistance Minecraft's vanilla far plane distance
|
||||
* @param partialTicks how many ticks into the frame we are
|
||||
*/
|
||||
private Matrix4f createProjectionMatrix(Matrix4f currentProjectionMatrix, float vanillaBlockRenderedDistance, float partialTicks)
|
||||
private Mat4f createProjectionMatrix(Mat4f currentProjectionMatrix, float vanillaBlockRenderedDistance, float partialTicks)
|
||||
{
|
||||
// create the new projection matrix
|
||||
Matrix4f lodProj =
|
||||
Matrix4f.perspective(
|
||||
getFov(partialTicks, true),
|
||||
(float) this.mc.getWindow().getScreenWidth() / (float) this.mc.getWindow().getScreenHeight(),
|
||||
LodConfig.CLIENT.graphics.advancedGraphicsOption.useExtendedNearClipPlane.get() ? vanillaBlockRenderedDistance / 5 : 1,
|
||||
farPlaneBlockDistance * LodUtil.CHUNK_WIDTH / 2);
|
||||
|
||||
Mat4f lodProj = Mat4f.perspective(
|
||||
getFov(partialTicks, true),
|
||||
(float) this.mc.getWindow().getScreenWidth() / (float) this.mc.getWindow().getScreenHeight(),
|
||||
LodConfig.CLIENT.graphics.advancedGraphicsOption.useExtendedNearClipPlane.get() ? vanillaBlockRenderedDistance / 5 : 1,
|
||||
farPlaneBlockDistance * LodUtil.CHUNK_WIDTH / 2);
|
||||
|
||||
|
||||
// get Minecraft's un-edited projection matrix
|
||||
// (this is before it is zoomed, distorted, etc.)
|
||||
Matrix4f defaultMcProj = mc.getGameRenderer().getProjectionMatrix(mc.getGameRenderer().getMainCamera(), partialTicks, true);
|
||||
Mat4f defaultMcProj = McObjectConverter.Convert(mc.getGameRenderer().getProjectionMatrix(mc.getGameRenderer().getMainCamera(), partialTicks, true));
|
||||
// true here means use "use fov setting" (probably)
|
||||
|
||||
|
||||
// this logic strips away the defaultMcProj matrix, so we
|
||||
// can get the distortionMatrix, which represents all
|
||||
// transformations, zooming, distortions, etc. done
|
||||
// to Minecraft's Projection matrix
|
||||
Matrix4f defaultMcProjInv = defaultMcProj.copy();
|
||||
Mat4f defaultMcProjInv = defaultMcProj.copy();
|
||||
defaultMcProjInv.invert();
|
||||
|
||||
Matrix4f distortionMatrix = defaultMcProjInv.copy();
|
||||
Mat4f distortionMatrix = defaultMcProjInv.copy();
|
||||
distortionMatrix.multiply(currentProjectionMatrix);
|
||||
|
||||
|
||||
@@ -611,7 +600,6 @@ public class LodRenderer
|
||||
return lodProj;
|
||||
}
|
||||
|
||||
|
||||
///** setup the lighting to be used for the LODs */
|
||||
/*private void setupLighting(LodDimension lodDimension, float partialTicks)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ import java.nio.FloatBuffer;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
|
||||
import net.minecraft.util.math.vector.Matrix4f;
|
||||
import com.seibel.lod.objects.rending.Mat4f;
|
||||
|
||||
|
||||
/**
|
||||
@@ -169,7 +169,7 @@ public class LodShaderProgram
|
||||
* @param location Uniform location
|
||||
* @param value Value to set
|
||||
*/
|
||||
public void setUniform(int location, Matrix4f value)
|
||||
public void setUniform(int location, Mat4f value)
|
||||
{
|
||||
try (MemoryStack stack = MemoryStack.stackPush())
|
||||
{
|
||||
|
||||
@@ -29,9 +29,9 @@ import com.seibel.lod.enums.HorizontalResolution;
|
||||
import com.seibel.lod.enums.VanillaOverdraw;
|
||||
import com.seibel.lod.objects.lod.LodDimension;
|
||||
import com.seibel.lod.objects.lod.RegionPos;
|
||||
import com.seibel.lod.wrappers.MinecraftWrapper;
|
||||
import com.seibel.lod.wrappers.Block.BlockPosWrapper;
|
||||
import com.seibel.lod.wrappers.Chunk.ChunkPosWrapper;
|
||||
import com.seibel.lod.wrappers.MinecraftWrapper;
|
||||
|
||||
import net.minecraft.client.multiplayer.ServerData;
|
||||
import net.minecraft.client.renderer.WorldRenderer;
|
||||
@@ -498,4 +498,15 @@ public class LodUtil
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/** This is copied from Minecraft's MathHelper class */
|
||||
public static float fastInvSqrt(float numb)
|
||||
{
|
||||
float half = 0.5F * numb;
|
||||
int i = Float.floatToIntBits(numb);
|
||||
i = 1597463007 - (i >> 1);
|
||||
numb = Float.intBitsToFloat(i);
|
||||
return numb * (1.5F - half * numb * numb);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,8 @@ public class ThreadMapUtil
|
||||
public static final ConcurrentMap<String, Map<Direction, long[]>> adjDataMap = new ConcurrentHashMap<>();
|
||||
public static final ConcurrentMap<String, Box> boxMap = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
|
||||
/** returns the array NOT cleared every time */
|
||||
public static boolean[] getAdjShadeDisabledArray()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.seibel.lod.wrappers;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import com.seibel.lod.objects.rending.Mat4f;
|
||||
|
||||
import net.minecraft.util.math.vector.Matrix4f;
|
||||
|
||||
/**
|
||||
* This class converts between Minecraft objects (Ex: Matrix4f)
|
||||
* and objects we created (Ex: Mat4f).
|
||||
* Since we don't want to deal with a bunch of tiny changes
|
||||
* every time Minecraft renames a variable in Matrix4f or something.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 11-11-2021
|
||||
*/
|
||||
public class McObjectConverter
|
||||
{
|
||||
|
||||
public McObjectConverter()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** 4x4 float matrix converter */
|
||||
public static Mat4f Convert(Matrix4f mcMatrix)
|
||||
{
|
||||
FloatBuffer buffer = FloatBuffer.allocate(16);
|
||||
mcMatrix.store(buffer);
|
||||
Mat4f matrix = new Mat4f(buffer);
|
||||
matrix.transpose();
|
||||
return matrix;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user