mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-16 02:45:13 -04:00
change block storage in chunks (wip light, biomes)
This commit is contained in:
parent
5312e0d436
commit
9d5952dd91
@ -1,139 +0,0 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020 Moritz Zwerger
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.data.mappings.tweaker;
|
||||
|
||||
import de.bixilon.minosoft.data.entities.EntityMetaData;
|
||||
import de.bixilon.minosoft.data.entities.EntityMetaDataFields;
|
||||
import de.bixilon.minosoft.data.entities.entities.Entity;
|
||||
import de.bixilon.minosoft.data.entities.entities.animal.horse.*;
|
||||
import de.bixilon.minosoft.data.entities.entities.monster.*;
|
||||
import de.bixilon.minosoft.data.entities.entities.vehicle.*;
|
||||
import de.bixilon.minosoft.data.mappings.blocks.Block;
|
||||
import de.bixilon.minosoft.data.world.Chunk;
|
||||
import de.bixilon.minosoft.data.world.ChunkSection;
|
||||
import de.bixilon.minosoft.data.world.InChunkLocation;
|
||||
import de.bixilon.minosoft.data.world.InChunkSectionLocation;
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static de.bixilon.minosoft.protocol.protocol.ProtocolVersions.V_1_8_9;
|
||||
|
||||
public class VersionTweaker {
|
||||
// some data was packed in mata data in early versions (1.8). This function converts it to the real identifier
|
||||
@SuppressWarnings("deprecation")
|
||||
public static Class<? extends Entity> getRealEntityClass(Class<? extends Entity> fakeClass, EntityMetaData metaData, int versionId) {
|
||||
if (fakeClass == ZombiePigman.class) {
|
||||
return ZombifiedPiglin.class;
|
||||
} else if (fakeClass == Zombie.class) {
|
||||
if (versionId > V_1_8_9) { // ToDo: No clue here
|
||||
return fakeClass;
|
||||
}
|
||||
if (metaData.getSets().getInt(EntityMetaDataFields.ZOMBIE_SPECIAL_TYPE) == 1) {
|
||||
return ZombieVillager.class;
|
||||
}
|
||||
} else if (fakeClass == Skeleton.class) {
|
||||
if (versionId > V_1_8_9) { // ToDo: No clue here
|
||||
return fakeClass;
|
||||
}
|
||||
if (metaData.getSets().getInt(EntityMetaDataFields.LEGACY_SKELETON_TYPE) == 1) {
|
||||
return WitherSkeleton.class;
|
||||
}
|
||||
} else if (fakeClass == Guardian.class) {
|
||||
if (versionId > V_1_8_9) { // ToDo: No clue here
|
||||
return fakeClass;
|
||||
}
|
||||
if (metaData.getSets().getBitMask(EntityMetaDataFields.LEGACY_GUARDIAN_FLAGS, 0x02)) {
|
||||
return ElderGuardian.class;
|
||||
}
|
||||
} else if (fakeClass == Horse.class) {
|
||||
if (versionId > V_1_8_9) { // ToDo: No clue here
|
||||
return fakeClass;
|
||||
}
|
||||
return switch (metaData.getSets().getByte(EntityMetaDataFields.LEGACY_HORSE_SPECIAL_TYPE)) {
|
||||
default -> fakeClass;
|
||||
case 1 -> Donkey.class;
|
||||
case 2 -> Mule.class;
|
||||
case 3 -> ZombieHorse.class;
|
||||
case 4 -> SkeletonHorse.class;
|
||||
};
|
||||
|
||||
}
|
||||
return fakeClass;
|
||||
}
|
||||
|
||||
public static Class<? extends Entity> getRealEntityObjectClass(Class<? extends Entity> fakeClass, int data, int versionId) {
|
||||
if (fakeClass == Minecart.class) {
|
||||
if (versionId > V_1_8_9) { // ToDo: No clue here
|
||||
return fakeClass;
|
||||
}
|
||||
return switch (data) {
|
||||
default -> fakeClass;
|
||||
case 1 -> MinecartChest.class;
|
||||
case 2 -> MinecartFurnace.class;
|
||||
case 3 -> MinecartTNT.class;
|
||||
case 4 -> MinecartSpawner.class;
|
||||
case 5 -> MinecartHopper.class;
|
||||
case 6 -> MinecartCommandBlock.class;
|
||||
};
|
||||
}
|
||||
return fakeClass;
|
||||
}
|
||||
|
||||
public static Chunk transformChunk(Chunk chunk, int versionId) {
|
||||
// some blocks need to be tweaked. eg. Grass with a snow block on top becomes snowy grass block
|
||||
if (versionId >= ProtocolDefinition.FLATTING_VERSION_ID) {
|
||||
return chunk;
|
||||
}
|
||||
for (Map.Entry<Integer, ChunkSection> sectionEntry : chunk.getSections().entrySet()) {
|
||||
for (Map.Entry<InChunkSectionLocation, Block> blockEntry : sectionEntry.getValue().getBlocks().entrySet()) {
|
||||
Block newBlock = transformBlock(blockEntry.getValue(), chunk, blockEntry.getKey(), sectionEntry.getKey());
|
||||
if (newBlock == blockEntry.getValue()) {
|
||||
continue;
|
||||
}
|
||||
sectionEntry.getValue().setBlock(blockEntry.getKey(), newBlock);
|
||||
}
|
||||
}
|
||||
return chunk;
|
||||
}
|
||||
|
||||
public static Block transformBlock(Block originalBlock, Chunk chunk, InChunkLocation location) {
|
||||
return transformBlock(originalBlock, chunk, location.getInChunkSectionLocation(), (byte) (location.getY() / ProtocolDefinition.SECTION_HEIGHT_Y));
|
||||
}
|
||||
|
||||
public static Block transformBlock(Block originalBlock, Chunk chunk, InChunkSectionLocation location, int sectionHeight) {
|
||||
if (originalBlock == null) {
|
||||
return null;
|
||||
}
|
||||
switch (originalBlock.getIdentifier().getFullIdentifier()) {
|
||||
case "minecraft:grass" -> {
|
||||
Block above = getBlockAbove(chunk, location, sectionHeight);
|
||||
if (above == null) {
|
||||
break;
|
||||
}
|
||||
if (above.equals(TweakBlocks.SNOW) || above.equals(TweakBlocks.SNOW_LAYER)) {
|
||||
return TweakBlocks.GRASS_BLOCK_SNOWY_YES;
|
||||
} else {
|
||||
return TweakBlocks.GRASS_BLOCK_SNOWY_NO;
|
||||
}
|
||||
}
|
||||
// ToDo: all blocks. e.g. doors, etc
|
||||
}
|
||||
return originalBlock;
|
||||
}
|
||||
|
||||
private static Block getBlockAbove(Chunk chunk, InChunkSectionLocation location, int sectionHeight) {
|
||||
return chunk.getBlock(location.getInChunkLocation(sectionHeight));
|
||||
}
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020 Moritz Zwerger
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
package de.bixilon.minosoft.data.mappings.tweaker
|
||||
|
||||
import de.bixilon.minosoft.data.entities.EntityMetaData
|
||||
import de.bixilon.minosoft.data.entities.EntityMetaDataFields
|
||||
import de.bixilon.minosoft.data.entities.entities.Entity
|
||||
import de.bixilon.minosoft.data.entities.entities.animal.horse.*
|
||||
import de.bixilon.minosoft.data.entities.entities.monster.*
|
||||
import de.bixilon.minosoft.data.entities.entities.vehicle.*
|
||||
import de.bixilon.minosoft.data.mappings.blocks.Block
|
||||
import de.bixilon.minosoft.data.world.*
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolVersions
|
||||
|
||||
object VersionTweaker {
|
||||
// some data was packed in mata data in early versions (1.8). This function converts it to the real identifier
|
||||
@JvmStatic
|
||||
fun getRealEntityClass(fakeClass: Class<out Entity>, metaData: EntityMetaData, versionId: Int): Class<out Entity> {
|
||||
if (versionId > ProtocolVersions.V_1_8_9) { // ToDo: No clue here
|
||||
return fakeClass
|
||||
}
|
||||
when (fakeClass) {
|
||||
ZombiePigman::class.java -> {
|
||||
return ZombifiedPiglin::class.java
|
||||
}
|
||||
Zombie::class.java -> {
|
||||
if (metaData.sets.getInt(EntityMetaDataFields.ZOMBIE_SPECIAL_TYPE) == 1) {
|
||||
return ZombieVillager::class.java
|
||||
}
|
||||
}
|
||||
Skeleton::class.java -> {
|
||||
if (metaData.sets.getInt(EntityMetaDataFields.LEGACY_SKELETON_TYPE) == 1) {
|
||||
return WitherSkeleton::class.java
|
||||
}
|
||||
}
|
||||
Guardian::class.java -> {
|
||||
if (metaData.sets.getBitMask(EntityMetaDataFields.LEGACY_GUARDIAN_FLAGS, 0x02)) {
|
||||
return ElderGuardian::class.java
|
||||
}
|
||||
}
|
||||
Horse::class.java -> {
|
||||
return when (metaData.sets.getByte(EntityMetaDataFields.LEGACY_HORSE_SPECIAL_TYPE).toInt()) {
|
||||
1 -> Donkey::class.java
|
||||
2 -> Mule::class.java
|
||||
3 -> ZombieHorse::class.java
|
||||
4 -> SkeletonHorse::class.java
|
||||
else -> fakeClass
|
||||
}
|
||||
}
|
||||
}
|
||||
return fakeClass
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getRealEntityObjectClass(fakeClass: Class<out Entity>, data: Int, versionId: Int): Class<out Entity> {
|
||||
if (versionId > ProtocolVersions.V_1_8_9) { // ToDo: No clue here
|
||||
return fakeClass
|
||||
}
|
||||
when (fakeClass) {
|
||||
Minecart::class.java -> {
|
||||
return when (data) {
|
||||
1 -> MinecartChest::class.java
|
||||
2 -> MinecartFurnace::class.java
|
||||
3 -> MinecartTNT::class.java
|
||||
4 -> MinecartSpawner::class.java
|
||||
5 -> MinecartHopper::class.java
|
||||
6 -> MinecartCommandBlock::class.java
|
||||
else -> fakeClass
|
||||
}
|
||||
}
|
||||
}
|
||||
return fakeClass
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun transformChunk(chunk: Chunk, versionId: Int): Chunk {
|
||||
// some blocks need to be tweaked. eg. Grass with a snow block on top becomes snowy grass block
|
||||
if (versionId >= ProtocolDefinition.FLATTING_VERSION_ID) {
|
||||
return chunk
|
||||
}
|
||||
for ((sectionHeight, section) in chunk.sections) {
|
||||
for ((location, blockInfo) in section.blocks) {
|
||||
val newBlock = transformBlock(blockInfo.block, chunk, location, sectionHeight)
|
||||
if (newBlock === blockInfo.block) {
|
||||
continue
|
||||
}
|
||||
if (newBlock == null) {
|
||||
section.setBlockInfo(location, null)
|
||||
continue
|
||||
}
|
||||
section.setBlockInfo(location, BlockInfo(newBlock, blockInfo.metaData, section.blocksStaticInfo[location] ?: BlockStaticInfo()))
|
||||
}
|
||||
}
|
||||
return chunk
|
||||
}
|
||||
|
||||
|
||||
@JvmStatic
|
||||
fun transformBlock(originalBlock: Block, chunk: Chunk, location: InChunkSectionLocation, sectionHeight: Int): Block? {
|
||||
when (originalBlock.identifier.fullIdentifier) {
|
||||
"minecraft:grass" -> {
|
||||
getBlockAbove(chunk, location, sectionHeight)?.let {
|
||||
if (it == TweakBlocks.SNOW || it == TweakBlocks.SNOW_LAYER) {
|
||||
return TweakBlocks.GRASS_BLOCK_SNOWY_YES
|
||||
}
|
||||
}
|
||||
return TweakBlocks.GRASS_BLOCK_SNOWY_NO
|
||||
}
|
||||
}
|
||||
return originalBlock
|
||||
}
|
||||
|
||||
private fun getBlockAbove(chunk: Chunk, location: InChunkSectionLocation, sectionHeight: Int): Block? {
|
||||
val above = location.getInChunkLocation(sectionHeight)
|
||||
return chunk.getBlockInfo(InChunkLocation(above.x, above.y + 1, above.z))?.block
|
||||
}
|
||||
}
|
25
src/main/java/de/bixilon/minosoft/data/world/BlockInfo.kt
Normal file
25
src/main/java/de/bixilon/minosoft/data/world/BlockInfo.kt
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2021 Moritz Zwerger
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.data.world
|
||||
|
||||
import de.bixilon.minosoft.data.entities.block.BlockEntityMetaData
|
||||
import de.bixilon.minosoft.data.mappings.blocks.Block
|
||||
|
||||
data class BlockInfo(
|
||||
val block: Block,
|
||||
var metaData: BlockEntityMetaData? = null,
|
||||
val info: BlockStaticInfo = BlockStaticInfo(),
|
||||
) {
|
||||
constructor(block: Block) : this(block, null) // ToDo: For java compatibility
|
||||
}
|
@ -45,7 +45,7 @@ data class BlockPosition(val x: Int, val y: Int, val z: Int) {
|
||||
|
||||
fun getInChunkSectionLocation(): InChunkSectionLocation {
|
||||
val location = getInChunkLocation()
|
||||
return InChunkSectionLocation(location.x, this.y % ProtocolDefinition.SECTION_HEIGHT_Y, location.z)
|
||||
return InChunkSectionLocation(location.x, getSectionHeight(), location.z)
|
||||
}
|
||||
|
||||
fun getSectionHeight(): Int {
|
||||
|
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2021 Moritz Zwerger
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.data.world
|
||||
|
||||
data class BlockStaticInfo(
|
||||
var light: Int = 15,
|
||||
var skyLight: Int = 15,
|
||||
// ToDo: Biome
|
||||
)
|
@ -1,99 +0,0 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020 Moritz Zwerger
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.data.world;
|
||||
|
||||
import de.bixilon.minosoft.data.entities.block.BlockEntityMetaData;
|
||||
import de.bixilon.minosoft.data.mappings.blocks.Block;
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Collection of 16 chunks sections
|
||||
*/
|
||||
public class Chunk {
|
||||
private final HashMap<Integer, ChunkSection> sections;
|
||||
|
||||
public Chunk(HashMap<Integer, ChunkSection> sections) {
|
||||
this.sections = sections;
|
||||
}
|
||||
|
||||
public Block getBlock(InChunkLocation location) {
|
||||
return getBlock(location.getX(), location.getY(), location.getZ());
|
||||
}
|
||||
|
||||
public Block getBlock(int x, int y, int z) {
|
||||
int section = (y / ProtocolDefinition.SECTION_HEIGHT_Y);
|
||||
if (!this.sections.containsKey(section)) {
|
||||
return null;
|
||||
}
|
||||
return this.sections.get(section).getBlock(x, y % ProtocolDefinition.SECTION_HEIGHT_Y, z);
|
||||
}
|
||||
|
||||
public void setBlock(int x, int y, int z, Block block) {
|
||||
int section = y / ProtocolDefinition.SECTION_HEIGHT_Y;
|
||||
createSection(section);
|
||||
this.sections.get(section).setBlock(x, y % 16, z, block);
|
||||
}
|
||||
|
||||
void createSection(int height) {
|
||||
if (this.sections.get(height) == null) {
|
||||
// section was empty before, creating it
|
||||
this.sections.put(height, new ChunkSection());
|
||||
}
|
||||
}
|
||||
|
||||
public void setBlocks(HashMap<InChunkLocation, Block> blocks) {
|
||||
blocks.forEach(this::setBlock);
|
||||
}
|
||||
|
||||
public void setBlock(InChunkLocation location, Block block) {
|
||||
int section = (location.getY() / ProtocolDefinition.SECTION_HEIGHT_Y);
|
||||
createSection(section);
|
||||
this.sections.get(section).setBlock(location.getInChunkSectionLocation(), block);
|
||||
}
|
||||
|
||||
public void setBlockEntityData(InChunkLocation position, BlockEntityMetaData data) {
|
||||
ChunkSection section = this.sections.get((position.getY() / ProtocolDefinition.SECTION_HEIGHT_Y));
|
||||
if (section == null) {
|
||||
return;
|
||||
}
|
||||
section.setBlockEntityData(position.getInChunkSectionLocation(), data);
|
||||
}
|
||||
|
||||
public BlockEntityMetaData getBlockEntityData(InChunkLocation position) {
|
||||
ChunkSection section = this.sections.get((position.getY() / ProtocolDefinition.SECTION_HEIGHT_Y));
|
||||
if (section == null) {
|
||||
return null;
|
||||
}
|
||||
return section.getBlockEntityData(position.getInChunkSectionLocation());
|
||||
}
|
||||
|
||||
public void setBlockEntityData(HashMap<InChunkLocation, BlockEntityMetaData> blockEntities) {
|
||||
blockEntities.forEach(this::setBlockEntityData);
|
||||
}
|
||||
|
||||
public HashMap<Integer, ChunkSection> getSections() {
|
||||
return this.sections;
|
||||
}
|
||||
|
||||
public ChunkSection getSectionOrCreate(int sectionHeight) {
|
||||
ChunkSection section = this.sections.get(sectionHeight);
|
||||
if (section == null) {
|
||||
section = new ChunkSection();
|
||||
this.sections.put(sectionHeight, section);
|
||||
}
|
||||
return section;
|
||||
}
|
||||
}
|
69
src/main/java/de/bixilon/minosoft/data/world/Chunk.kt
Normal file
69
src/main/java/de/bixilon/minosoft/data/world/Chunk.kt
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020 Moritz Zwerger
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
package de.bixilon.minosoft.data.world
|
||||
|
||||
import de.bixilon.minosoft.data.mappings.blocks.Block
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Collection of chunks sections (allocated in y)
|
||||
*/
|
||||
class Chunk(val sections: MutableMap<Int, ChunkSection> = mutableMapOf()) {
|
||||
|
||||
fun getBlockInfo(location: InChunkLocation): BlockInfo? {
|
||||
return sections[location.getSectionHeight()]?.getBlockInfo(location.getInChunkSectionLocation())
|
||||
}
|
||||
|
||||
fun getBlockInfo(x: Int, y: Int, z: Int): BlockInfo? {
|
||||
return getBlockInfo(InChunkLocation(x, y, z))
|
||||
}
|
||||
|
||||
fun setBlocks(blocks: HashMap<InChunkLocation, BlockInfo?>) {
|
||||
for ((location, blockInfo) in blocks) {
|
||||
setBlock(location, blockInfo)
|
||||
}
|
||||
}
|
||||
|
||||
fun setRawBlocks(blocks: HashMap<InChunkLocation, Block?>) {
|
||||
for ((location, blockInfo) in blocks) {
|
||||
setRawBlock(location, blockInfo)
|
||||
}
|
||||
}
|
||||
|
||||
fun setBlock(location: InChunkLocation, block: BlockInfo?) {
|
||||
getSectionOrCreate(location.getSectionHeight()).setBlockInfo(location.getInChunkSectionLocation(), block)
|
||||
}
|
||||
|
||||
fun setRawBlock(location: InChunkLocation, block: Block?) {
|
||||
getSectionOrCreate(location.getSectionHeight()).let {
|
||||
val inChunkSectionLocation = location.getInChunkSectionLocation()
|
||||
if (block == null) {
|
||||
it.blocks.remove(inChunkSectionLocation)
|
||||
return
|
||||
}
|
||||
it.setBlockInfo(inChunkSectionLocation, BlockInfo(block, info = it.blocksStaticInfo[inChunkSectionLocation] ?: BlockStaticInfo()))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun getSectionOrCreate(sectionHeight: Int): ChunkSection {
|
||||
return sections[sectionHeight].let {
|
||||
var section = it
|
||||
if (section == null) {
|
||||
section = ChunkSection()
|
||||
sections[sectionHeight] = section
|
||||
}
|
||||
section
|
||||
}
|
||||
}
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020 Moritz Zwerger
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.data.world;
|
||||
|
||||
import de.bixilon.minosoft.data.entities.block.BlockEntityMetaData;
|
||||
import de.bixilon.minosoft.data.mappings.blocks.Block;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Collection of 16x16x16 blocks
|
||||
*/
|
||||
public class ChunkSection {
|
||||
private final HashMap<InChunkSectionLocation, Block> blocks;
|
||||
private final HashMap<InChunkSectionLocation, BlockEntityMetaData> blockEntityMeta = new HashMap<>();
|
||||
private final HashMap<InChunkSectionLocation, Byte> light;
|
||||
private final HashMap<InChunkSectionLocation, Byte> skyLight;
|
||||
|
||||
public ChunkSection(HashMap<InChunkSectionLocation, Block> blocks) {
|
||||
this(blocks, new HashMap<>(), new HashMap<>());
|
||||
}
|
||||
|
||||
public ChunkSection(HashMap<InChunkSectionLocation, Block> blocks, HashMap<InChunkSectionLocation, Byte> light, HashMap<InChunkSectionLocation, Byte> skyLight) {
|
||||
this.blocks = blocks;
|
||||
this.light = light;
|
||||
this.skyLight = skyLight;
|
||||
}
|
||||
|
||||
public ChunkSection() {
|
||||
this(new HashMap<>());
|
||||
}
|
||||
|
||||
public Block getBlock(int x, int y, int z) {
|
||||
return getBlock(new InChunkSectionLocation(x, y, z));
|
||||
}
|
||||
|
||||
public Block getBlock(InChunkSectionLocation loc) {
|
||||
return this.blocks.get(loc);
|
||||
}
|
||||
|
||||
public void setBlock(int x, int y, int z, Block block) {
|
||||
setBlock(new InChunkSectionLocation(x, y, z), block);
|
||||
}
|
||||
|
||||
public void setBlock(InChunkSectionLocation location, Block block) {
|
||||
if (block == null) {
|
||||
this.blocks.remove(location);
|
||||
this.blockEntityMeta.remove(location);
|
||||
return;
|
||||
}
|
||||
this.blocks.put(location, block);
|
||||
this.blockEntityMeta.remove(location);
|
||||
}
|
||||
|
||||
public void setBlockEntityData(InChunkSectionLocation position, BlockEntityMetaData data) {
|
||||
// ToDo check if block is really a block entity (command block, spawner, skull, flower pot)
|
||||
this.blockEntityMeta.put(position, data);
|
||||
}
|
||||
|
||||
public HashMap<InChunkSectionLocation, Block> getBlocks() {
|
||||
return this.blocks;
|
||||
}
|
||||
|
||||
public HashMap<InChunkSectionLocation, BlockEntityMetaData> getBlockEntityMeta() {
|
||||
return this.blockEntityMeta;
|
||||
}
|
||||
|
||||
public HashMap<InChunkSectionLocation, Byte> getLight() {
|
||||
return this.light;
|
||||
}
|
||||
|
||||
public HashMap<InChunkSectionLocation, Byte> getSkyLight() {
|
||||
return this.skyLight;
|
||||
}
|
||||
|
||||
public BlockEntityMetaData getBlockEntityData(InChunkSectionLocation position) {
|
||||
return this.blockEntityMeta.get(position);
|
||||
}
|
||||
|
||||
public void setBlockEntityData(HashMap<InChunkSectionLocation, BlockEntityMetaData> blockEntities) {
|
||||
blockEntities.forEach(this.blockEntityMeta::put);
|
||||
}
|
||||
}
|
52
src/main/java/de/bixilon/minosoft/data/world/ChunkSection.kt
Normal file
52
src/main/java/de/bixilon/minosoft/data/world/ChunkSection.kt
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020 Moritz Zwerger
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
package de.bixilon.minosoft.data.world
|
||||
|
||||
import de.bixilon.minosoft.data.mappings.blocks.Block
|
||||
|
||||
/**
|
||||
* Collection of 16x16x16 blocks
|
||||
*/
|
||||
class ChunkSection constructor(
|
||||
val blocks: MutableMap<InChunkSectionLocation, BlockInfo> = mutableMapOf(),
|
||||
val blocksStaticInfo: MutableMap<InChunkSectionLocation, BlockStaticInfo> = mutableMapOf(),
|
||||
) {
|
||||
|
||||
fun getBlockInfo(location: InChunkSectionLocation): BlockInfo? {
|
||||
return blocks[location]
|
||||
}
|
||||
|
||||
fun setBlockInfo(location: InChunkSectionLocation, blockInfo: BlockInfo?) {
|
||||
if (blockInfo == null) {
|
||||
blocks.remove(location)
|
||||
return
|
||||
}
|
||||
blocks[location] = blockInfo
|
||||
}
|
||||
|
||||
fun getBlockInfo(x: Int, y: Int, z: Int): BlockInfo? {
|
||||
return getBlockInfo(InChunkSectionLocation(x, y, z))
|
||||
}
|
||||
|
||||
fun updateStaticData() {
|
||||
|
||||
}
|
||||
|
||||
fun setRawBlock(location: InChunkSectionLocation, block: Block?) {
|
||||
if (block == null) {
|
||||
setBlockInfo(location, null)
|
||||
return
|
||||
}
|
||||
setBlockInfo(location, BlockInfo(block, info = blocksStaticInfo[location] ?: BlockStaticInfo()))
|
||||
}
|
||||
}
|
@ -30,19 +30,19 @@ public class World {
|
||||
private final HashMap<ChunkLocation, Chunk> chunks = new HashMap<>();
|
||||
private final HashBiMap<Integer, Entity> entityIdMap = HashBiMap.create();
|
||||
private final HashBiMap<UUID, Entity> entityUUIDMap = HashBiMap.create();
|
||||
boolean hardcore;
|
||||
boolean raining;
|
||||
Dimension dimension; // used for sky color, etc
|
||||
private boolean hardcore;
|
||||
private boolean raining;
|
||||
private Dimension dimension; // used for sky color, etc
|
||||
|
||||
public HashMap<ChunkLocation, Chunk> getAllChunks() {
|
||||
return this.chunks;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Block getBlock(BlockPosition pos) {
|
||||
public BlockInfo getBlockInfo(BlockPosition pos) {
|
||||
ChunkLocation loc = pos.getChunkLocation();
|
||||
if (getChunk(loc) != null) {
|
||||
return getChunk(loc).getBlock(pos.getInChunkLocation());
|
||||
return getChunk(loc).getBlockInfo(pos.getInChunkLocation());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -53,7 +53,7 @@ public class World {
|
||||
|
||||
public void setBlock(BlockPosition pos, Block block) {
|
||||
if (getChunk(pos.getChunkLocation()) != null) {
|
||||
getChunk(pos.getChunkLocation()).setBlock(pos.getInChunkLocation(), block);
|
||||
getChunk(pos.getChunkLocation()).setRawBlock(pos.getInChunkLocation(), block);
|
||||
}
|
||||
// do nothing if chunk is unloaded
|
||||
}
|
||||
@ -125,16 +125,17 @@ public class World {
|
||||
if (chunk == null) {
|
||||
return;
|
||||
}
|
||||
chunk.setBlockEntityData(position.getInChunkLocation(), data);
|
||||
var section = chunk.getSections().get(position.getSectionHeight());
|
||||
if (section == null) {
|
||||
return;
|
||||
}
|
||||
var blockInfo = section.getBlockInfo(position.getInChunkSectionLocation());
|
||||
if (blockInfo == null) {
|
||||
return;
|
||||
}
|
||||
blockInfo.setMetaData(data);
|
||||
}
|
||||
|
||||
public BlockEntityMetaData getBlockEntityData(BlockPosition position) {
|
||||
Chunk chunk = this.chunks.get(position.getChunkLocation());
|
||||
if (chunk == null) {
|
||||
return null;
|
||||
}
|
||||
return chunk.getBlockEntityData(position.getInChunkLocation());
|
||||
}
|
||||
|
||||
public void setBlockEntityData(HashMap<BlockPosition, BlockEntityMetaData> blockEntities) {
|
||||
blockEntities.forEach(this::setBlockEntityData);
|
||||
|
@ -53,38 +53,40 @@ class ChunkRenderer(private val connection: Connection, private val world: World
|
||||
val west = world.allChunks[chunkLocation.getLocationByDirection(Directions.WEST)]?.sections?.get(sectionHeight)
|
||||
val east = world.allChunks[chunkLocation.getLocationByDirection(Directions.EAST)]?.sections?.get(sectionHeight)
|
||||
|
||||
for ((position, block) in section.blocks) {
|
||||
val blockBelow: Block? = if (position.y == 0 && below != null) {
|
||||
below.getBlock(position.x, ProtocolDefinition.SECTION_HEIGHT_Y - 1, position.z)
|
||||
for ((position, blockInfo) in section.blocks) {
|
||||
val blockBelow: BlockInfo? = if (position.y == 0 && below != null) {
|
||||
below.getBlockInfo(position.x, ProtocolDefinition.SECTION_HEIGHT_Y - 1, position.z)
|
||||
} else {
|
||||
section.getBlock(position.getLocationByDirection(Directions.DOWN))
|
||||
section.getBlockInfo(position.getLocationByDirection(Directions.DOWN))
|
||||
}
|
||||
val blockAbove: Block? = if (position.y == ProtocolDefinition.SECTION_HEIGHT_Y - 1 && above != null) {
|
||||
above.getBlock(position.x, 0, position.z)
|
||||
val blockAbove: BlockInfo? = if (position.y == ProtocolDefinition.SECTION_HEIGHT_Y - 1 && above != null) {
|
||||
above.getBlockInfo(position.x, 0, position.z)
|
||||
} else {
|
||||
section.getBlock(position.getLocationByDirection(Directions.UP))
|
||||
section.getBlockInfo(position.getLocationByDirection(Directions.UP))
|
||||
}
|
||||
val blockNorth: Block? = if (position.z == 0 && north != null) {
|
||||
north.getBlock(position.x, position.y, ProtocolDefinition.SECTION_WIDTH_Z - 1)
|
||||
val blockNorth: BlockInfo? = if (position.z == 0 && north != null) {
|
||||
north.getBlockInfo(position.x, position.y, ProtocolDefinition.SECTION_WIDTH_Z - 1)
|
||||
} else {
|
||||
section.getBlock(position.getLocationByDirection(Directions.NORTH))
|
||||
section.getBlockInfo(position.getLocationByDirection(Directions.NORTH))
|
||||
}
|
||||
val blockSouth: Block? = if (position.z == ProtocolDefinition.SECTION_WIDTH_Z - 1 && south != null) {
|
||||
south.getBlock(position.x, position.y, 0)
|
||||
val blockSouth: BlockInfo? = if (position.z == ProtocolDefinition.SECTION_WIDTH_Z - 1 && south != null) {
|
||||
south.getBlockInfo(position.x, position.y, 0)
|
||||
} else {
|
||||
section.getBlock(position.getLocationByDirection(Directions.SOUTH))
|
||||
section.getBlockInfo(position.getLocationByDirection(Directions.SOUTH))
|
||||
}
|
||||
val blockWest: Block? = if (position.x == 0 && west != null) {
|
||||
west.getBlock(ProtocolDefinition.SECTION_WIDTH_X - 1, position.y, position.x)
|
||||
val blockWest: BlockInfo? = if (position.x == 0 && west != null) {
|
||||
west.getBlockInfo(ProtocolDefinition.SECTION_WIDTH_X - 1, position.y, position.x)
|
||||
} else {
|
||||
section.getBlock(position.getLocationByDirection(Directions.WEST))
|
||||
section.getBlockInfo(position.getLocationByDirection(Directions.WEST))
|
||||
}
|
||||
val blockEast: Block? = if (position.x == ProtocolDefinition.SECTION_WIDTH_X - 1 && east != null) {
|
||||
east.getBlock(0, position.y, position.z)
|
||||
val blockEast: BlockInfo? = if (position.x == ProtocolDefinition.SECTION_WIDTH_X - 1 && east != null) {
|
||||
east.getBlockInfo(0, position.y, position.z)
|
||||
} else {
|
||||
section.getBlock(position.getLocationByDirection(Directions.EAST))
|
||||
section.getBlockInfo(position.getLocationByDirection(Directions.EAST))
|
||||
}
|
||||
block.getBlockRenderer(BlockPosition(chunkLocation, sectionHeight, position)).render(Vec3(position.x + chunkLocation.x * ProtocolDefinition.SECTION_WIDTH_X, position.y + sectionHeight * ProtocolDefinition.SECTION_HEIGHT_Y, position.z + chunkLocation.z * ProtocolDefinition.SECTION_WIDTH_Z), data, arrayOf(blockBelow, blockAbove, blockNorth, blockSouth, blockWest, blockEast))
|
||||
val worldPosition = Vec3(position.x + chunkLocation.x * ProtocolDefinition.SECTION_WIDTH_X, position.y + sectionHeight * ProtocolDefinition.SECTION_HEIGHT_Y, position.z + chunkLocation.z * ProtocolDefinition.SECTION_WIDTH_Z)
|
||||
|
||||
blockInfo.block.getBlockRenderer(BlockPosition(chunkLocation, sectionHeight, position)).render(blockInfo, worldPosition, arrayOf(blockBelow, blockAbove, blockNorth, blockSouth, blockWest, blockEast))
|
||||
}
|
||||
return data.toFloatArray()
|
||||
}
|
||||
|
@ -57,11 +57,10 @@ public class PacketBlockChange extends ClientboundPacket {
|
||||
|
||||
// tweak
|
||||
if (!connection.getVersion().isFlattened()) {
|
||||
Block block = VersionTweaker.transformBlock(getBlock(), chunk, getPosition().getInChunkLocation());
|
||||
section.setBlock(getPosition().getInChunkLocation().getInChunkSectionLocation(), block);
|
||||
Block block = VersionTweaker.transformBlock(getBlock(), chunk, this.position.getInChunkSectionLocation(), this.position.getSectionHeight());
|
||||
section.setRawBlock(getPosition().getInChunkLocation().getInChunkSectionLocation(), block);
|
||||
} else {
|
||||
Log.debug("Replacing %s with %s", section.getBlock(getPosition().getInChunkLocation().getInChunkSectionLocation()), this.block);
|
||||
section.setBlock(getPosition().getInChunkLocation().getInChunkSectionLocation(), getBlock());
|
||||
section.setRawBlock(getPosition().getInChunkLocation().getInChunkSectionLocation(), getBlock());
|
||||
}
|
||||
|
||||
connection.getRenderer().getRenderWindow().getChunkRenderer().prepareChunkSection(getPosition().getChunkLocation(), sectionHeight, section);
|
||||
|
@ -33,7 +33,7 @@ import static de.bixilon.minosoft.protocol.protocol.ProtocolVersions.*;
|
||||
|
||||
public class PacketMultiBlockChange extends ClientboundPacket {
|
||||
private final HashMap<InChunkLocation, Block> blocks = new HashMap<>();
|
||||
ChunkLocation location;
|
||||
private ChunkLocation location;
|
||||
|
||||
@Override
|
||||
public boolean read(InByteBuffer buffer) {
|
||||
@ -89,16 +89,16 @@ public class PacketMultiBlockChange extends ClientboundPacket {
|
||||
return;
|
||||
}
|
||||
connection.fireEvent(new MultiBlockChangeEvent(connection, this));
|
||||
chunk.setBlocks(getBlocks());
|
||||
chunk.setRawBlocks(getBlocks());
|
||||
|
||||
// tweak
|
||||
if (!connection.getVersion().isFlattened()) {
|
||||
for (Map.Entry<InChunkLocation, Block> entry : getBlocks().entrySet()) {
|
||||
Block block = VersionTweaker.transformBlock(entry.getValue(), chunk, entry.getKey());
|
||||
Block block = VersionTweaker.transformBlock(entry.getValue(), chunk, entry.getKey().getInChunkSectionLocation(), entry.getKey().getSectionHeight());
|
||||
if (block == entry.getValue()) {
|
||||
continue;
|
||||
}
|
||||
chunk.setBlock(entry.getKey(), block);
|
||||
chunk.setRawBlock(entry.getKey(), block);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ package de.bixilon.minosoft.util;
|
||||
|
||||
import de.bixilon.minosoft.data.mappings.Dimension;
|
||||
import de.bixilon.minosoft.data.mappings.blocks.Block;
|
||||
import de.bixilon.minosoft.data.world.BlockInfo;
|
||||
import de.bixilon.minosoft.data.world.Chunk;
|
||||
import de.bixilon.minosoft.data.world.ChunkSection;
|
||||
import de.bixilon.minosoft.data.world.InChunkSectionLocation;
|
||||
@ -24,6 +25,7 @@ import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static de.bixilon.minosoft.protocol.protocol.ProtocolVersions.*;
|
||||
|
||||
@ -56,7 +58,7 @@ public final class ChunkUtil {
|
||||
HashMap<Integer, ChunkSection> sectionMap = new HashMap<>();
|
||||
for (int c = 0; c < ProtocolDefinition.SECTIONS_PER_CHUNK; c++) { // max sections per chunks in chunk column
|
||||
if (BitByte.isBitSet(sectionBitMasks[0], c)) {
|
||||
HashMap<InChunkSectionLocation, Block> blockMap = new HashMap<>();
|
||||
HashMap<InChunkSectionLocation, BlockInfo> blockMap = new HashMap<>();
|
||||
|
||||
for (int nibbleY = 0; nibbleY < ProtocolDefinition.SECTION_HEIGHT_Y; nibbleY++) {
|
||||
for (int nibbleZ = 0; nibbleZ < ProtocolDefinition.SECTION_WIDTH_Z; nibbleZ++) {
|
||||
@ -84,12 +86,12 @@ public final class ChunkUtil {
|
||||
continue;
|
||||
}
|
||||
Block block = buffer.getConnection().getMapping().getBlock(fullBlockId);
|
||||
blockMap.put(new InChunkSectionLocation(nibbleX, nibbleY, nibbleZ), block);
|
||||
blockMap.put(new InChunkSectionLocation(nibbleX, nibbleY, nibbleZ), new BlockInfo(block));
|
||||
arrayPos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
sectionMap.put(dimension.getLowestSection() + c, new ChunkSection(blockMap));
|
||||
sectionMap.put(dimension.getLowestSection() + c, new ChunkSection(blockMap, Map.of())); // ToDo
|
||||
}
|
||||
}
|
||||
return new Chunk(sectionMap);
|
||||
@ -120,7 +122,7 @@ public final class ChunkUtil {
|
||||
if (!BitByte.isBitSet(sectionBitMasks[0], c)) {
|
||||
continue;
|
||||
}
|
||||
HashMap<InChunkSectionLocation, Block> blockMap = new HashMap<>();
|
||||
HashMap<InChunkSectionLocation, BlockInfo> blockMap = new HashMap<>();
|
||||
|
||||
for (int nibbleY = 0; nibbleY < ProtocolDefinition.SECTION_HEIGHT_Y; nibbleY++) {
|
||||
for (int nibbleZ = 0; nibbleZ < ProtocolDefinition.SECTION_WIDTH_Z; nibbleZ++) {
|
||||
@ -131,12 +133,12 @@ public final class ChunkUtil {
|
||||
arrayPos++;
|
||||
continue;
|
||||
}
|
||||
blockMap.put(new InChunkSectionLocation(nibbleX, nibbleY, nibbleZ), block);
|
||||
blockMap.put(new InChunkSectionLocation(nibbleX, nibbleY, nibbleZ), new BlockInfo(block));
|
||||
arrayPos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
sectionMap.put(dimension.getLowestSection() + c, new ChunkSection(blockMap));
|
||||
sectionMap.put(dimension.getLowestSection() + c, new ChunkSection(blockMap, Map.of()));
|
||||
}
|
||||
return new Chunk(sectionMap);
|
||||
}
|
||||
@ -156,7 +158,7 @@ public final class ChunkUtil {
|
||||
|
||||
long[] data = buffer.readLongArray();
|
||||
|
||||
HashMap<InChunkSectionLocation, Block> blockMap = new HashMap<>();
|
||||
HashMap<InChunkSectionLocation, BlockInfo> blockMap = new HashMap<>();
|
||||
for (int nibbleY = 0; nibbleY < ProtocolDefinition.SECTION_HEIGHT_Y; nibbleY++) {
|
||||
for (int nibbleZ = 0; nibbleZ < ProtocolDefinition.SECTION_WIDTH_Z; nibbleZ++) {
|
||||
for (int nibbleX = 0; nibbleX < ProtocolDefinition.SECTION_WIDTH_X; nibbleX++) {
|
||||
@ -187,7 +189,7 @@ public final class ChunkUtil {
|
||||
if (block == null) {
|
||||
continue;
|
||||
}
|
||||
blockMap.put(new InChunkSectionLocation(nibbleX, nibbleY, nibbleZ), block);
|
||||
blockMap.put(new InChunkSectionLocation(nibbleX, nibbleY, nibbleZ), new BlockInfo(block));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -199,7 +201,7 @@ public final class ChunkUtil {
|
||||
}
|
||||
}
|
||||
|
||||
sectionMap.put(dimension.getLowestSection() + c, new ChunkSection(blockMap));
|
||||
sectionMap.put(dimension.getLowestSection() + c, new ChunkSection(blockMap, Map.of()));
|
||||
}
|
||||
if (buffer.getVersionId() < V_19W36A) {
|
||||
byte[] biomes = buffer.readBytes(256);
|
||||
|
Loading…
x
Reference in New Issue
Block a user