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 }