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/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 sel.data.enchantment;
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(sel.data.enchantment.Enchantment ench, ubyte level) {
643 this.addEnchantment(new Enchantment(ench, level));
644 }
645
646 /// ditto
647 public @safe void addEnchantment(sel.data.enchantment.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 sel.data.enchantment.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 sel.data.enchantment.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 }