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