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 }