1 /** 2 Copyright: Copyright (c) 2016-2017 Andrey Penechko. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 module voxelman.world.blockentity.blockentityaccess; 7 8 import voxelman.log; 9 import std..string; 10 import voxelman.math; 11 import voxelman.geometry.box; 12 import voxelman.core.config; 13 import voxelman.world.block; 14 import voxelman.world.storage; 15 import voxelman.blockentity.plugin; 16 17 import voxelman.world.blockentity; 18 19 20 ushort boxEntityIndex(Box blockBox) { 21 return BlockChunkIndex(blockBox.position).index; 22 } 23 24 ulong payloadFromIdAndEntityData(ushort id, ulong entityData) { 25 ulong payload = cast(ulong)id << 46 | entityData & ENTITY_DATA_MASK; 26 return payload; 27 } 28 29 // get chunk local piece of world space box 30 Box chunkLocalBlockBox(ChunkWorldPos cwp, Box blockBox) { 31 Box chunkBlockBox = chunkToBlockBox(cwp); 32 auto intersection = boxIntersection(chunkBlockBox, blockBox); 33 assert(!intersection.empty); 34 auto chunkLocalBox = intersection; 35 chunkLocalBox.position -= chunkBlockBox.position; 36 return chunkLocalBox; 37 } 38 39 // Will not place entity if blockBox lies in non-loaded chunk. 40 void placeEntity(WorldBox blockBox, ulong payload, 41 WorldAccess worldAccess, BlockEntityAccess entityAccess) 42 { 43 ushort dimension = blockBox.dimension; 44 Box affectedChunks = blockBoxToChunkBox(blockBox); 45 46 foreach(chunkPos; affectedChunks.positions) { 47 auto cwp = ChunkWorldPos(chunkPos, dimension); 48 if (!worldAccess.isChunkLoaded(cwp)) return; 49 } 50 51 auto mainCwp = ChunkWorldPos(BlockWorldPos(blockBox.position, blockBox.dimension)); 52 Box mainChunkBox = chunkLocalBlockBox(mainCwp, blockBox); 53 ushort mainBlockIndex = boxEntityIndex(mainChunkBox); 54 auto mainData = BlockEntityData( 55 BlockEntityType.localBlockEntity, payload); 56 57 foreach(chunkPos; affectedChunks.positions) { 58 auto cwp = ChunkWorldPos(chunkPos, dimension); 59 Box chunkLocalBox = chunkLocalBlockBox(cwp, blockBox); 60 61 ushort blockIndex = boxEntityIndex(chunkLocalBox); 62 BlockId blockId = blockIdFromBlockEntityIndex(blockIndex); 63 64 if (cwp == mainCwp) 65 { 66 entityAccess.setBlockEntity(cwp, mainBlockIndex, mainData); 67 } 68 else 69 { 70 ivec3 moff = cwp.xyz - mainCwp.xyz; 71 ubyte[3] mainOffset = [cast(ubyte)moff.x, 72 cast(ubyte)moff.y, cast(ubyte)moff.z]; 73 auto data = BlockEntityData(BlockEntityType.foreignBlockEntity, 74 mainData.id, mainOffset, mainBlockIndex); 75 entityAccess.setBlockEntity(cwp, blockIndex, data); 76 } 77 worldAccess.fillChunkBox(cwp, chunkLocalBox, blockId); 78 } 79 } 80 81 void placeChunkEntity(WorldBox blockBox, ulong payload, 82 WorldAccess worldAccess, BlockEntityAccess entityAccess) 83 { 84 auto corner = BlockWorldPos(blockBox.position, blockBox.dimension); 85 auto cwp = ChunkWorldPos(corner); 86 87 // limit entity to a single chunk 88 Box chunkLocalBox = chunkLocalBlockBox(cwp, blockBox); 89 90 ushort blockIndex = boxEntityIndex(chunkLocalBox); 91 BlockId blockId = blockIdFromBlockEntityIndex(blockIndex); 92 worldAccess.fillChunkBox(cwp, chunkLocalBox, blockId); 93 auto beData = BlockEntityData(BlockEntityType.localBlockEntity, payload); 94 bool placed = entityAccess.setBlockEntity(cwp, blockIndex, beData); 95 } 96 97 WorldBox getBlockEntityBox(ChunkWorldPos cwp, ushort blockIndex, 98 BlockEntityInfoTable blockEntityInfos, BlockEntityAccess entityAccess) 99 { 100 BlockEntityData entity = entityAccess.getBlockEntity(cwp, blockIndex); 101 102 with(BlockEntityType) final switch(entity.type) 103 { 104 case localBlockEntity: 105 BlockEntityInfo eInfo = blockEntityInfos[entity.id]; 106 auto entityBwp = BlockWorldPos(cwp, blockIndex); 107 WorldBox eVol = eInfo.boxHandler(entityBwp, entity); 108 return eVol; 109 case foreignBlockEntity: 110 auto mainPtr = entity.mainChunkPointer; 111 auto mainCwp = ChunkWorldPos(ivec3(cwp.xyz) - mainPtr.mainChunkOffset, cwp.w); 112 BlockEntityData mainEntity = entityAccess.getBlockEntity(mainCwp, mainPtr.blockIndex); 113 auto mainBwp = BlockWorldPos(mainCwp, mainPtr.blockIndex); 114 115 BlockEntityInfo eInfo = blockEntityInfos[mainPtr.entityId]; 116 WorldBox eVol = eInfo.boxHandler(mainBwp, mainEntity); 117 return eVol; 118 } 119 } 120 121 /// Returns changed box 122 WorldBox removeEntity(BlockWorldPos bwp, BlockEntityInfoTable beInfos, 123 WorldAccess worldAccess, BlockEntityAccess entityAccess, 124 BlockId fillerBlock) 125 { 126 BlockId blockId = worldAccess.getBlock(bwp); 127 if (!isBlockEntity(blockId)) 128 return WorldBox(); 129 130 auto mainCwp = ChunkWorldPos(bwp); 131 ushort mainBlockIndex = blockEntityIndexFromBlockId(blockId); 132 WorldBox blockBox = getBlockEntityBox(mainCwp, mainBlockIndex, beInfos, entityAccess); 133 134 Box affectedChunks = blockBoxToChunkBox(blockBox); 135 ushort dimension = blockBox.dimension; 136 foreach(chunkPos; affectedChunks.positions) { 137 auto cwp = ChunkWorldPos(chunkPos, dimension); 138 Box chunkLocalBox = chunkLocalBlockBox(cwp, blockBox); 139 140 ushort blockIndex = boxEntityIndex(chunkLocalBox); 141 142 entityAccess.removeEntity(cwp, blockIndex); 143 worldAccess.fillChunkBox(cwp, chunkLocalBox, fillerBlock); 144 } 145 146 return blockBox; 147 } 148 149 final class BlockEntityAccess 150 { 151 private ChunkManager chunkManager; 152 153 this(ChunkManager chunkManager) { 154 this.chunkManager = chunkManager; 155 } 156 157 bool setBlockEntity(ChunkWorldPos cwp, ushort blockIndex, BlockEntityData beData) 158 { 159 assert((blockIndex & BLOCK_ENTITY_FLAG) == 0); 160 if (!chunkManager.isChunkLoaded(cwp)) return false; 161 162 WriteBuffer* writeBuffer = chunkManager.getOrCreateWriteBuffer(cwp, 163 ENTITY_LAYER, WriteBufferPolicy.copySnapshotArray); 164 assert(writeBuffer); 165 166 BlockEntityMap map = getHashMapFromLayer(writeBuffer.layer); 167 map[blockIndex] = beData.storage; 168 setLayerMap(writeBuffer.layer, map); 169 writeBuffer.removeSnapshot = false; 170 return true; 171 } 172 173 BlockEntityData getBlockEntity(ChunkWorldPos cwp, ushort blockIndex) 174 { 175 assert((blockIndex & BLOCK_ENTITY_FLAG) == 0); 176 auto entities = chunkManager.getChunkSnapshot(cwp, ENTITY_LAYER, Yes.Uncompress); 177 if (entities.isNull) return BlockEntityData.init; 178 if (entities.type == StorageType.uniform) return BlockEntityData.init; 179 180 BlockEntityMap map = getHashMapFromLayer(entities); 181 182 ulong* entity = blockIndex in map; 183 if (entity is null) return BlockEntityData.init; 184 185 return BlockEntityData(*entity); 186 } 187 188 bool removeEntity(ChunkWorldPos cwp, ushort blockIndex) 189 { 190 assert((blockIndex & BLOCK_ENTITY_FLAG) == 0); 191 if (!chunkManager.isChunkLoaded(cwp)) return false; 192 193 WriteBuffer* writeBuffer = chunkManager.getOrCreateWriteBuffer(cwp, 194 ENTITY_LAYER, WriteBufferPolicy.copySnapshotArray); 195 assert(writeBuffer); 196 197 BlockEntityMap map = getHashMapFromLayer(writeBuffer.layer); 198 199 map.remove(blockIndex); 200 201 if (map.length == 0) 202 writeBuffer.removeSnapshot = true; 203 204 setLayerMap(writeBuffer.layer, map); 205 206 return true; 207 } 208 } 209 210 void setLayerMap(Layer)(ref Layer layer, BlockEntityMap map) { 211 ubyte[] arr = map.getTable(); 212 layer.dataPtr = arr.ptr; 213 layer.dataLength = cast(LayerDataLenType)arr.length; 214 layer.metadata = cast(ushort)map.length; 215 } 216 217 BlockEntityMap getHashMapFromLayer(Layer)(const ref Layer layer) { 218 if (layer.type == StorageType.uniform) 219 return BlockEntityMap(); 220 return BlockEntityMap(layer.getArray!ubyte, layer.metadata); 221 }