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