1 /*
2  * Copyright (c) 2017-2019 sel-project
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in all
12  * copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20  * SOFTWARE.
21  *
22  */
23 /**
24  * Copyright: Copyright (c) 2017-2019 sel-project
25  * License: MIT
26  * Authors: Kripth
27  * Source: $(HTTP github.com/sel-project/selery/source/selery/world/chunk.d, selery/world/chunk.d)
28  */
29 module selery.world.chunk;
30 
31 import std.algorithm : sort;
32 import std.conv : to;
33 import std.math : abs, ceil, log2;
34 import std.path : dirSeparator;
35 import std.random : randomShuffle;
36 import std..string : split, join, endsWith;
37 
38 import memutils.utils : ThreadMem;
39 
40 import selery.about : block_t;
41 import selery.block.block : Block;
42 import selery.block.blocks : BlockStorage;
43 import selery.block.tile : Tile;
44 import selery.math.vector;
45 import selery.world.world : World;
46 
47 import sul.biomes : Biome, Biomes;
48 
49 /**
50  * Classic chunk with the size of 16 * 16.
51  */
52 class Chunk {
53 
54 	// default height
55 	public enum uint HEIGHT = 256;
56 	
57 	private ChunkPosition n_position;
58 	private World n_world;
59 
60 	private Section[size_t] n_sections;
61 	private size_t highest_section;
62 
63 	public ubyte[16 * 16 * 2] lights = 255;
64 	public const(Biome)[16 * 16] biomes = Biomes.plains;
65 
66 	public bool saveChangedBlocks = false;
67 	public BlockPosition[] changed_blocks;
68 	public Tile[uint] changed_tiles;
69 	
70 	public Tile[ushort] tiles;
71 
72 	// snowing informations
73 	private Vector2!ubyte[] next_snow;
74 
75 	public @safe this(World world, ChunkPosition position) {
76 		this.n_world = world;
77 		this.n_position = position;
78 	}
79 
80 	/// Returns the position of the chunk in the world.
81 	public final pure nothrow @property @safe @nogc const ChunkPosition position() {
82 		return this.n_position;
83 	}
84 
85 	/// ditto
86 	public final pure nothrow @property @safe @nogc const int x() {
87 		return this.position.x;
88 	}
89 
90 	/// ditto
91 	public final pure nothrow @property @safe @nogc const int z() {
92 		return this.position.z;
93 	}
94 
95 	/// Gets the chunk's world.
96 	public final pure nothrow @property @safe @nogc World world() {
97 		return this.n_world;
98 	}
99 	
100 	/// Gets the world's blocks
101 	public final pure nothrow @property @safe @nogc BlockStorage blocks() {
102 		return this.world.blocks;
103 	}
104 
105 	/**
106 	 * Gets a block in the chunk.
107 	 * Params:
108 	 * 		x = x coordinates in range 0..16
109 	 * 		y = y coordinate in range 0..HEIGHT
110 	 * 		z = z coordinate in range 0..16
111 	 */
112 	public Block* opIndex(BlockPosition position) {
113 		return this.opIndex(position.x & 15, position.y, position.z & 15);
114 	}
115 
116 	/// ditto
117 	public Block* opIndex(ubyte x, size_t y, ubyte z) {
118 		immutable sectiony = y >> 4;
119 		if(auto s = (sectiony in this.n_sections)) {
120 			return (*s)[x, y & 15, z];
121 		} else {
122 			return null;
123 		}
124 	}
125 
126 	/**
127 	 * Sets a block in the chunk.
128 	 * Params:
129 	 * 		block = the block to be set
130 	 * 		x = x coordinates in range 0..16
131 	 * 		y = y coordinate in range 0..HEIGHT
132 	 * 		z = z coordinate in range 0..16
133 	 */
134 	public Block* opIndexAssign(Block* block, BlockPosition position) {
135 
136 		if(block && (*block).id == 0) block = null;
137 
138 		immutable sy = position.y >> 4;
139 		Section* section = sy in this.n_sections;
140 		if(section is null) {
141 			if(block is null) return null;
142 			section = this.createSection(sy);
143 		}
144 
145 		auto ptr = (*section)[position.x & 15, position.y & 15, position.z & 15] = block;
146 
147 		if((*section).empty) {
148 			this.removeSection(sy);
149 		}
150 
151 		//TODO recalculate highest air block
152 
153 		if(this.saveChangedBlocks) {
154 			this.changed_blocks ~= position;
155 		}
156 
157 		auto spos = shortBlockPosition(position);
158 		if(spos in this.tiles) {
159 			//this.translatable_tiles.remove(this.tiles[spos].tid);
160 			this.tiles[spos].unplace();
161 			this.tiles.remove(spos);
162 		}
163 
164 		return ptr;
165 	}
166 
167 	/// ditto
168 	public Block* opIndexAssign(block_t block, BlockPosition position) {
169 		return this.opIndexAssign(block in this.blocks, position);
170 	}
171 	
172 	/// ditto
173 	public Block* opIndexAssign(Block* block, ubyte x, uint y, ubyte z) {
174 		return this.opIndexAssign(block, BlockPosition(x, y, z));
175 	}
176 	
177 	/// ditto
178 	public Block* opIndexAssign(block_t block, ubyte x, uint y, ubyte z) {
179 		return this.opIndexAssign(block, BlockPosition(x, y, z));
180 	}
181 
182 	/// Registers a tile.
183 	public @safe void registerTile(T)(T tile) if(is(T : Tile) && is(T : Block)) {
184 		this.tiles[shortBlockPosition(tile.position & [15, 255, 15])] = tile;
185 		this.changed_tiles[shortBlockPosition(tile.position)] = tile;
186 	}
187 
188 	/// Gets a tile.
189 	public @safe T tileAt(T)(BlockPosition position) {
190 		auto s = shortBlockPosition(position) in this.tiles;
191 		if(s) {
192 			return cast(T)*s;
193 		} else {
194 			return null;
195 		}
196 	}
197 
198 	/**
199 	 * Gets the y position of the first non-air block
200 	 * from top.
201 	 * Returns: the y position of the block or -1
202 	 * Example:
203 	 * ---
204 	 * if(chunk.firstBlock(12, 9) == -1) {
205 	 *    d("The column is air-only");
206 	 * }
207 	 * ---
208 	 */
209 	public ptrdiff_t firstBlock(ubyte x, ubyte z) {
210 		foreach_reverse(size_t y ; 0..(this.highest_section*16)+16) {
211 			if(this[x, y, z] !is null) return y;
212 		}
213 		return -1;
214 	}
215 
216 	/**
217 	 * Checks whether or not a section is empty
218 	 * (has no blocks in it).
219 	 */
220 	public bool emptySection(size_t y) {
221 		auto ptr = y in this.n_sections;
222 		return ptr is null || (*ptr).empty;
223 	}
224 
225 	/**
226 	 * Checks if a section contains blocks with the random tick.
227 	 */
228 	public bool tickSection(size_t y) {
229 		return y in this.n_sections ? this.n_sections[y].tick : false;
230 	}
231 
232 	/**
233 	 * Gets a section.
234 	 */
235 	public Section sectionAt(size_t y) {
236 		return this.n_sections[y];
237 	}
238 
239 	/// ditto
240 	public Section opIndex(size_t y) {
241 		return this.sectionAt(y);
242 	}
243 
244 	public Section* opBinaryRight(string op)(size_t y) if(op == "in") {
245 		return y in this.n_sections;
246 	}
247 
248 	public Section* createSection(size_t y) {
249 		//this.n_sections[y] = new Section();
250 		this.n_sections[y] = ThreadMem.alloc!Section();
251 		if(y > this.highest_section) {
252 			this.highest_section = y;
253 		}
254 		return y in this.n_sections;
255 	}
256 
257 	public void removeSection(size_t y) {
258 		auto section = y in this.n_sections;
259 		if(y) {
260 			ThreadMem.free(*section);
261 			this.n_sections.remove(y);
262 			if(y == this.highest_section) {
263 				size_t[] keys = this.n_sections.keys;
264 				if(keys.length) {
265 					sort(keys);
266 					this.highest_section = keys[$-1];
267 				} else {
268 					this.highest_section = 0;
269 				}
270 			}
271 		}
272 	}
273 
274 	/**
275 	 * Gets the sections.
276 	 */
277 	public final pure nothrow @property @safe @nogc Section[size_t] sections() {
278 		return this.n_sections;
279 	}
280 
281 	/**
282 	 * Checks whether or not the chunks is air.
283 	 */
284 	public final pure nothrow @property @safe @nogc bool empty() {
285 		return this.n_sections.length == 0;
286 	}
287 
288 	/**
289 	 * Gets or generate the position for the next block
290 	 * where the snow should fall.
291 	 */
292 	public @property @safe Vector2!ubyte nextSnow() {
293 		if(this.next_snow.length == 0) {
294 			Vector2!ubyte[] positions;
295 			foreach(ubyte x ; 0..16) {
296 				foreach(ubyte z ; 0..16) {
297 					positions ~= Vector2!ubyte(x, z);
298 				}
299 			}
300 			randomShuffle(positions, this.world.random);
301 			this.next_snow = positions;
302 		}
303 		auto ret = this.next_snow[$-1];
304 		this.next_snow.length = this.next_snow.length - 1;
305 		return ret;
306 	}
307 
308 	/// Resets the snow
309 	public @safe void resetSnow() {
310 		this.next_snow.length = 0;
311 	}
312 
313 	public void save() {
314 		//TODO
315 	}
316 
317 	/// Unloads a chunks and frees its memory
318 	public void unload() {
319 		this.save();
320 		//this.sections.call!"unload"();
321 		foreach(Tile tile ; this.tiles) {
322 			tile.unplace();
323 		}
324 		// free memory
325 		foreach(section ; this.n_sections) {
326 			ThreadMem.free(section);
327 		}
328 	}
329 
330 }
331 
332 class Section {
333 
334 	public enum order = "yxz".dup;
335 
336 	private Block*[4096] n_blocks;
337 	private ubyte[2048] n_sky_light = 255;
338 	private ubyte[2048] n_blocks_light = 0;
339 
340 	private size_t n_amount = 0;
341 	private size_t n_random_ticked = 0;
342 
343 	public pure nothrow @property @safe @nogc ref auto blocks() {
344 		return this.n_blocks;
345 	}
346 	
347 	public @property auto blocks(Block*[4096] blocks) {
348 		this.n_blocks = blocks;
349 		foreach(ref Block* block ; this.n_blocks) {
350 			if(block) {
351 				if((*block).id == 0) {
352 					block = null;
353 				} else {
354 					this.n_amount++;
355 					if((*block).doRandomTick) this.n_random_ticked++;
356 				}
357 			}
358 		}
359 		return this.n_blocks;
360 	}
361 
362 	public pure nothrow @property @safe @nogc auto skyLight() {
363 		return this.n_sky_light;
364 	}
365 	
366 	public pure nothrow @property @safe auto skyLight(ubyte[2048] skyLight) {
367 		return this.n_sky_light = skyLight.dup;
368 	}
369 
370 	public pure nothrow @property @safe @nogc auto blocksLight() {
371 		return this.n_blocks_light;
372 	}
373 	
374 	public pure nothrow @property @safe auto blocksLight(ubyte[2048] blocksLight) {
375 		return this.n_blocks_light = blocksLight.dup;
376 	}
377 
378 	public pure nothrow @property @safe @nogc bool empty() {
379 		return this.n_amount == 0;
380 	}
381 
382 	public pure nothrow @property @safe @nogc bool full() {
383 		return this.n_amount == 4096;
384 	}
385 
386 	public pure nothrow @property @safe @nogc bool tick() {
387 		return this.n_random_ticked > 0;
388 	}
389 
390 	private Block** blockAt(ubyte x, ubyte y, ubyte z) {
391 		return &this.n_blocks[y << 8 | x << 4 | z];
392 	}
393 
394 	public Block* opIndex(ubyte x, ubyte y, ubyte z) {
395 		return *this.blockAt(x, y, z);
396 	}
397 
398 	public Block* opIndexAssign(Block* block, ubyte x, ubyte y, ubyte z) {
399 
400 		Block** ptr = this.blockAt(x, y, z);
401 
402 		if(block && (*block).id == 0) block = null;
403 		Block* old = *ptr;
404 
405 		bool old_air = old is null;
406 		bool new_air = block is null;
407 
408 		// update the number of blocks
409 		if(old_air ^ new_air) {
410 			if(old_air) this.n_amount++;
411 			else this.n_amount--;
412 		}
413 
414 		bool old_rt = !old_air && (*old).doRandomTick;
415 		bool new_rt = !new_air && (*block).doRandomTick;
416 
417 		// update the number of random-ticked blocks
418 		if(old_rt ^ new_rt) {
419 			if(old_rt) this.n_random_ticked--;
420 			else this.n_random_ticked++;
421 		}
422 
423 		return *ptr = block;
424 
425 	}
426 
427 }