2020-01-09 03:05:42 +00:00
/ *
2022-01-01 19:03:05 +00:00
* Copyright ( c ) 2019 - 2022 GeyserMC . http : //geysermc.org
2020-01-09 03:05:42 +00:00
*
* Permission is hereby granted , free of charge , to any person obtaining a copy
* of this software and associated documentation files ( the " Software " ) , to deal
* in the Software without restriction , including without limitation the rights
* to use , copy , modify , merge , publish , distribute , sublicense , and / or sell
* copies of the Software , and to permit persons to whom the Software is
* furnished to do so , subject to the following conditions :
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software .
*
* THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
* IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER
* LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING FROM ,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE .
*
* @author GeyserMC
* @link https : //github.com/GeyserMC/Geyser
* /
2021-11-20 23:29:46 +00:00
package org.geysermc.geyser.util ;
2019-08-30 21:47:33 +00:00
2022-10-30 00:23:21 +00:00
import org.cloudburstmc.math.vector.Vector2i ;
import org.cloudburstmc.math.vector.Vector3i ;
import org.cloudburstmc.protocol.bedrock.packet.LevelChunkPacket ;
import org.cloudburstmc.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket ;
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket ;
2021-07-15 01:14:53 +00:00
import io.netty.buffer.ByteBuf ;
2022-10-16 00:26:02 +00:00
import io.netty.buffer.ByteBufAllocator ;
2021-07-15 01:14:53 +00:00
import io.netty.buffer.Unpooled ;
2021-11-14 22:59:14 +00:00
import it.unimi.dsi.fastutil.ints.IntLists ;
2020-10-15 06:30:25 +00:00
import lombok.experimental.UtilityClass ;
2021-11-20 23:29:46 +00:00
import org.geysermc.geyser.entity.type.ItemFrameEntity ;
2022-05-25 19:55:15 +00:00
import org.geysermc.geyser.level.BedrockDimension ;
import org.geysermc.geyser.level.JavaDimension ;
2021-11-20 23:29:46 +00:00
import org.geysermc.geyser.level.block.BlockStateValues ;
import org.geysermc.geyser.level.chunk.BlockStorage ;
import org.geysermc.geyser.level.chunk.GeyserChunkSection ;
import org.geysermc.geyser.level.chunk.bitarray.SingletonBitArray ;
2021-11-20 21:34:30 +00:00
import org.geysermc.geyser.registry.BlockRegistries ;
2021-12-21 02:31:19 +00:00
import org.geysermc.geyser.session.GeyserSession ;
import org.geysermc.geyser.text.GeyserLocale ;
import org.geysermc.geyser.translator.level.block.entity.BedrockOnlyBlockEntity ;
2019-08-30 21:47:33 +00:00
2021-11-20 23:29:46 +00:00
import static org.geysermc.geyser.level.block.BlockStateValues.JAVA_AIR_ID ;
2020-02-09 22:06:22 +00:00
2020-10-15 06:30:25 +00:00
@UtilityClass
2019-08-30 21:47:33 +00:00
public class ChunkUtils {
2021-11-14 22:59:14 +00:00
/ * *
* An empty subchunk .
* /
public static final byte [ ] SERIALIZED_CHUNK_DATA ;
2021-07-15 01:14:53 +00:00
public static final byte [ ] EMPTY_BIOME_DATA ;
static {
2021-11-12 14:42:35 +00:00
ByteBuf byteBuf = Unpooled . buffer ( ) ;
try {
2021-11-14 22:59:14 +00:00
new GeyserChunkSection ( new BlockStorage [ 0 ] )
. writeToNetwork ( byteBuf ) ;
SERIALIZED_CHUNK_DATA = new byte [ byteBuf . readableBytes ( ) ] ;
byteBuf . readBytes ( SERIALIZED_CHUNK_DATA ) ;
} finally {
byteBuf . release ( ) ;
}
byteBuf = Unpooled . buffer ( ) ;
try {
BlockStorage blockStorage = new BlockStorage ( SingletonBitArray . INSTANCE , IntLists . singleton ( 0 ) ) ;
2021-11-12 14:42:35 +00:00
blockStorage . writeToNetwork ( byteBuf ) ;
EMPTY_BIOME_DATA = new byte [ byteBuf . readableBytes ( ) ] ;
byteBuf . readBytes ( EMPTY_BIOME_DATA ) ;
} finally {
byteBuf . release ( ) ;
}
2021-07-15 01:14:53 +00:00
}
2020-04-21 05:32:32 +00:00
2021-11-14 04:49:31 +00:00
public static int indexYZXtoXZY ( int yzx ) {
2020-10-15 06:30:25 +00:00
return ( yzx > > 8 ) | ( yzx & 0x0F0 ) | ( ( yzx & 0x00F ) < < 8 ) ;
}
2019-08-30 21:47:33 +00:00
2021-11-22 19:52:26 +00:00
public static void updateChunkPosition ( GeyserSession session , Vector3i position ) {
2020-03-06 01:26:36 +00:00
Vector2i chunkPos = session . getLastChunkPosition ( ) ;
Vector2i newChunkPos = Vector2i . from ( position . getX ( ) > > 4 , position . getZ ( ) > > 4 ) ;
if ( chunkPos = = null | | ! chunkPos . equals ( newChunkPos ) ) {
NetworkChunkPublisherUpdatePacket chunkPublisherUpdatePacket = new NetworkChunkPublisherUpdatePacket ( ) ;
chunkPublisherUpdatePacket . setPosition ( position ) ;
2022-01-16 01:29:00 +00:00
chunkPublisherUpdatePacket . setRadius ( session . getServerRenderDistance ( ) < < 4 ) ;
2020-05-05 15:51:43 +00:00
session . sendUpstreamPacket ( chunkPublisherUpdatePacket ) ;
2020-03-06 01:26:36 +00:00
session . setLastChunkPosition ( newChunkPos ) ;
}
}
2020-11-17 17:03:12 +00:00
/ * *
2022-05-25 23:17:49 +00:00
* Sends a block update to the Bedrock client . If the platform is not Spigot , this also
2020-11-17 17:03:12 +00:00
* adds that block to the cache .
* @param session the Bedrock session to send / register the block to
* @param blockState the Java block state of the block
* @param position the position of the block
* /
2021-11-22 19:52:26 +00:00
public static void updateBlock ( GeyserSession session , int blockState , Vector3i position ) {
2022-07-10 03:02:19 +00:00
updateBlockClientSide ( session , blockState , position ) ;
session . getChunkCache ( ) . updateBlock ( position . getX ( ) , position . getY ( ) , position . getZ ( ) , blockState ) ;
}
/ * *
* Updates a block , but client - side only .
* /
public static void updateBlockClientSide ( GeyserSession session , int blockState , Vector3i position ) {
2020-05-02 20:44:05 +00:00
// Checks for item frames so they aren't tripped up and removed
2021-05-09 19:44:41 +00:00
ItemFrameEntity itemFrameEntity = ItemFrameEntity . getItemFrameEntity ( session , position ) ;
if ( itemFrameEntity ! = null ) {
if ( blockState = = JAVA_AIR_ID ) { // Item frame is still present and no block overrides that; refresh it
2021-11-19 01:44:03 +00:00
itemFrameEntity . updateBlock ( true ) ;
2022-07-10 03:02:19 +00:00
// Still update the chunk cache with the new block if updateBlock is called
2020-11-05 21:36:22 +00:00
return ;
}
2021-05-09 19:44:41 +00:00
// Otherwise, let's still store our reference to the item frame, but let the new block take precedence for now
2020-05-02 20:44:05 +00:00
}
2022-05-14 19:12:18 +00:00
if ( BlockStateValues . getSkullVariant ( blockState ) = = - 1 ) {
2020-12-04 21:55:24 +00:00
// Skull is gone
2022-05-14 19:12:18 +00:00
session . getSkullCache ( ) . removeSkull ( position ) ;
2020-12-04 21:55:24 +00:00
}
2021-09-10 01:20:25 +00:00
// Prevent moving_piston from being placed
// It's used for extending piston heads, but it isn't needed on Bedrock and causes pistons to flicker
if ( ! BlockStateValues . isMovingPiston ( blockState ) ) {
int blockId = session . getBlockMappings ( ) . getBedrockBlockId ( blockState ) ;
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket ( ) ;
updateBlockPacket . setDataLayer ( 0 ) ;
updateBlockPacket . setBlockPosition ( position ) ;
updateBlockPacket . setRuntimeId ( blockId ) ;
updateBlockPacket . getFlags ( ) . add ( UpdateBlockPacket . Flag . NEIGHBORS ) ;
updateBlockPacket . getFlags ( ) . add ( UpdateBlockPacket . Flag . NETWORK ) ;
session . sendUpstreamPacket ( updateBlockPacket ) ;
UpdateBlockPacket waterPacket = new UpdateBlockPacket ( ) ;
waterPacket . setDataLayer ( 1 ) ;
waterPacket . setBlockPosition ( position ) ;
if ( BlockRegistries . WATERLOGGED . get ( ) . contains ( blockState ) ) {
2022-10-30 03:02:11 +00:00
waterPacket . setRuntimeId ( session . getBlockMappings ( ) . getBedrockWater ( ) . getRuntimeId ( ) ) ;
2021-09-10 01:20:25 +00:00
} else {
2022-10-30 03:02:11 +00:00
waterPacket . setRuntimeId ( session . getBlockMappings ( ) . getBedrockAir ( ) . getRuntimeId ( ) ) ;
2021-09-10 01:20:25 +00:00
}
session . sendUpstreamPacket ( waterPacket ) ;
2019-12-21 05:05:20 +00:00
}
2020-03-26 02:03:46 +00:00
2021-09-04 18:55:22 +00:00
BlockStateValues . getLecternBookStates ( ) . handleBlockChange ( session , blockState , position ) ;
2020-12-28 05:29:27 +00:00
2021-06-01 19:36:33 +00:00
// Iterates through all Bedrock-only block entity translators and determines if a manual block entity packet
// needs to be sent
2021-07-13 01:19:40 +00:00
for ( BedrockOnlyBlockEntity bedrockOnlyBlockEntity : BlockEntityUtils . BEDROCK_ONLY_BLOCK_ENTITIES ) {
2021-06-01 19:36:33 +00:00
if ( bedrockOnlyBlockEntity . isBlock ( blockState ) ) {
2020-05-05 02:32:02 +00:00
// Flower pots are block entities only in Bedrock and are not updated anywhere else like note blocks
2021-06-01 19:36:33 +00:00
bedrockOnlyBlockEntity . updateBlock ( session , blockState , position ) ;
2020-05-01 05:51:23 +00:00
break ; //No block will be a part of two classes
2020-04-21 05:32:32 +00:00
}
}
2019-12-21 05:05:20 +00:00
}
2021-12-29 05:25:33 +00:00
public static void sendEmptyChunk ( GeyserSession session , int chunkX , int chunkZ , boolean forceUpdate ) {
2022-10-16 00:26:02 +00:00
BedrockDimension bedrockDimension = session . getChunkCache ( ) . getBedrockDimension ( ) ;
int bedrockSubChunkCount = bedrockDimension . height ( ) > > 4 ;
// Allocate output buffer
ByteBuf byteBuf = ByteBufAllocator . DEFAULT . buffer ( ChunkUtils . EMPTY_BIOME_DATA . length * bedrockSubChunkCount + 1 ) ; // Consists only of biome data and border blocks
try {
byteBuf . writeBytes ( EMPTY_BIOME_DATA ) ;
for ( int i = 1 ; i < bedrockSubChunkCount ; i + + ) {
byteBuf . writeByte ( ( 127 < < 1 ) | 1 ) ;
}
byteBuf . writeByte ( 0 ) ; // Border blocks - Edu edition only
} finally {
byteBuf . release ( ) ;
}
2021-12-29 05:25:33 +00:00
LevelChunkPacket data = new LevelChunkPacket ( ) ;
data . setChunkX ( chunkX ) ;
data . setChunkZ ( chunkZ ) ;
data . setSubChunksLength ( 0 ) ;
2022-10-30 03:02:11 +00:00
data . setData ( byteBuf ) ;
2021-12-29 05:25:33 +00:00
data . setCachingEnabled ( false ) ;
session . sendUpstreamPacket ( data ) ;
if ( forceUpdate ) {
Vector3i pos = Vector3i . from ( chunkX < < 4 , 80 , chunkZ < < 4 ) ;
UpdateBlockPacket blockPacket = new UpdateBlockPacket ( ) ;
blockPacket . setBlockPosition ( pos ) ;
blockPacket . setDataLayer ( 0 ) ;
blockPacket . setRuntimeId ( 1 ) ;
session . sendUpstreamPacket ( blockPacket ) ;
}
}
2021-11-22 19:52:26 +00:00
public static void sendEmptyChunks ( GeyserSession session , Vector3i position , int radius , boolean forceUpdate ) {
2019-12-28 13:35:21 +00:00
int chunkX = position . getX ( ) > > 4 ;
int chunkZ = position . getZ ( ) > > 4 ;
2019-12-29 03:17:00 +00:00
for ( int x = - radius ; x < = radius ; x + + ) {
for ( int z = - radius ; z < = radius ; z + + ) {
2021-12-29 05:25:33 +00:00
sendEmptyChunk ( session , chunkX + x , chunkZ + z , forceUpdate ) ;
2019-12-28 13:35:21 +00:00
}
}
}
2021-06-08 03:09:42 +00:00
/ * *
2021-10-10 15:21:48 +00:00
* Process the minimum and maximum heights for this dimension , and processes the world coordinate scale .
2021-07-15 01:46:56 +00:00
* This must be done after the player has switched dimensions so we know what their dimension is
2021-06-08 03:09:42 +00:00
* /
2022-05-25 19:55:15 +00:00
public static void loadDimension ( GeyserSession session ) {
JavaDimension dimension = session . getDimensionType ( ) ;
int minY = dimension . minY ( ) ;
int maxY = dimension . maxY ( ) ;
2021-06-08 03:09:42 +00:00
if ( minY % 16 ! = 0 ) {
throw new RuntimeException ( " Minimum Y must be a multiple of 16! " ) ;
}
if ( maxY % 16 ! = 0 ) {
throw new RuntimeException ( " Maximum Y must be a multiple of 16! " ) ;
}
2022-04-17 23:53:06 +00:00
BedrockDimension bedrockDimension = switch ( session . getDimension ( ) ) {
case DimensionUtils . THE_END - > BedrockDimension . THE_END ;
case DimensionUtils . NETHER - > DimensionUtils . isCustomBedrockNetherId ( ) ? BedrockDimension . THE_END : BedrockDimension . THE_NETHER ;
default - > BedrockDimension . OVERWORLD ;
} ;
session . getChunkCache ( ) . setBedrockDimension ( bedrockDimension ) ;
2021-07-17 00:06:09 +00:00
2021-07-17 00:18:13 +00:00
// Yell in the console if the world height is too height in the current scenario
// The constraints change depending on if the player is in the overworld or not, and if experimental height is enabled
2022-04-17 23:53:06 +00:00
// (Ignore this for the Nether. We can't change that at the moment without the workaround. :/ )
if ( minY < bedrockDimension . minY ( ) | | ( bedrockDimension . doUpperHeightWarn ( ) & & maxY > bedrockDimension . height ( ) ) ) {
2021-11-20 23:29:46 +00:00
session . getGeyser ( ) . getLogger ( ) . warning ( GeyserLocale . getLocaleStringLog ( " geyser.network.translator.chunk.out_of_bounds " ,
2022-04-17 23:53:06 +00:00
String . valueOf ( bedrockDimension . minY ( ) ) ,
String . valueOf ( bedrockDimension . height ( ) ) ,
2021-11-12 14:42:35 +00:00
session . getDimension ( ) ) ) ;
2021-06-08 03:09:42 +00:00
}
session . getChunkCache ( ) . setMinY ( minY ) ;
2021-06-29 14:09:47 +00:00
session . getChunkCache ( ) . setHeightY ( maxY ) ;
2021-10-10 15:21:48 +00:00
2022-05-25 19:55:15 +00:00
session . getWorldBorder ( ) . setWorldCoordinateScale ( dimension . worldCoordinateScale ( ) ) ;
2021-06-08 03:09:42 +00:00
}
2019-08-30 21:47:33 +00:00
}