1 /*
2  * Copyright (c) 2017-2018 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: 2017-2018 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.all;
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 	public immutable string location;
60 
61 	private Section[size_t] n_sections;
62 	private size_t highest_section;
63 
64 	public ubyte[16 * 16 * 2] lights = 255;
65 	public const(Biome)[16 * 16] biomes = Biomes.plains;
66 
67 	public bool saveChangedBlocks = false;
68 	public BlockPosition[] changed_blocks;
69 	public Tile[uint] changed_tiles;
70 	
71 	public Tile[ushort] tiles;
72 
73 	// snowing informations
74 	private Vector2!ubyte[] next_snow;
75 
76 	public @safe this(World world, ChunkPosition position, string location=null) {
77 		this.n_world = world;
78 		if(location is null) {
79 			this.location = "worlds" ~ dirSeparator ~ world.name ~ dirSeparator ~ "chunks" ~ dirSeparator ~ to!string(position.x) ~ "_" ~ to!string(position.z) ~ ".sc";
80 		} else {
81 			assert(location.endsWith(dirSeparator ~ to!string(position.x) ~ "_" ~ to!string(position.z) ~ ".sc"));
82 			this.location = location;	
83 		}
84 		this.n_position = position;
85 	}
86 
87 	/// Returns the position of the chunk in the world.
88 	public final pure nothrow @property @safe @nogc const ChunkPosition position() {
89 		return this.n_position;
90 	}
91 
92 	/// ditto
93 	public final pure nothrow @property @safe @nogc const int x() {
94 		return this.position.x;
95 	}
96 
97 	/// ditto
98 	public final pure nothrow @property @safe @nogc const int z() {
99 		return this.position.z;
100 	}
101 
102 	/// Gets the chunk's world.
103 	public final pure nothrow @property @safe @nogc World world() {
104 		return this.n_world;
105 	}
106 	
107 	/// Gets the world's blocks
108 	public final pure nothrow @property @safe @nogc BlockStorage blocks() {
109 		return this.world.blocks;
110 	}
111 
112 	/**
113 	 * Gets a block in the chunk.
114 	 * Params:
115 	 * 		x = x coordinates in range 0..16
116 	 * 		y = y coordinate in range 0..HEIGHT
117 	 * 		z = z coordinate in range 0..16
118 	 */
119 	public Block* opIndex(BlockPosition position) {
120 		return this.opIndex(position.x & 15, position.y, position.z & 15);
121 	}
122 
123 	/// ditto
124 	public Block* opIndex(ubyte x, size_t y, ubyte z) {
125 		immutable sectiony = y >> 4;
126 		if(auto s = (sectiony in this.n_sections)) {
127 			return (*s)[x, y & 15, z];
128 		} else {
129 			return null;
130 		}
131 	}
132 
133 	/**
134 	 * Sets a block in the chunk.
135 	 * Params:
136 	 * 		block = the block to be set
137 	 * 		x = x coordinates in range 0..16
138 	 * 		y = y coordinate in range 0..HEIGHT
139 	 * 		z = z coordinate in range 0..16
140 	 */
141 	public Block* opIndexAssign(Block* block, BlockPosition position) {
142 
143 		if(block && (*block).id == 0) block = null;
144 
145 		immutable sy = position.y >> 4;
146 		Section* section = sy in this.n_sections;
147 		if(section is null) {
148 			if(block is null) return null;
149 			section = this.createSection(sy);
150 		}
151 
152 		auto ptr = (*section)[position.x & 15, position.y & 15, position.z & 15] = block;
153 
154 		if((*section).empty) {
155 			this.removeSection(sy);
156 		}
157 
158 		//TODO recalculate highest air block
159 
160 		if(this.saveChangedBlocks) {
161 			this.changed_blocks ~= position;
162 		}
163 
164 		auto spos = shortBlockPosition(position);
165 		if(spos in this.tiles) {
166 			//this.translatable_tiles.remove(this.tiles[spos].tid);
167 			this.tiles[spos].unplace();
168 			this.tiles.remove(spos);
169 		}
170 
171 		return ptr;
172 	}
173 
174 	/// ditto
175 	public Block* opIndexAssign(block_t block, BlockPosition position) {
176 		return this.opIndexAssign(block in this.blocks, position);
177 	}
178 	
179 	/// ditto
180 	public Block* opIndexAssign(Block* block, ubyte x, uint y, ubyte z) {
181 		return this.opIndexAssign(block, BlockPosition(x, y, z));
182 	}
183 	
184 	/// ditto
185 	public Block* opIndexAssign(block_t block, ubyte x, uint y, ubyte z) {
186 		return this.opIndexAssign(block, BlockPosition(x, y, z));
187 	}
188 
189 	/// Registers a tile.
190 	public @safe void registerTile(T)(T tile) if(is(T : Tile) && is(T : Block)) {
191 		this.tiles[shortBlockPosition(tile.position & [15, 255, 15])] = tile;
192 		this.changed_tiles[shortBlockPosition(tile.position)] = tile;
193 	}
194 
195 	/// Gets a tile.
196 	public @safe T tileAt(T)(BlockPosition position) {
197 		auto s = shortBlockPosition(position) in this.tiles;
198 		if(s) {
199 			return cast(T)*s;
200 		} else {
201 			return null;
202 		}
203 	}
204 
205 	/**
206 	 * Gets the y position of the first non-air block
207 	 * from top.
208 	 * Returns: the y position of the block or -1
209 	 * Example:
210 	 * ---
211 	 * if(chunk.firstBlock(12, 9) == -1) {
212 	 *    d("The column is air-only");
213 	 * }
214 	 * ---
215 	 */
216 	public ptrdiff_t firstBlock(ubyte x, ubyte z) {
217 		foreach_reverse(size_t y ; 0..(this.highest_section*16)+16) {
218 			if(this[x, y, z] !is null) return y;
219 		}
220 		return -1;
221 	}
222 
223 	/**
224 	 * Checks whether or not a section is empty
225 	 * (has no blocks in it).
226 	 */
227 	public bool emptySection(size_t y) {
228 		auto ptr = y in this.n_sections;
229 		return ptr is null || (*ptr).empty;
230 	}
231 
232 	/**
233 	 * Checks if a section contains blocks with the random tick.
234 	 */
235 	public bool tickSection(size_t y) {
236 		return y in this.n_sections ? this.n_sections[y].tick : false;
237 	}
238 
239 	/**
240 	 * Gets a section.
241 	 */
242 	public Section sectionAt(size_t y) {
243 		return this.n_sections[y];
244 	}
245 
246 	/// ditto
247 	public Section opIndex(size_t y) {
248 		return this.sectionAt(y);
249 	}
250 
251 	public Section* opBinaryRight(string op)(size_t y) if(op == "in") {
252 		return y in this.n_sections;
253 	}
254 
255 	public Section* createSection(size_t y) {
256 		//this.n_sections[y] = new Section();
257 		this.n_sections[y] = ThreadMem.alloc!Section();
258 		if(y > this.highest_section) {
259 			this.highest_section = y;
260 		}
261 		return y in this.n_sections;
262 	}
263 
264 	public void removeSection(size_t y) {
265 		auto section = y in this.n_sections;
266 		if(y) {
267 			ThreadMem.free(*section);
268 			this.n_sections.remove(y);
269 			if(y == this.highest_section) {
270 				size_t[] keys = this.n_sections.keys;
271 				if(keys.length) {
272 					sort(keys);
273 					this.highest_section = keys[$-1];
274 				} else {
275 					this.highest_section = 0;
276 				}
277 			}
278 		}
279 	}
280 
281 	/**
282 	 * Gets the sections.
283 	 */
284 	public final pure nothrow @property @safe @nogc Section[size_t] sections() {
285 		return this.n_sections;
286 	}
287 
288 	/**
289 	 * Checks whether or not the chunks is air.
290 	 */
291 	public final pure nothrow @property @safe @nogc bool empty() {
292 		return this.n_sections.length == 0;
293 	}
294 
295 	/**
296 	 * Gets or generate the position for the next block
297 	 * where the snow should fall.
298 	 */
299 	public @property @safe Vector2!ubyte nextSnow() {
300 		if(this.next_snow.length == 0) {
301 			Vector2!ubyte[] positions;
302 			foreach(ubyte x ; 0..16) {
303 				foreach(ubyte z ; 0..16) {
304 					positions ~= Vector2!ubyte(x, z);
305 				}
306 			}
307 			randomShuffle(positions, this.world.random);
308 			this.next_snow = positions;
309 		}
310 		auto ret = this.next_snow[$-1];
311 		this.next_snow.length = this.next_snow.length - 1;
312 		return ret;
313 	}
314 
315 	/// Resets the snow
316 	public @safe void resetSnow() {
317 		this.next_snow.length = 0;
318 	}
319 
320 	public void save() {
321 		//TODO
322 	}
323 
324 	/// Unloads a chunks and frees its memory
325 	public void unload() {
326 		this.save();
327 		//this.sections.call!"unload"();
328 		foreach(Tile tile ; this.tiles) {
329 			tile.unplace();
330 		}
331 		// free memory
332 		foreach(section ; this.n_sections) {
333 			ThreadMem.free(section);
334 		}
335 	}
336 
337 }
338 
339 class UnsaveableChunk : Chunk {
340 
341 	public @safe this(World world, ChunkPosition position, string location=null) {
342 		super(world, position, location);
343 	}
344 
345 	public override @safe @nogc void save() {}
346 
347 }
348 
349 class Section {
350 
351 	public enum order = "yxz".dup;
352 
353 	private Block*[4096] n_blocks;
354 	private ubyte[2048] n_sky_light = 255;
355 	private ubyte[2048] n_blocks_light = 0;
356 
357 	private size_t n_amount = 0;
358 	private size_t n_random_ticked = 0;
359 
360 	public pure nothrow @property @safe @nogc ref auto blocks() {
361 		return this.n_blocks;
362 	}
363 	
364 	public @property auto blocks(Block*[4096] blocks) {
365 		this.n_blocks = blocks;
366 		foreach(ref Block* block ; this.n_blocks) {
367 			if(block) {
368 				if((*block).id == 0) {
369 					block = null;
370 				} else {
371 					this.n_amount++;
372 					if((*block).doRandomTick) this.n_random_ticked++;
373 				}
374 			}
375 		}
376 		return this.n_blocks;
377 	}
378 
379 	public pure nothrow @property @safe @nogc auto skyLight() {
380 		return this.n_sky_light;
381 	}
382 	
383 	public pure nothrow @property @safe auto skyLight(ubyte[2048] skyLight) {
384 		return this.n_sky_light = skyLight.dup;
385 	}
386 
387 	public pure nothrow @property @safe @nogc auto blocksLight() {
388 		return this.n_blocks_light;
389 	}
390 	
391 	public pure nothrow @property @safe auto blocksLight(ubyte[2048] blocksLight) {
392 		return this.n_blocks_light = blocksLight.dup;
393 	}
394 
395 	public pure nothrow @property @safe @nogc bool empty() {
396 		return this.n_amount == 0;
397 	}
398 
399 	public pure nothrow @property @safe @nogc bool full() {
400 		return this.n_amount == 4096;
401 	}
402 
403 	public pure nothrow @property @safe @nogc bool tick() {
404 		return this.n_random_ticked > 0;
405 	}
406 
407 	private Block** blockAt(ubyte x, ubyte y, ubyte z) {
408 		return &this.n_blocks[y << 8 | x << 4 | z];
409 	}
410 
411 	public Block* opIndex(ubyte x, ubyte y, ubyte z) {
412 		return *this.blockAt(x, y, z);
413 	}
414 
415 	public Block* opIndexAssign(Block* block, ubyte x, ubyte y, ubyte z) {
416 
417 		Block** ptr = this.blockAt(x, y, z);
418 
419 		if(block && (*block).id == 0) block = null;
420 		Block* old = *ptr;
421 
422 		bool old_air = old is null;
423 		bool new_air = block is null;
424 
425 		// update the number of blocks
426 		if(old_air ^ new_air) {
427 			if(old_air) this.n_amount++;
428 			else this.n_amount--;
429 		}
430 
431 		bool old_rt = !old_air && (*old).doRandomTick;
432 		bool new_rt = !new_air && (*block).doRandomTick;
433 
434 		// update the number of random-ticked blocks
435 		if(old_rt ^ new_rt) {
436 			if(old_rt) this.n_random_ticked--;
437 			else this.n_random_ticked++;
438 		}
439 
440 		return *ptr = block;
441 
442 	}
443 
444 }