implement torch on wall
This commit is contained in:
@@ -224,7 +224,13 @@ window.Minecraft = class {
|
|||||||
if (!placedBoundingBox.intersects(this.player.boundingBox)) {
|
if (!placedBoundingBox.intersects(this.player.boundingBox)) {
|
||||||
let typeId = this.inventory.getItemInSelectedSlot();
|
let typeId = this.inventory.getItemInSelectedSlot();
|
||||||
if (typeId !== 0) {
|
if (typeId !== 0) {
|
||||||
|
|
||||||
|
// Place block
|
||||||
this.world.setBlockAt(x, y, z, typeId);
|
this.world.setBlockAt(x, y, z, typeId);
|
||||||
|
|
||||||
|
// Handle block abilities
|
||||||
|
let block = Block.getById(typeId);
|
||||||
|
block.onBlockPlaced(this.world, x, y, z, hitResult.face);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,18 +158,44 @@ window.BlockRenderer = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderTorch(world, block, x, y, z) {
|
renderTorch(world, block, x, y, z) {
|
||||||
let boundingBox = block.getBoundingBox(world, x, y, z);
|
|
||||||
|
|
||||||
// Thickness of the torch
|
// Thickness of the torch
|
||||||
let size = 1 / 16;
|
let size = 1 / 16;
|
||||||
|
|
||||||
|
let distortX = 0;
|
||||||
|
let distortZ = 0;
|
||||||
|
|
||||||
|
// Attach torch at wall
|
||||||
|
switch (world.getBlockDataAt(x, y, z)) {
|
||||||
|
case 1:
|
||||||
|
distortX = -0.2;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
distortX = 0.2;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
distortZ = -0.2;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
distortZ = 0.2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Model type
|
||||||
|
let centerX = 0.5 + distortX * 1.5;
|
||||||
|
let centerZ = 0.5 + distortZ * 1.5;
|
||||||
|
|
||||||
|
// Lift the torch up
|
||||||
|
if (distortX !== 0 || distortZ !== 0) {
|
||||||
|
y += 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
// Vertex mappings
|
// Vertex mappings
|
||||||
let minX = x + 0.5 - size;
|
let minX = x + centerX - size;
|
||||||
let minY = y;
|
let minY = y;
|
||||||
let minZ = z + 0.5 - size;
|
let minZ = z + centerZ - size;
|
||||||
let maxX = x + 0.5 + size;
|
let maxX = x + centerX + size;
|
||||||
let maxY = y + 10 / 16;
|
let maxY = y + 10 / 16;
|
||||||
let maxZ = z + 0.5 + size;
|
let maxZ = z + centerZ + size;
|
||||||
|
|
||||||
// UV Mapping
|
// UV Mapping
|
||||||
let textureIndex = block.getTextureForFace(EnumBlockFace.NORTH);
|
let textureIndex = block.getTextureForFace(EnumBlockFace.NORTH);
|
||||||
@@ -191,36 +217,39 @@ window.BlockRenderer = class {
|
|||||||
// Set color with shading
|
// Set color with shading
|
||||||
this.tessellator.setColor(1, 1, 1);
|
this.tessellator.setColor(1, 1, 1);
|
||||||
|
|
||||||
// Add faceS to tessellator
|
// Add faces to tessellator
|
||||||
this.addFace(world, EnumBlockFace.NORTH, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV);
|
this.addDistortFace(world, EnumBlockFace.NORTH, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV, distortX, distortZ);
|
||||||
this.addFace(world, EnumBlockFace.EAST, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV);
|
this.addDistortFace(world, EnumBlockFace.EAST, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV, distortX, distortZ);
|
||||||
this.addFace(world, EnumBlockFace.SOUTH, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV);
|
this.addDistortFace(world, EnumBlockFace.SOUTH, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV, distortX, distortZ);
|
||||||
this.addFace(world, EnumBlockFace.WEST, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV);
|
this.addDistortFace(world, EnumBlockFace.WEST, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV, distortX, distortZ);
|
||||||
this.addFace(world, EnumBlockFace.TOP, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV + 8 / 256);
|
this.addFace(world, EnumBlockFace.TOP, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV + 8 / 256);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderGuiItem(block) {
|
addDistortFace(world, face, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV, distortX, distortZ) {
|
||||||
// Vertex mappings
|
if (face === EnumBlockFace.NORTH) {
|
||||||
let minX = 0;
|
this.addBlockCorner(world, face, minX, maxY, minZ, minU, minV);
|
||||||
let minY = 0;
|
this.addBlockCorner(world, face, minX + distortX, minY, minZ + distortZ, minU, maxV);
|
||||||
let minZ = 0;
|
this.addBlockCorner(world, face, maxX + distortX, minY, minZ + distortZ, maxU, maxV);
|
||||||
let maxX = 1;
|
this.addBlockCorner(world, face, maxX, maxY, minZ, maxU, minV);
|
||||||
let maxY = 1;
|
}
|
||||||
let maxZ = 1;
|
if (face === EnumBlockFace.SOUTH) {
|
||||||
|
this.addBlockCorner(world, face, minX, maxY, maxZ, maxU, minV);
|
||||||
// UV Mapping
|
this.addBlockCorner(world, face, maxX, maxY, maxZ, minU, minV);
|
||||||
let textureIndex = block.getTextureForFace(EnumBlockFace.NORTH);
|
this.addBlockCorner(world, face, maxX + distortX, minY, maxZ + distortZ, minU, maxV);
|
||||||
let minU = (textureIndex % 16) / 16.0;
|
this.addBlockCorner(world, face, minX + distortX, minY, maxZ + distortZ, maxU, maxV);
|
||||||
let maxU = minU + (16 / 256);
|
}
|
||||||
let minV = Math.floor(textureIndex / 16) / 16.0;
|
if (face === EnumBlockFace.WEST) {
|
||||||
let maxV = minV + (16 / 256);
|
this.addBlockCorner(world, face, minX + distortX, minY, maxZ + distortZ, minU, maxV);
|
||||||
|
this.addBlockCorner(world, face, minX + distortX, minY, minZ + distortZ, maxU, maxV);
|
||||||
// Flip V
|
this.addBlockCorner(world, face, minX, maxY, minZ, maxU, minV);
|
||||||
minV = 1 - minV;
|
this.addBlockCorner(world, face, minX, maxY, maxZ, minU, minV);
|
||||||
maxV = 1 - maxV;
|
}
|
||||||
|
if (face === EnumBlockFace.EAST) {
|
||||||
// Render item
|
this.addBlockCorner(world, face, maxX, maxY, maxZ, maxU, minV);
|
||||||
this.addFace(null, EnumBlockFace.NORTH, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV);
|
this.addBlockCorner(world, face, maxX, maxY, minZ, minU, minV);
|
||||||
|
this.addBlockCorner(world, face, maxX + distortX, minY, minZ + distortZ, minU, maxV);
|
||||||
|
this.addBlockCorner(world, face, maxX + distortX, minY, maxZ + distortZ, maxU, maxV);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderGuiBlock(group, block, x, y, size) {
|
renderGuiBlock(group, block, x, y, size) {
|
||||||
@@ -266,4 +295,28 @@ window.BlockRenderer = class {
|
|||||||
mesh.scale.y = size;
|
mesh.scale.y = size;
|
||||||
mesh.scale.z = size;
|
mesh.scale.z = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderGuiItem(block) {
|
||||||
|
// Vertex mappings
|
||||||
|
let minX = 0;
|
||||||
|
let minY = 0;
|
||||||
|
let minZ = 0;
|
||||||
|
let maxX = 1;
|
||||||
|
let maxY = 1;
|
||||||
|
let maxZ = 1;
|
||||||
|
|
||||||
|
// UV Mapping
|
||||||
|
let textureIndex = block.getTextureForFace(EnumBlockFace.NORTH);
|
||||||
|
let minU = (textureIndex % 16) / 16.0;
|
||||||
|
let maxU = minU + (16 / 256);
|
||||||
|
let minV = Math.floor(textureIndex / 16) / 16.0;
|
||||||
|
let maxV = minV + (16 / 256);
|
||||||
|
|
||||||
|
// Flip V
|
||||||
|
minV = 1 - minV;
|
||||||
|
maxV = 1 - maxV;
|
||||||
|
|
||||||
|
// Render item
|
||||||
|
this.addFace(null, EnumBlockFace.NORTH, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,9 @@ window.ScreenRenderer = class {
|
|||||||
this.stack2d.clearRect(0, 0, this.window.width, this.window.height);
|
this.stack2d.clearRect(0, 0, this.window.width, this.window.height);
|
||||||
|
|
||||||
// Render in-game overlay
|
// Render in-game overlay
|
||||||
this.minecraft.ingameOverlay.render(this.stack2d, mouseX, mouseY, partialTicks);
|
if (this.minecraft.loadingScreen === null) {
|
||||||
|
this.minecraft.ingameOverlay.render(this.stack2d, mouseX, mouseY, partialTicks);
|
||||||
|
}
|
||||||
|
|
||||||
// Render current screen
|
// Render current screen
|
||||||
if (!(this.minecraft.currentScreen === null)) {
|
if (!(this.minecraft.currentScreen === null)) {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ window.Chunk = class {
|
|||||||
// Calculate height map
|
// Calculate height map
|
||||||
for (let x = 0; x < 16; x++) {
|
for (let x = 0; x < 16; x++) {
|
||||||
for (let z = 0; z < 16; z++) {
|
for (let z = 0; z < 16; z++) {
|
||||||
this.setHeightAt(x, z, 0); // TODO set to 0 to calculate proper lightning
|
this.setHeightAt(x, z, 0);
|
||||||
this.updateHeightMap(x, World.TOTAL_HEIGHT, z);
|
this.updateHeightMap(x, World.TOTAL_HEIGHT, z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,19 +238,32 @@ window.Chunk = class {
|
|||||||
section.setLightAt(sourceType, x, y & 15, z, level);
|
section.setLightAt(sourceType, x, y & 15, z, level);
|
||||||
}
|
}
|
||||||
|
|
||||||
setBlockAt(x, y, z, typeId) {
|
setBlockDataAt(x, y, z, data) {
|
||||||
|
this.setBlockAt(x, y, z, this.getBlockAt(x, y, z), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
setBlockAt(x, y, z, typeId, data = 0) {
|
||||||
|
let section = this.getSection(y >> 4);
|
||||||
|
let yInSection = y & 15;
|
||||||
|
|
||||||
let height = this.getHeightAt(x, z);
|
let height = this.getHeightAt(x, z);
|
||||||
let prevTypeId = this.getBlockAt(x, y, z);
|
let prevTypeId = section.getBlockAt(x, yInSection, z);
|
||||||
if (prevTypeId === typeId) {
|
let prevData = section.getBlockDataAt(x, yInSection, z);
|
||||||
|
|
||||||
|
// Check if block type has changed
|
||||||
|
if (prevTypeId === typeId && prevData === data) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getSection(y >> 4).setBlockAt(x, y & 15, z, typeId);
|
// Update block type and data
|
||||||
|
section.setBlockAt(x, yInSection, z, typeId);
|
||||||
|
section.setBlockDataAt(x, yInSection, z, data);
|
||||||
|
|
||||||
if (!this.loaded) {
|
if (!this.loaded) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update height map
|
||||||
let block = Block.getById(typeId);
|
let block = Block.getById(typeId);
|
||||||
if (typeId !== 0 && block.isSolid()) {
|
if (typeId !== 0 && block.isSolid()) {
|
||||||
if (y >= height) {
|
if (y >= height) {
|
||||||
@@ -263,11 +276,18 @@ window.Chunk = class {
|
|||||||
let totalX = this.x * 16 + x;
|
let totalX = this.x * 16 + x;
|
||||||
let totalZ = this.z * 16 + z;
|
let totalZ = this.z * 16 + z;
|
||||||
|
|
||||||
|
// Update light
|
||||||
this.world.updateLight(EnumSkyBlock.SKY, totalX, y, totalZ, totalX, y, totalZ);
|
this.world.updateLight(EnumSkyBlock.SKY, totalX, y, totalZ, totalX, y, totalZ);
|
||||||
this.world.updateLight(EnumSkyBlock.BLOCK, totalX, y, totalZ, totalX, y, totalZ);
|
this.world.updateLight(EnumSkyBlock.BLOCK, totalX, y, totalZ, totalX, y, totalZ);
|
||||||
|
|
||||||
|
// Notify surrounding blocks
|
||||||
this.notifyNeighbors(x, z);
|
this.notifyNeighbors(x, z);
|
||||||
this.setModifiedAllSections();
|
|
||||||
|
// Handle block abilities
|
||||||
|
if (typeId !== 0) {
|
||||||
|
block.onBlockAdded(this.world, totalX, y, totalZ);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,6 +299,10 @@ window.Chunk = class {
|
|||||||
return this.getSection(y >> 4).getBlockAt(x, y & 15, z);
|
return this.getSection(y >> 4).getBlockAt(x, y & 15, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getBlockDataAt(x, y, z) {
|
||||||
|
return this.getSection(y >> 4).getBlockDataAt(x, y & 15, z);
|
||||||
|
}
|
||||||
|
|
||||||
getSection(y) {
|
getSection(y) {
|
||||||
return this.sections[y];
|
return this.sections[y];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ window.ChunkSection = class {
|
|||||||
this.isModified = false;
|
this.isModified = false;
|
||||||
|
|
||||||
this.blocks = [];
|
this.blocks = [];
|
||||||
|
this.blocksData = [];
|
||||||
this.blockLight = [];
|
this.blockLight = [];
|
||||||
this.skyLight = [];
|
this.skyLight = [];
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ window.ChunkSection = class {
|
|||||||
for (let tZ = 0; tZ < ChunkSection.SIZE; tZ++) {
|
for (let tZ = 0; tZ < ChunkSection.SIZE; tZ++) {
|
||||||
let index = tY << 8 | tZ << 4 | tX;
|
let index = tY << 8 | tZ << 4 | tX;
|
||||||
this.blocks[index] = 0;
|
this.blocks[index] = 0;
|
||||||
|
this.blocksData[index] = 0;
|
||||||
this.blockLight[index] = 0;
|
this.blockLight[index] = 0;
|
||||||
this.skyLight[index] = 0;
|
this.skyLight[index] = 0;
|
||||||
}
|
}
|
||||||
@@ -77,6 +79,11 @@ window.ChunkSection = class {
|
|||||||
return this.blocks[index];
|
return this.blocks[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getBlockDataAt(x, y, z) {
|
||||||
|
let index = y << 8 | z << 4 | x;
|
||||||
|
return this.blocksData[index];
|
||||||
|
}
|
||||||
|
|
||||||
setBlockAt(x, y, z, typeId) {
|
setBlockAt(x, y, z, typeId) {
|
||||||
let index = y << 8 | z << 4 | x;
|
let index = y << 8 | z << 4 | x;
|
||||||
this.blocks[index] = typeId;
|
this.blocks[index] = typeId;
|
||||||
@@ -84,6 +91,13 @@ window.ChunkSection = class {
|
|||||||
this.isModified = true;
|
this.isModified = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setBlockDataAt(x, y, z, data) {
|
||||||
|
let index = y << 8 | z << 4 | x;
|
||||||
|
this.blocksData[index] = data;
|
||||||
|
|
||||||
|
this.isModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
setLightAt(sourceType, x, y, z, lightLevel) {
|
setLightAt(sourceType, x, y, z, lightLevel) {
|
||||||
let index = y << 8 | z << 4 | x;
|
let index = y << 8 | z << 4 | x;
|
||||||
|
|
||||||
|
|||||||
@@ -278,11 +278,20 @@ window.World = class {
|
|||||||
this.onBlockChanged(x, y, z);
|
this.onBlockChanged(x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setBlockDataAt(x, y, z, data) {
|
||||||
|
this.getChunkAt(x >> 4, z >> 4).setBlockDataAt(x & 15, y, z & 15, data);
|
||||||
|
}
|
||||||
|
|
||||||
getBlockAt(x, y, z) {
|
getBlockAt(x, y, z) {
|
||||||
let chunkSection = this.getChunkAtBlock(x, y, z);
|
let chunkSection = this.getChunkAtBlock(x, y, z);
|
||||||
return chunkSection == null ? 0 : chunkSection.getBlockAt(x & 15, y & 15, z & 15);
|
return chunkSection == null ? 0 : chunkSection.getBlockAt(x & 15, y & 15, z & 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getBlockDataAt(x, y, z) {
|
||||||
|
let chunkSection = this.getChunkAtBlock(x, y, z);
|
||||||
|
return chunkSection == null ? 0 : chunkSection.getBlockDataAt(x & 15, y & 15, z & 15);
|
||||||
|
}
|
||||||
|
|
||||||
getBlockAtFace(x, y, z, face) {
|
getBlockAtFace(x, y, z, face) {
|
||||||
return this.getBlockAt(x + face.x, y + face.y, z + face.z);
|
return this.getBlockAt(x + face.x, y + face.y, z + face.z);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,14 @@ window.Block = class {
|
|||||||
return this.boundingBox;
|
return this.boundingBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBlockAdded(world, x, y, z) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onBlockPlaced(world, x, y, z, face) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
collisionRayTrace(x, y, z, start, end) {
|
collisionRayTrace(x, y, z, start, end) {
|
||||||
start = start.addVector(-x, -y, -z);
|
start = start.addVector(-x, -y, -z);
|
||||||
end = end.addVector(-x, -y, -z);
|
end = end.addVector(-x, -y, -z);
|
||||||
|
|||||||
@@ -4,6 +4,14 @@ window.BlockTorch = class extends Block {
|
|||||||
super(id, textureSlotId);
|
super(id, textureSlotId);
|
||||||
|
|
||||||
this.boundingBox = new BoundingBox(0.4, 0.0, 0.4, 0.6, 0.6, 0.6);
|
this.boundingBox = new BoundingBox(0.4, 0.0, 0.4, 0.6, 0.6, 0.6);
|
||||||
|
|
||||||
|
this.dataFaces = [
|
||||||
|
EnumBlockFace.WEST,
|
||||||
|
EnumBlockFace.EAST,
|
||||||
|
EnumBlockFace.NORTH,
|
||||||
|
EnumBlockFace.SOUTH,
|
||||||
|
EnumBlockFace.BOTTOM,
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
getLightValue() {
|
getLightValue() {
|
||||||
@@ -17,4 +25,30 @@ window.BlockTorch = class extends Block {
|
|||||||
getRenderType() {
|
getRenderType() {
|
||||||
return BlockRenderType.TORCH;
|
return BlockRenderType.TORCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBlockAdded(world, x, y, z) {
|
||||||
|
for (let i = this.dataFaces.length - 1; i >= 0; i--) {
|
||||||
|
let dataFace = this.dataFaces[i];
|
||||||
|
|
||||||
|
if (world.isSolidBlockAt(x + dataFace.x, y + dataFace.y, z + dataFace.z)) {
|
||||||
|
world.setBlockDataAt(x, y, z, i + 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onBlockPlaced(world, x, y, z, face) {
|
||||||
|
let meta = world.getBlockDataAt(x, y, z);
|
||||||
|
|
||||||
|
for (let i in this.dataFaces) {
|
||||||
|
let dataFace = this.dataFaces[i];
|
||||||
|
|
||||||
|
if (face === dataFace.opposite() && world.isSolidBlockAt(x + dataFace.x, y + dataFace.y, z + dataFace.z)) {
|
||||||
|
meta = parseInt(i) + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
world.getChunkSectionAt(x >> 4, y >> 4, z >> 4).setBlockDataAt(x & 15, y & 15, z & 15, meta);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -22,6 +22,31 @@ window.EnumBlockFace = class {
|
|||||||
return this.z !== 0;
|
return this.z !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
opposite() {
|
||||||
|
if (this === EnumBlockFace.TOP) {
|
||||||
|
return EnumBlockFace.BOTTOM;
|
||||||
|
}
|
||||||
|
if (this === EnumBlockFace.BOTTOM) {
|
||||||
|
return EnumBlockFace.TOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this === EnumBlockFace.NORTH) {
|
||||||
|
return EnumBlockFace.SOUTH;
|
||||||
|
}
|
||||||
|
if (this === EnumBlockFace.SOUTH) {
|
||||||
|
return EnumBlockFace.NORTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this === EnumBlockFace.EAST) {
|
||||||
|
return EnumBlockFace.WEST;
|
||||||
|
}
|
||||||
|
if (this === EnumBlockFace.WEST) {
|
||||||
|
return EnumBlockFace.EAST;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
static values() {
|
static values() {
|
||||||
return [
|
return [
|
||||||
EnumBlockFace.TOP,
|
EnumBlockFace.TOP,
|
||||||
|
|||||||
Reference in New Issue
Block a user