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/item/item.d, selery/item/item.d)
28  */
29 module selery.item.item;
30 
31 import std.conv : to;
32 static import std.json;
33 import std.string : split, join;
34 
35 import sel.nbt.tags;
36 
37 import selery.about;
38 import selery.block.block : Block;
39 import selery.block.blocks : Blocks;
40 import selery.enchantment : Enchantments, Enchantment, EnchantmentException;
41 import selery.entity.entity : Entity;
42 import selery.item.slot : Slot;
43 import selery.item.tool : Tools;
44 import selery.math.vector : BlockPosition, face;
45 import selery.player.player : Player;
46 import selery.world.world : World;
47 
48 static import sul.enchantments;
49 static import sul.items;
50 
51 
52 /**
53  * Base class for an Item.
54  */
55 class Item {
56 
57 	protected Compound m_pc_tag;
58 	protected Compound m_pe_tag;
59 
60 	private string m_name = "";
61 	private string m_lore = "";
62 	private Enchantment[ubyte] enchantments;
63 	private bool m_unbreakable = false;
64 
65 	private block_t[] canPlaceOn, canDestroy; //TODO
66 
67 	public pure nothrow @safe @nogc this() {}
68 	
69 	/**
70 	 * Constructs an item with some extra data.
71 	 * Throws: JSONException if the JSON string is malformed
72 	 * Example:
73 	 * ---
74 	 * auto item = new Items.Apple(`{"customName":"SPECIAL APPLE","enchantments":[{"name":"protection","level":"IV"}]}`);
75 	 * assert(item.customName == "SPECIAL APPLE");
76 	 * assert(Enchantments.protection in item);
77 	 * ---
78 	 */
79 	public @trusted this(string data) {
80 		this(std.json.parseJSON(data));
81 	}
82 
83 	/**
84 	 * Constructs an item adding properties from a JSON.
85 	 * Throws: RangeError if the enchanting name doesn't exist
86 	 */
87 	public @safe this(std.json.JSONValue data) {
88 		this.parseJSON(data);
89 	}
90 
91 	public @trusted void parseJSON(std.json.JSONValue data) {
92 		if(data.type == std.json.JSON_TYPE.OBJECT) {
93 
94 			auto name = "customName" in data;
95 			if(name && name.type == std.json.JSON_TYPE.STRING) this.customName = name.str;
96 
97 			auto lore = "lore" in data;
98 			if(lore) {
99 				if(lore.type == std.json.JSON_TYPE.STRING) this.lore = lore.str;
100 				else if(lore.type == std.json.JSON_TYPE.ARRAY) {
101 					string[] l;
102 					foreach(value ; lore.array) {
103 						if(value.type == std.json.JSON_TYPE.STRING) l ~= value.str;
104 					}
105 					this.lore = l.join("\n");
106 				}
107 			}
108 
109 			void parseEnchantment(std.json.JSONValue ench) @trusted {
110 				if(ench.type == std.json.JSON_TYPE.ARRAY) {
111 					foreach(e ; ench.array) {
112 						if(e.type == std.json.JSON_TYPE.OBJECT) {
113 							ubyte l = 1;
114 							auto level = "level" in e;
115 							auto lvl = "lvl" in e;
116 							if(level && level.type == std.json.JSON_TYPE.INTEGER) {
117 								l = cast(ubyte)level.integer;
118 							} else if(lvl && lvl.type == std.json.JSON_TYPE.INTEGER) {
119 								l = cast(ubyte)lvl.integer;
120 							}
121 							auto name = "name" in e;
122 							auto java = "java" in e;
123 							auto bedrock = "bedrock" in e;
124 							try {
125 								if(name && name.type == std.json.JSON_TYPE.STRING) {
126 									this.addEnchantment(Enchantment.fromString(name.str, l));
127 								} else if(java && java.type == std.json.JSON_TYPE.INTEGER) {
128 									this.addEnchantment(Enchantment.fromJava(cast(ubyte)java.integer, l));
129 								} else if(bedrock && bedrock.type == std.json.JSON_TYPE.INTEGER) {
130 									this.addEnchantment(Enchantment.fromBedrock(cast(ubyte)bedrock.integer, l));
131 								}
132 							} catch(EnchantmentException) {}
133 						}
134 					}
135 				}
136 			}
137 			if("ench" in data) parseEnchantment(data["ench"]);
138 			else if("enchantments" in data) parseEnchantment(data["enchantments"]);
139 
140 			auto unb = "unbreakable" in data;
141 			if(unb && unb.type == std.json.JSON_TYPE.TRUE) this.unbreakable = true;
142 
143 		}
144 	}
145 
146 	/**
147 	 * Gets the item's data.
148 	 */
149 	public pure nothrow @property @safe @nogc const sul.items.Item data() {
150 		return sul.items.Item.init;
151 	}
152 
153 	/**
154 	 * Indicates wether the item exists in Minecraft.
155 	 */
156 	public pure nothrow @property @safe @nogc bool java() {
157 		return this.data.java.exists;
158 	}
159 
160 	/// ditto
161 	public pure nothrow @property @safe @nogc ushort javaId() {
162 		return this.data.java.id;
163 	}
164 
165 	/// ditto
166 	public pure nothrow @property @safe @nogc ushort javaMeta() {
167 		return this.data.java.meta;
168 	}
169 
170 	/**
171 	 * Indicates whether the item exists in Minecraft.
172 	 */
173 	public pure nothrow @property @safe @nogc bool bedrock() {
174 		return this.data.bedrock.exists;
175 	}
176 
177 	public pure nothrow @property @safe @nogc ushort bedrockId() {
178 		return this.data.bedrock.id;
179 	}
180 
181 	public pure nothrow @property @safe @nogc ushort bedrockMeta() {
182 		return this.data.bedrock.meta;
183 	}
184 
185 	/**
186 	 * Gets the name (not the custom name!) of the item.
187 	 * Example:
188 	 * ---
189 	 * if(item.name == "string")
190 	 *    item.customName = "Special String";
191 	 * ---
192 	 */
193 	public pure nothrow @property @safe @nogc string name() {
194 		return this.data.name;
195 	}
196 
197 	/** 
198 	 * Indicates the highest number of items that can be stacked in a slot.
199 	 * This number is the default slot's count if not specified when created.
200 	 * Returns: a number between 1 and 64 (usually 1, 16 or 64).
201 	 * Example:
202 	 * ---
203 	 * Slot slot = new Items.Beetroot();
204 	 * assert(slot.count == 64 && slot.item.max == 64);
205 	 * assert(slot.item.max == 64);
206 	 * 
207 	 * slot = new Slot(new Items.Beetroot(), 23);
208 	 * assert(slot.count != 64 && slot.count == 23);
209 	 * ---
210 	 */
211 	public pure nothrow @property @safe @nogc ubyte max() {
212 		return this.data.stack;
213 	}
214 
215 	/**
216 	 * Indicates whether the item is a tool.
217 	 * A tool can be used on blocks and entities
218 	 * and its meta will vary.
219 	 * Example:
220 	 * ---
221 	 * assert(new Items.Beetroot().tool == false);
222 	 * assert(new Items.DiamondSword().tool == true);
223 	 * ---
224 	 */
225 	public pure nothrow @property @safe @nogc bool tool() {
226 		return false;
227 	}
228 
229 	/**
230 	 * Gets the item's tool type.
231 	 * Returns: Tools.none if the item is not a tool or a number higher
232 	 * 			than 0 indicating the tool type.
233 	 * Example:
234 	 * ---
235 	 * assert(new Items.Beetroot().toolType == Tools.none);
236 	 * assert(new Items.DiamondSword().toolType == Tools.sword);
237 	 * ---
238 	 */
239 	public pure nothrow @property @safe @nogc ubyte toolType() {
240 		return Tools.none;
241 	}
242 
243 	/**
244 	 * Gets the tool's material if the item is a tool.
245 	 * Items with ID 0 have unspecified material, 1 is the minimum (wood)
246 	 * and 5 is the maximum (diamond).
247 	 * Example:
248 	 * ---
249 	 * assert(new Items.Beetroot().toolMaterial == NO_TOOL);
250 	 * assert(new Items.DiamondSword().toolMaterial == DIAMOND);
251 	 * ---
252 	 */
253 	public pure nothrow @property @safe @nogc ubyte toolMaterial() {
254 		return Tools.none;
255 	}
256 
257 	/**
258 	 * If the item is a tool, checks whether its damage is higher than
259 	 * its durability.
260 	 * Example:
261 	 * ---
262 	 * assert(new Items.Beetroot().finished == false); // beetroots aren't tools
263 	 * assert(new Items.DiamondSword().finished == false);
264 	 * assert(new Items.DiamondSword(Items.DiamondSword.DURABILITY + 1).finished == true);
265 	 * ---
266 	 */
267 	public pure nothrow @property @safe @nogc bool finished() {
268 		return false;
269 	}
270 
271 	/**
272 	 * Indicates the damage caused by the item when used as a weapon.
273 	 * The value indicates the base damage without the influence of
274 	 * enchantments or effects.
275 	 * Example:
276 	 * ---
277 	 * if(item.attack > 1)
278 	 *    assert(item.tool);
279 	 * ---
280 	 */
281 	public pure nothrow @property @safe @nogc uint attack() {
282 		return 1;
283 	}
284 
285 	/**
286 	 * Indicates whether or not an item can be eaten/drunk.
287 	 * If true, Item::onConsumed(Human consumer) will be called
288 	 * when this item is eaten/drunk.
289 	 * Example:
290 	 * ---
291 	 * if(item.consumeable) {
292 	 *    Item residue;
293 	 *    if((residue = item.onConsumed(player)) !is null) {
294 	 *       player.held = residue;
295 	 *    }
296 	 * }
297 	 * ---
298 	 */
299 	public pure nothrow @property @safe @nogc bool consumeable() {
300 		return false;
301 	}
302 
303 	/**
304 	 * Indicates whether the item can be consumed when the holder's
305 	 * hunger is full.
306 	 */
307 	public pure nothrow @property @safe @nogc bool alwaysConsumeable() {
308 		return true;
309 	}
310 
311 	/**
312 	 * If consumeable is true, this function is called.
313 	 * when the item is eaten/drunk by its holder, who's passed
314 	 * as the first arguments.
315 	 * Return:
316 	 * 		null: the item count will be reduced by 1
317 	 * 		item: the item will substitutes the consumed item
318 	 * Example:
319 	 * ---
320 	 * assert(new Items.Beetroot().onConsumed(player) is null);
321 	 * assert(new Items.BeetrootSoup().onConsumed(player) == Items.BOWL);
322 	 * ---
323 	 */
324 	public Item onConsumed(Player player) {
325 		return null;
326 	}
327 	
328 	/**
329 	 * Indicates whether or not the item can be placed.
330 	 * If this function returns true, Item::place(World world) will be probably
331 	 * called next for place a block
332 	 */
333 	public pure nothrow @property @safe @nogc bool placeable() {
334 		return false;
335 	}
336 
337 	/**
338 	 * Function called when the item is ready to be placed by
339 	 * a player (the event for the player has already been called).
340 	 * Returns: true if a block has been placed, false otherwise
341 	 */
342 	public bool onPlaced(Player player, BlockPosition tpos, uint tface) {
343 		BlockPosition position = tpos.face(tface);
344 		//TODO calling events on player and on block
345 		auto placed = this.place(player.world, position, tface);
346 		if(placed != 0) {
347 			player.world[position] = placed;
348 			return true;
349 		} else {
350 			return false;
351 		}
352 	}
353 	
354 	/**
355 	 * If Item::placeable returns true, this function
356 	 * should return an instance of the block that will
357 	 * be placed.
358 	 * Params:
359 	 * 		world = the world where the block has been placed
360 	 * 		position = where the item should place the block
361 	 * 		face = side of the block touched when placed
362 	 */
363 	public ushort place(World world, BlockPosition position, uint face) {
364 		return Blocks.air;
365 	}
366 
367 	/** 
368 	 * Function called when the item is used on a block
369 	 * clicking the right mouse button or performing a long pressure on the screen.
370 	 * Returns: true if the item is a tool and it has been cosnumed, false otherwise
371 	 * Example:
372 	 * ---
373 	 * // N.B. that this will not work as the block hasn't been placed
374 	 * world[0, 64, 0] = Blocks.DIRT;
375 	 * assert(world[0, 64, 0] == Blocks.DIRT);
376 	 * 
377 	 * new Items.WoodenShovel().useOnBlock(player, world[0, 64, 0], Faces.TOP);
378 	 * 
379 	 * assert(world[0, 64, 0] == Blocks.GRASS_PATH);
380 	 * ---
381 	 */
382 	public bool useOnBlock(Player player, Block block, BlockPosition position, ubyte face) {
383 		return false;
384 	}
385 
386 	/**
387 	 * Function called when the item is used to the destroy a block.
388 	 * Returns: true if the item is a tool and it has been consumed, false otherwise
389 	 * Example:
390 	 * ---
391 	 * auto dirt = new Blocks.Dirt();
392 	 * auto sword = new Items.DiamondSword(Items.DiamondSword.DURABILITY - 2);
393 	 * auto shovel = new Items.DiamondShovel(Items.DiamondShovel.DURABILITY - 2);
394 	 * 
395 	 * assert(sword.finished == false);
396 	 * assert(shovel.finished == false);
397 	 * 
398 	 * sword.destroyOn(player, dirt);	// 2 uses
399 	 * shovel.destroyOn(player, dirt);	// 1 use
400 	 * 
401 	 * assert(sword.finished == true);
402 	 * assert(shovel.finished == false);
403 	 * ---
404 	 */
405 	public bool destroyOn(Player player, Block block, BlockPosition position) {
406 		return false;
407 	}
408 
409 	/**
410 	 * Function called when the item is used on an entity as
411 	 * right click or long screen pressure.
412 	 * Returns: true if the items is a tool and it has been consumed, false otherwise
413 	 */
414 	public bool useOnEntity(Player player, Entity entity) {
415 		return false;
416 	}
417 
418 	/**
419 	 * Function called when the item is used against an
420 	 * entity as a left click or screen tap.
421 	 * Returns: true if the items is a tool and it has been consumed, false otherwise
422 	 */
423 	public bool attackOnEntity(Player player, Entity entity) {
424 		return false;
425 	}
426 
427 	/**
428 	 * Function called when the item is throwed or aimed.
429 	 * Returns: true if the item count should be reduced by 1, false otherwise
430 	 */
431 	public bool onThrowed(Player player) {
432 		return false;
433 	}
434 
435 	/**
436 	 * Function called when the item is released, usually after
437 	 * it has been throwed (which is used as aim-start function).
438 	 * Returns: true if the item has been consumed, false otherwise
439 	 */
440 	public bool onReleased(Player holder) {
441 		return false;
442 	}
443 
444 	/**
445 	 * Gets the item's compound tag with the custom data of the item.
446 	 * It may be null if the item has no custom behaviours.
447 	 * Example:
448 	 * ---
449 	 * if(item.minecraftCompound is null) {
450 	 *    assert(item.customName == "");
451 	 * }
452 	 * item.customName = "not empty";
453 	 * assert(item.pocketCompound !is null);
454 	 * ---
455 	 */
456 	public final pure nothrow @property @safe @nogc Compound javaCompound() {
457 		return this.m_pc_tag;
458 	}
459 
460 	/// ditto
461 	public final pure nothrow @property @safe @nogc Compound pocketCompound() {
462 		return this.m_pe_tag;
463 	}
464 
465 	/**
466 	 * Parses a compound, usually received from the client or
467 	 * saved in a world.
468 	 * The tag should never be null as the method doesn't check it.
469 	 * Example:
470 	 * ---
471 	 * item.parseMinecraftCompound(new Compound(new Compound("display", new String("Name", "custom"))));
472 	 * assert(item.customName == "custom");
473 	 * ---
474 	 */
475 	public @safe void parseJavaCompound(Compound compound) {
476 		this.clear();
477 		this.parseCompound(compound, &Enchantment.fromJava);
478 	}
479 
480 	/// ditto
481 	public @safe void parseBedrockCompound(Compound compound) {
482 		this.clear();
483 		this.parseCompound(compound, &Enchantment.fromBedrock);
484 	}
485 
486 	private @trusted void parseCompound(Compound compound, Enchantment function(ubyte, ubyte) @safe get) {
487 		compound = compound.get!Compound("", compound); //TODO is this still required?
488 		auto display = compound.get!Compound("display", null);
489 		if(display !is null) {
490 			immutable name = display.getValue!String("Name", "");
491 			if(name.length) this.customName = name;
492 			//TODO lore
493 		}
494 		auto ench = compound.get!(ListOf!Compound)("ench", null);
495 		if(ench !is null) {
496 			foreach(e ; ench) {
497 				auto getted = get(cast(ubyte)e.getValue!Short("id", short.init), cast(ubyte)e.getValue!Short("lvl", short.init));
498 				if(getted !is null) this.addEnchantment(getted);
499 			}
500 		}
501 		if(compound.getValue!Byte("Unbreakable", 0) != 0) {
502 			this.unbreakable = true;
503 		}
504 	}
505 
506 	/**
507 	 * Removes the custom behaviours of the item, like custom name
508 	 * and enchantments.
509 	 * Example:
510 	 * ---
511 	 * item.customName = "name";
512 	 * assert(item.customName == "name");
513 	 * item.clear();
514 	 * assert(item.customName == "");
515 	 * ---
516 	 */
517 	public @trusted void clear() {
518 		this.m_pc_tag = null;
519 		this.m_pe_tag = null;
520 		this.m_name = "";
521 		this.m_lore = "";
522 		this.enchantments.clear();
523 	}
524 
525 	/**
526 	 * Gets the item's custom name.
527 	 */
528 	public pure nothrow @property @safe @nogc string customName() {
529 		return this.m_name;
530 	}
531 
532 	/**
533 	 * Sets the item's custom name.
534 	 * Example:
535 	 * ---
536 	 * item.customName = "§aColoured!";
537 	 * item.customName = ""; // remove
538 	 * ---
539 	 */
540 	public @property @safe string customName(string name) {
541 		if(name.length) {
542 			void set(ref Compound compound) {
543 				auto n = new Named!String("Name", name);
544 				if(compound is null) compound = new Compound(new Named!Compound("display", n));
545 				else if(!compound.has!Compound("display")) compound["display"] = new Compound(n);
546 				else compound.get!Compound("display", null)[] = n;
547 			}
548 			set(this.m_pc_tag);
549 			set(this.m_pe_tag);
550 		} else {
551 			void reset(ref Compound compound) {
552 				auto display = cast(Compound)compound["display"];
553 				display.remove("Name");
554 				if(display.empty) {
555 					compound.remove("display");
556 					if(compound.empty) compound = null;
557 				}
558 			}
559 			reset(this.m_pc_tag);
560 			reset(this.m_pe_tag);
561 		}
562 		return this.m_name = name;
563 	}
564 
565 	/**
566 	 * Gets the item's lore or description which is displayed under
567 	 * the item's name when the item is hovered in the player's inventory.
568 	 */
569 	public pure nothrow @property @safe @nogc string lore() {
570 		return this.m_lore;
571 	}
572 
573 	public @property @safe string lore(string[] lore) {
574 		if(lore.length) {
575 			void set(ref Compound compound) {
576 				auto n = new Named!(ListOf!String)("Lore", lore);
577 				if(compound is null) compound = new Compound(new Named!Compound("display", n));
578 				else if(!compound.has!Compound("display")) compound["display"] = new Compound(n);
579 				else compound.get!Compound("display", null)[] = n;
580 			}
581 			set(this.m_pc_tag);
582 		} else {
583 			void reset(ref Compound compound) {
584 				auto display = cast(Compound)compound["display"];
585 				display.remove("Lore");
586 				if(display.empty) {
587 					compound.remove("display");
588 					if(compound.empty) compound = null;
589 				}
590 			}
591 			reset(this.m_pc_tag);
592 		}
593 		return this.m_lore = lore.join("\n");
594 	}
595 
596 	public @property @safe string lore(string lore) {
597 		return this.lore = lore.split("\n");
598 	}
599 
600 	/**
601 	 * Adds an enchantment to the item.
602 	 * Throws: EnchantmentException if the enchantment doesn't exist
603 	 * Example:
604 	 * ---
605 	 * item.addEnchantment(new Enchantment(Enchantments.sharpness, 1));
606 	 * item.addEnchantment(Enchantments.power, 5);
607 	 * item.addEnchantment(Enchantments.fortune, "X");
608 	 * item += new Enchantment(Enchantments.smite, 2);
609 	 * ---
610 	 */
611 	public @safe void addEnchantment(Enchantment ench) {
612 		if(ench is null) throw new EnchantmentException("Invalid enchantment given");
613 		auto e = ench.id in this.enchantments;
614 		if(e) {
615 			// modify
616 			*e = ench;
617 			void modify(ref Compound compound, ubyte id) @safe {
618 				foreach(ref tag ; compound.get!(ListOf!Compound)("ench", null)) {
619 					if(tag.getValue!Short("id", -1) == id) {
620 						tag.get!Short("lvl", null).value = ench.level;
621 						break;
622 					}
623 				}
624 			}
625 			if(ench.java) modify(this.m_pc_tag, ench.java.id);
626 			if(ench.bedrock) modify(this.m_pe_tag, ench.bedrock.id);
627 		} else {
628 			// add
629 			this.enchantments[ench.id] = ench;
630 			void add(ref Compound compound, ubyte id) @safe {
631 				auto ec = new Compound([new Named!Short("id", id), new Named!Short("lvl", ench.level)]);
632 				if(compound is null) compound = new Compound([new Named!(ListOf!Compound)("ench", [ec])]);
633 				else if(!compound.has!(ListOf!Compound)("ench")) compound["ench"] = new ListOf!Compound(ec);
634 				else compound.get!(ListOf!Compound)("ench", null) ~= ec;
635 			}
636 			if(ench.java) add(this.m_pc_tag, ench.java.id);
637 			if(ench.bedrock) add(this.m_pe_tag, ench.bedrock.id);
638 		}
639 	}
640 
641 	/// ditto
642 	public @safe void addEnchantment(sul.enchantments.Enchantment ench, ubyte level) {
643 		this.addEnchantment(new Enchantment(ench, level));
644 	}
645 
646 	/// ditto
647 	public @safe void addEnchantment(sul.enchantments.Enchantment ench, string level) {
648 		this.addEnchantment(new Enchantment(ench, level));
649 	}
650 
651 	/// ditto
652 	public @safe void opBinaryRight(string op : "+")(Enchantment ench) {
653 		this.addEnchantment(ench);
654 	}
655 
656 	/// ditto
657 	alias enchant = this.addEnchantment;
658 
659 	/**
660 	 * Gets a pointer to the enchantment.
661 	 * This method can be used to check if the item has an
662 	 * enchantment and its level.
663 	 * Example:
664 	 * ---
665 	 * auto e = Enchantments.protection in item;
666 	 * if(!e || e.level != 5) {
667 	 *    item.enchant(Enchantment.protection, 5);
668 	 * }
669 	 * assert(Enchantments.protection in item);
670 	 * ---
671 	 */
672 	public @safe Enchantment* opBinaryRight(string op : "in")(inout sul.enchantments.Enchantment ench) {
673 		return ench.java.id in this.enchantments;
674 	}
675 
676 	/**
677 	 * Removes an enchantment from the item.
678 	 * Example:
679 	 * ---
680 	 * item.removeEnchantment(Enchantments.sharpness);
681 	 * item -= Enchantments.fortune;
682 	 * ---
683 	 */
684 	public @safe void removeEnchantment(inout sul.enchantments.Enchantment ench) {
685 		if(ench.java.id in this.enchantments) {
686 			this.enchantments.remove(ench.java.id);
687 			void remove(ref Compound compound, ubyte id) @safe {
688 				auto list = compound.get!(ListOf!Compound)("ench", null);
689 				if(list.length == 1) {
690 					compound.remove("ench");
691 					if(compound.empty) compound = null;
692 				} else {
693 					foreach(i, e; list) {
694 						if(e.getValue!Short("id", short(-1)) == id) {
695 							list.remove(i);
696 							break;
697 						}
698 					}
699 				}
700 			}
701 			if(ench.java) remove(this.m_pc_tag, ench.java.id);
702 			if(ench.bedrock) remove(this.m_pe_tag, ench.bedrock.id);
703 		}
704 	}
705 
706 	/// ditto
707 	public @safe void opBinaryRight(string op : "-")(inout sul.enchantments.Enchantment ench) {
708 		this.removeEnchantment(ench);
709 	}
710 
711 	/**
712 	 * If the item is a tool, indicates whether the item is consumed
713 	 * when used for breaking or combat.
714 	 */
715 	public pure nothrow @property @safe @nogc bool unbreakable() {
716 		return this.m_unbreakable;
717 	}
718 
719 	public @property @safe bool unbreakable(bool unbreakable) {
720 		if(unbreakable) {
721 			auto u = new Named!Byte("Unbreakable", true);
722 			if(this.m_pc_tag is null) this.m_pc_tag = new Compound(u);
723 			else this.m_pc_tag[] = u;
724 		} else {
725 			this.m_pc_tag.remove("Unbreakable");
726 			if(this.m_pc_tag.empty) this.m_pc_tag = null;
727 		}
728 		return this.m_unbreakable = unbreakable;
729 	}
730 
731 	/**
732 	 * Deep comparation of 2 instantiated items.
733 	 * Compare ids, metas, custom names and enchantments.
734 	 * Example:
735 	 * ---
736 	 * Item a = new Items.Beetroot();
737 	 * Item b = a.dup;
738 	 * assert(a == b);
739 	 * 
740 	 * a.customName = "beetroot";
741 	 * assert(a != b);
742 	 * 
743 	 * b.customName = "beetroot";
744 	 * a.enchant(Enchantments.protection, "IV");
745 	 * b.enchant(Enchantments.protection, "IV");
746 	 * assert(a == b);
747 	 * ---
748 	 */
749 	public override bool opEquals(Object o) {
750 		if(cast(Item)o) {
751 			Item i = cast(Item)o;
752 			return this.javaId == i.javaId &&
753 				this.bedrockId == i.bedrockId &&
754 				this.javaMeta == i.javaMeta &&
755 				this.bedrockMeta == i.bedrockMeta &&
756 				this.customName == i.customName &&
757 				this.lore == i.lore &&
758 				this.enchantments == i.enchantments;
759 		}
760 		return false;
761 	}
762 
763 	/**
764 	 * Compare an item with its type as a string or a group of strings.
765 	 * Example:
766 	 * ---
767 	 * Item item = new Items.Beetroot();
768 	 * assert(item == Items.beetroot);
769 	 * assert(item == [Items.beetrootSoup, Items.beetroot]);
770 	 * ---
771 	 */
772 	public @safe @nogc bool opEquals(item_t item) {
773 		return item == this.data.index;
774 	}
775 
776 	/// ditto
777 	public @safe @nogc bool opEquals(item_t[] items) {
778 		foreach(item ; items) {
779 			if(this.opEquals(item)) return true;
780 		}
781 		return false;
782 	}
783 
784 	/**
785 	 * Returns the item as string in format "name" or "name:damage" for tools.
786 	 */
787 	public override string toString() {
788 		//TODO override in tools to print damage
789 		return this.name ~ "(" ~ this.customName ~ ", " ~ to!string(this.enchantments.values) ~ ")";
790 	}
791 
792 	/**
793 	 * Create a slot with the Item::max as count
794 	 * Example:
795 	 * ---
796 	 * Slot a = new Slot(new Items.Beetroot(), 12);
797 	 * Slot b = new Items.Beetroot(); // same as new Items.Beetroot().slot;
798 	 * 
799 	 * assert(a.count == 12);
800 	 * assert(b.count == 64);
801 	 * ---
802 	 */
803 	public final @property @safe Slot slot() {
804 		return Slot(this);
805 	}
806 
807 	alias slot this;
808 
809 }
810 
811 //TODO the translatable should affect the compound tag
812 /*template Translatable(T:Item) {
813 	alias Translatable = GenericTranslatable!("this.customName", T);
814 }*/
815 
816 class SimpleItem(sul.items.Item _data) : Item {
817 
818 	public @safe this(E...)(E args) {
819 		super(args);
820 	}
821 
822 	public override pure nothrow @property @safe @nogc const sul.items.Item data() {
823 		return _data;
824 	}
825 	
826 	alias slot this;
827 
828 }