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 }