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 }