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 }