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/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 }