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/block/block.d, selery/block/block.d) 28 */ 29 module selery.block.block; 30 31 import std.algorithm : canFind; 32 import std.conv : to; 33 import std.math : ceil; 34 import std.string : split, join, capitalize; 35 36 import selery.about : block_t, item_t, tick_t; 37 import selery.entity.entity : Entity; 38 import selery.event.event : EventListener; 39 import selery.event.world.world : WorldEvent; 40 import selery.item.item : Item; 41 import selery.item.slot : Slot; 42 import selery.math.vector : BlockAxis, BlockPosition, entityPosition; 43 import selery.player.player : Player; 44 import selery.world.chunk : Chunk; 45 import selery.world.world : World; 46 47 public import selery.math.vector : Faces = Face; 48 49 static import sul.blocks; 50 51 enum Update { 52 53 placed, 54 nearestChanged, 55 56 } 57 58 enum Remove { 59 60 broken, 61 creativeBroken, 62 exploded, 63 burnt, 64 enderDragon, 65 unset, 66 67 } 68 69 private enum double m = 1.0 / 16.0; 70 71 /** 72 * Base class for every block. 73 */ 74 class Block { 75 76 private const sul.blocks.Block _data; 77 78 private immutable bool has_bounding_box; 79 private BlockAxis bounding_box; 80 81 public this(sul.blocks.Block data) { 82 this._data = data; 83 if(data.boundingBox) { 84 this.has_bounding_box = true; 85 with(data.boundingBox) this.bounding_box = new BlockAxis(m * min.x, m * min.y, m * min.z, m * max.x, m * max.y, m * max.z); 86 //TODO calculate shapes 87 } else { 88 this.has_bounding_box = false; 89 } 90 } 91 92 /** 93 * Gets the block's sul data. 94 */ 95 public pure nothrow @property @safe @nogc const sul.blocks.Block data() { 96 return this._data; 97 } 98 99 /** 100 * Gets the block's SEL id. 101 */ 102 public pure nothrow @property @safe @nogc block_t id() { 103 return this.data.id; 104 } 105 106 /** 107 * Indicates whether the block exists in Minecraft. 108 */ 109 public pure nothrow @property @safe @nogc bool java() { 110 return this.data.java.exists; 111 } 112 113 public pure nothrow @property @safe @nogc ubyte javaId() { 114 return this.data.java.id; 115 } 116 117 public pure nothrow @property @safe @nogc ubyte javaMeta() { 118 return this.data.java.meta; 119 } 120 121 /** 122 * Indicates whether the block exists in Minecraft. 123 */ 124 public pure nothrow @property @safe @nogc bool bedrock() { 125 return this.data.bedrock.exists; 126 } 127 128 public pure nothrow @property @safe @nogc ubyte bedrockId() { 129 return this.data.bedrock.id; 130 } 131 132 public pure nothrow @property @safe @nogc ubyte bedrockMeta() { 133 return this.data.bedrock.meta; 134 } 135 136 /** 137 * Indicates whether a block is solid (can sustain another block or 138 * an entity) or not. 139 */ 140 public pure nothrow @property @safe @nogc bool solid() { 141 return this.data.solid; 142 } 143 144 /** 145 * Indicates whether the block is a fluid. 146 */ 147 public pure nothrow @property @safe @nogc bool fluid() { 148 return false; 149 } 150 151 /** 152 * Indicates the block's hardness, used to calculate the mining 153 * time of the block's material. 154 */ 155 public pure nothrow @property @safe @nogc double hardness() { 156 return this.data.hardness; 157 } 158 159 /** 160 * Indicates whether the block can be mined. 161 */ 162 public pure nothrow @property @safe @nogc bool indestructible() { 163 return this.hardness < 0; 164 } 165 166 /** 167 * Indicates whether the block can be mined or it's destroyed 168 * simply by a left-click. 169 */ 170 public pure nothrow @property @safe @nogc bool instantBreaking() { 171 return this.hardness == 0; 172 } 173 174 /** 175 * Gets the blast resistance, used for calculate 176 * the resistance at the explosion of solid blocks. 177 */ 178 public pure nothrow @property @safe @nogc double blastResistance() { 179 return this.data.blastResistance; 180 } 181 182 /** 183 * Gets the block's opacity, in a range from 0 to 15, where 0 means 184 * that the light propagates like in the air and 15 means that the 185 * light is totally blocked. 186 */ 187 public pure nothrow @property @safe @nogc ubyte opacity() { 188 return this.data.opacity; 189 } 190 191 /** 192 * Indicates the level of light emitted by the block in a range from 193 * 0 to 15. 194 */ 195 public pure nothrow @property @safe @nogc ubyte luminance() { 196 return this.data.luminance; 197 } 198 199 /** 200 * Boolean value indicating whether or not the block is replaced 201 * when touched with a placeable item. 202 */ 203 public pure nothrow @property @safe @nogc bool replaceable() { 204 return this.data.replaceable; 205 } 206 207 /** 208 * Boolean value indicating whether or not the block can be burnt. 209 */ 210 public pure nothrow @property @safe @nogc bool flammable() { 211 return this.encouragement > 0; 212 } 213 214 public pure nothrow @property @safe @nogc ubyte encouragement() { 215 return this.data.encouragement; 216 } 217 218 public pure nothrow @property @safe @nogc ubyte flammability() { 219 return this.data.flammability; 220 } 221 222 /** 223 * Modifies an entity's damage. The value should be higher than 0. 224 * Example: 225 * --- 226 * 0 = no damage 227 * .5 = half damage 228 * 1 = normal damage 229 * 2 = double damage 230 * --- 231 */ 232 public pure nothrow @property @safe @nogc float fallDamageModifier() { 233 return 1f; 234 } 235 236 /** 237 * Indicates whether the block has a bounding box which entities 238 * can collide with, even if the block is not solid. 239 */ 240 public pure nothrow @property @safe @nogc bool hasBoundingBox() { 241 return this.has_bounding_box; 242 } 243 244 /** 245 * If hasBoundingBox is true, returns the bounding box of the block 246 * as an Axis instance. 247 * Values are from 0 to 1 248 */ 249 public pure nothrow @property @safe @nogc BlockAxis box() { 250 return this.bounding_box; 251 } 252 253 public pure nothrow @property @safe @nogc bool fullUpperShape() { 254 return false; 255 } 256 257 public void onCollide(World world, Entity entity) {} 258 259 /** 260 * Get the dropped items as a slot array. 261 * Params: 262 * world = the world where the block has been broken 263 * player = the player who broke the block, can be null (e.g. explosion, fire...) 264 * item = item used to break the block, is null if player is null or the player broke the block with his hand 265 * Returns: a slot array with the dropped items 266 */ 267 public Slot[] drops(World world, Player player, Item item) { 268 return []; 269 } 270 271 /** 272 * Get the amount of dropped xp when the block is broken 273 * Params: 274 * world = the world where the block has been broken 275 * player = the player who broke the block, can be null (e.g. explosion, fire...) 276 * item = item used to break the block, is null if player is null or the player broke the block with his hand 277 * Returns: an integer, indicating the amount of xp that will be spawned 278 */ 279 public uint xp(World world, Player player, Item item) { 280 return 0; 281 } 282 283 public tick_t miningTime(Player player, Item item) { 284 return 0; 285 } 286 287 /** 288 * Function called when a player right-click the block. 289 * Blocks like tile should use this function for handle 290 * the interaction. 291 * N.B. That this function will no be called if the player shifts 292 * while performing the right-click/screen-tap. 293 * Params: 294 * player = the player who tapped the block 295 * item = the item used, is the same as player.inventory.held 296 * position = 297 * face = the face tapped 298 * Returns: false is a block should be placed, true otherwise 299 */ 300 public bool onInteract(Player player, Item item, BlockPosition position, ubyte face) { 301 return false; 302 } 303 304 /** 305 * Called when an entity is inside the block (or part of it). 306 */ 307 public void onEntityInside(Entity entity, BlockPosition position, bool headInside) {} 308 309 /** 310 * Called when an entity falls on walks on the block. 311 */ 312 public void onEntityStep(Entity entity, BlockPosition position, float fallDistance) {} 313 314 /** 315 * Called when an entity collides with the block's side (except top). 316 */ 317 public void onEntityCollide(Entity entity, BlockPosition position) {} 318 319 /** 320 * Boolean value indicating whether or not the block can receive a 321 * random tick. This property is only requested when the block is placed. 322 */ 323 public pure nothrow @property @safe @nogc bool doRandomTick() { 324 return false; 325 } 326 327 /** 328 * If the property doRandomTick is true, this function could be called 329 * undefined times duraing the chunk's random ticks. 330 */ 331 public void onRandomTick(World world, BlockPosition position) {} 332 333 /** 334 * Function called when the block is receives an update. 335 * Redstone mechanism should be handled from this function. 336 */ 337 public void onUpdated(World world, BlockPosition position, Update type) {} 338 339 public void onRemoved(World world, BlockPosition position, Remove type) {} 340 341 /** 342 * Function called by the world after a requets made 343 * by the block using World.scheduleBlockUpdate if 344 * the rule in the world is activated. 345 */ 346 public void onScheduledUpdate(World world, BlockPosition position) {} 347 348 /** 349 * Boolean value indicating whether or not the upper 350 * block is air or isn't solid. 351 * Params: 352 * world = the world there the block is placed 353 * position = position in the world where the block is placed 354 * checkFluid = boolean value indicating whether or not the fluid should be considered as a solid block 355 * Example: 356 * --- 357 * // farmlands become when dirt when they can't breathe 358 * world[0, 0, 0] = Blocks.FARMLAND; 359 * 360 * world[0, 1, 0] = Blocks.BEETROOT_BLOCK; 361 * assert(world[0, 0, 0] == Blocks.FARMLAND); 362 * 363 * world[0, 1, 0] = Blocks.DIRT; 364 * assert(world[0, 0, 0] != Blocks.FARMLAND); 365 * --- 366 */ 367 public final bool breathe(World world, BlockPosition position, bool checkFluid=true) { 368 Block up = world[position + [0, 1, 0]]; 369 return up.blastResistance == 0 && (!checkFluid || !up.fluid); 370 } 371 372 /** 373 * Compare the block names. 374 * Example: 375 * --- 376 * // one block 377 * assert(new Blocks.Dirt() == Blocks.dirt); 378 * 379 * // a group of blocks 380 * assert(new Blocks.Grass() == [Blocks.dirt, Blocks.grass, Blocks.grassPath]); 381 * --- 382 */ 383 public bool opEquals(block_t block) { 384 return this.id == block; 385 } 386 387 /// ditto 388 public bool opEquals(block_t[] blocks) { 389 return blocks.canFind(this.id); 390 } 391 392 /// ditto 393 public bool opEquals(Block[] blocks) { 394 foreach(block ; blocks) { 395 if(this.opEquals(block)) return true; 396 } 397 return false; 398 } 399 400 /// ditto 401 public bool opEquals(Block* block) { 402 if(block) return this.opEquals(*block); 403 else return this.id == 0; 404 } 405 406 public override bool opEquals(Object o) { 407 return cast(Block)o && this.opEquals((cast(Block)o).id); 408 } 409 410 } 411 412 public bool compareBlock(block_t[] blocks)(Block block) { 413 return compareBlock!blocks(block.id); 414 } 415 416 public bool compareBlock(block_t[] blocks)(block_t block) { 417 //TODO better compile time cmp 418 return blocks.canFind(block); 419 } 420 421 private bool compareBlockImpl(block_t[] blocks)(block_t block) { 422 static if(blocks.length == 1) return block == blocks[0]; 423 else static if(blocks.length == 2) return block == blocks[0] || block == blocks[1]; 424 else return block >= blocks[0] && block <= blocks[$-1]; 425 } 426 427 /** 428 * Placed block in a world, used when a position is needed 429 * but the block can be null. 430 */ 431 struct PlacedBlock { 432 433 private BlockPosition n_position; 434 private sul.blocks.Block n_block; 435 436 public @safe @nogc this(BlockPosition position, sul.blocks.Block block) { 437 this.n_position = position; 438 this.n_block = block; 439 } 440 441 public pure nothrow @property @safe @nogc BlockPosition position() { 442 return this.n_position; 443 } 444 445 public pure nothrow @property @safe @nogc sul.blocks.Block block() { 446 return this.n_block; 447 } 448 449 alias block this; 450 451 } 452 453 public @property @safe int blockInto(float value) { 454 if(value < 0) return (-value).ceil.to!int * -1; 455 else return value.to!int; 456 }