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/inventory/inventory.d, selery/inventory/inventory.d) 28 */ 29 module selery.inventory.inventory; 30 31 import std.conv : to; 32 import std.exception : enforce; 33 import std.typecons : Tuple; 34 35 import selery.about : item_t; 36 import selery.entity.human : Human; 37 import selery.item.item : Item; 38 import selery.item.slot : Slot; 39 import selery.item.tool : Armor; 40 41 private alias Slice = Tuple!(size_t, "min", size_t, "max"); 42 43 /** 44 * Exception thrown by the inventory when a range error happens, 45 * instead of the classic RangeError that gives a bad description 46 * of the problem. 47 */ 48 class InventoryRangeError : Error { 49 50 public pure nothrow @safe this(string message, string file=__FILE__, size_t line=__LINE__) { 51 super(message, file, line, null); 52 } 53 54 public pure nothrow @safe this(size_t index, Inventory inventory, string file=__FILE__, size_t line=__LINE__) { 55 this("Index " ~ to!string(index) ~ " exceeds the inventory range of 0.." ~ to!string(inventory.length), file, line); 56 } 57 58 } 59 60 /+struct InventorySlot { 61 62 private Slot n_slot; 63 64 public this(Slot slot) { 65 this.n_slot = slot; 66 } 67 68 public this(Item item) { 69 this(Slot(item)); 70 } 71 72 public this(Item item, ubyte count) { 73 this(Slot(item, count)); 74 } 75 76 public @property ubyte count() { 77 return this.n_slot.count; 78 } 79 80 public @property ubyte count(ubyte count) { 81 return count; 82 } 83 84 public void fill() { 85 //TODO 86 } 87 88 public pure nothrow @property @safe @nogc Slot slot() { 89 return this.n_slot; 90 } 91 92 alias slot this; 93 94 }+/ 95 96 /** 97 * Basic inventory class with adding/removing/assigning/filling functions. 98 * Example: 99 * --- 100 * auto inventory = new Inventory(10); 101 * 102 * // assign 103 * inventory[4] = Slot(new Items.Apple(), 12); 104 * inventory[5] = new Items.Apple(); 105 * 106 * // automatically add in the first empty slot 107 * inventory.add(new Items.Beetroot()); 108 * 109 * // fill 110 * inventory = new Items.Beetroot(); 111 * 112 * // an inventory can also be iterated 113 * foreach(ref Slot slot ; inventory) { 114 * d(slot); 115 * } 116 * --- 117 */ 118 class Inventory { 119 120 private Slot[] n_slots; 121 122 /** 123 * Creates an inventory with the given number of slots. 124 * The number of slots must be higher than 0 and shorter than 2^16. 125 * Params: 126 * size = the size of the inventory 127 */ 128 public @safe this(size_t size) { 129 this.n_slots.length = size; 130 } 131 132 /** 133 * Constructs an inventory giving an array of slots. 134 * The length of the inventory will be the same as the given array. 135 * Params: 136 * slots = one or more slots 137 * Example: 138 * --- 139 * auto a = new Inventory(Slot(null)); 140 * auto b = new Inventory(Slot(null), Slot(null)); 141 * auto c = new Inventory([Slot(null), Slot(null)]); 142 * --- 143 */ 144 public @safe this(Slot[] slots ...) { 145 this.n_slots = slots; 146 } 147 148 /// ditto 149 public @safe this(Inventory inventory) { 150 this(cast(Slot[])inventory); 151 } 152 153 /** 154 * Gets every slots of the inventory (0..$). 155 * This property should only be used when the full inventory is needed, 156 * otherwise opIndex should be used for getting a slot in a specific index 157 * or in a specific range. 158 * Returns: an array with every slot in the inventory 159 * Example: 160 * --- 161 * auto inventory = new Inventory(2); 162 * assert(inventory[] == [Slot(null), Slot(null)]); 163 * assert(cast(Slot[])inventory == inventory[]); 164 * --- 165 */ 166 public @safe Slot[] opIndex() { 167 return this.n_slots[0..$]; 168 } 169 170 /// ditto 171 public @safe T opCast(T)() if(is(T == Slot[])) { 172 return this.opIndex(); 173 } 174 175 /** 176 * Gets the slot at the given index. 177 * Params: 178 * index = a number in range 0..$ 179 * Returns: the slot at the given index 180 * Throws: RangeError if an invalid index is given 181 * Example: 182 * --- 183 * if(!inventory[1].empty && inventory[1].item == Items.APPLE) { ... } 184 * --- 185 */ 186 public @safe Slot opIndex(size_t index) { 187 //if(index >= this.length) throw new InventoryRangeError(index, this); 188 return this.n_slots[index]; 189 } 190 191 /** 192 * Gets the slots in a specific range. 193 * Params: 194 * slice = the slice obtained with opSlice, e.g. inventory[0..10] 195 * Returns: an <a href="#InventoryRange">InventoryRange</a> with the slots at the given indexes 196 * Throws: RangeError if an invalid range or index is given 197 * Example: 198 * --- 199 * auto inventory = new Inventory(4); 200 * inventory[2] = Slot(new Items.Apple(), 1); 201 * assert(inventory[2..$] == [Slot(new Items.Apple(), 1), Slot(null)]); 202 * --- 203 */ 204 public @safe InventoryRange opIndex(Slice slice) { 205 //if(slice.max > this.length) throw new InventoryRangeError(slice.max, this); 206 return new InventoryRange(this, slice.min, slice.max); 207 } 208 209 /** 210 * Sets the slot at the given index. 211 * Params: 212 * slot = the item to set 213 * index = a number in range 0..$ 214 * Throws: RangeError if an invalid index is given 215 * Example: 216 * --- 217 * if(inventory[1].empty || inventory[1].item != Items.BEETROOT) { 218 * inventory[1] = Slot(new Items.Beetroot()); 219 * } 220 * --- 221 */ 222 public @safe void opIndexAssign(Slot slot, size_t index) { 223 //if(index >= this.length) throw new InventoryRangeError(index, this); 224 // TODO create a duplicate for the item (mantaining the class type) 225 this.n_slots[index] = slot.empty ? slot : Slot(slot.item/*.exactDuplicate*/, slot.count); 226 } 227 228 /** 229 * Sets the slots in the given range. 230 * Params: 231 * slot = the slot to be set 232 * slice = the range to set 233 * Throws: RangeError is an invalid range or index is given 234 * Example: 235 * --- 236 * inventory[2..4] = Slot(new Items.Apple(), 32); 237 * inventory[4..6] = another[10..12]; 238 * --- 239 */ 240 public @safe void opIndexAssign(Slot slot, Slice slice) { 241 foreach(size_t index ; slice.min..slice.max) { 242 this[index] = slot; 243 } 244 } 245 246 /// ditto 247 public @safe void opIndexAssign(Slot[] slots, Slice slice) { 248 foreach(size_t index ; slice) { 249 this[index] = slots[index - slice.min]; 250 } 251 } 252 253 // returns a slice with the given indexes 254 public @safe Slice opSlice(size_t pos)(size_t min, size_t max) { 255 return Slice(min, max); 256 } 257 258 /// Gets the size of the inventory. 259 public pure nothrow @property @safe @nogc size_t length() { 260 return this.n_slots.length; 261 } 262 263 /// ditto 264 public pure nothrow @safe @nogc size_t opDollar(size_t pos)() { 265 return this.length; 266 } 267 268 /** 269 * Concatenates two inventories (or an inventory and an array of slots) 270 * and returns a new one. 271 * Example: 272 * --- 273 * auto inventory = new Inventory(4); 274 * auto ni = inventory ~ [Slot(null), Slot(null)] ~ inventory; 275 * assert(inventory.length == 4); 276 * assert(ni.length == 10); 277 * --- 278 */ 279 public @safe Inventory opBinary(string op)(Slot[] slots) if(op == "~") { 280 return new Inventory(this[] ~ slots); 281 } 282 283 /// ditto 284 public @safe Inventory opBinary(string op)(Inventory inventory) if(op == "~") { 285 return this.opBinary!op(inventory[]); 286 } 287 288 /// ditto 289 public @safe Inventory opBinary(string op)(Slot slot) if(op == "~") { 290 return this.opBinary!op([slot]); 291 } 292 293 /// ditto 294 public @safe Inventory opBinaryRight(string op)(Slot[] slots) if(op == "~") { 295 return this.opBinary!op(slots); 296 } 297 298 /// ditto 299 public @safe Inventory opBinaryRight(string op)(Slot slot) if(op == "~") { 300 return this.opBinaryRight!op([slot]); 301 } 302 303 /** 304 * Adds slot(s) to the inventory (if there's enough space). 305 * Note that this function will only mutate the the inventory's 306 * slots' content without mutating its length. 307 * Params: 308 * slot = slot(s) that will be added to the inventory 309 * Returns: the slot(s) that couldn't be added to the inventory 310 * Example: 311 * --- 312 * auto inventory = new Inventory(10); 313 * inventory += Slot(new Items.Apple(), 32); 314 * assert(inventory[0].item == Items.APPLE); 315 * --- 316 */ 317 public @trusted Slot opOpAssign(string op)(Slot slot) if(op == "+") { 318 if(slot.empty) return slot; 319 // try to add on deep equals items 320 // try to add on free space 321 size_t[] empties; 322 foreach(size_t index, Slot islot; this[]) { 323 if(islot.empty) { 324 empties ~= index; 325 } else if(islot.item == slot.item && !islot.full) { 326 uint count = islot.count + slot.count; 327 ubyte max = (count < slot.item.max ? count : slot.item.max) & 255; 328 this[index] = Slot(slot.item, max); 329 if(count > max) { 330 slot.count = (count - max) & 255; 331 } else { 332 return Slot(null); 333 } 334 } 335 } 336 foreach(size_t index ; empties) { 337 ubyte count = slot.count <= slot.item.max ? slot.count : slot.item.max; 338 this[index] = Slot(slot.item, count); 339 if(count < slot.count) { 340 slot.count = (slot.count - count) & 255; 341 } else { 342 return Slot(null); 343 } 344 } 345 return slot; 346 } 347 348 /// ditto 349 public @safe Slot[] opOpAssign(string op)(Slot[] slots) if(op == "+") { 350 Slot[] ret; 351 foreach(Slot slot ; slots) { 352 Slot res = this.opOpAssign!op(slot); 353 if(!res.empty) { 354 ret ~= res; 355 } 356 } 357 return ret; 358 } 359 360 /** 361 * Removes slot(s) from the inventory. 362 * Parameter types: 363 * string = tries to remove items with the same name 364 * string[] = tries to remove items with one of the names in the array 365 * Item = tries to remove items with the same name and properties 366 * Slot = tries to remove items with the same name, properties and count 367 * Returns: the number of slots that has been set to empty 368 * Example: 369 * --- 370 * inventory -= Items.FOOD; // remove food 371 * inventory -= new Items.Apple(); // remove apples 372 * --- 373 */ 374 public @trusted uint opOpAssign(string op, T)(T item) if(op == "-" && (is(T == Slot) || is(T : Item) || is(T == string) || is(T == string[]) || is(T == immutable(string)[]))) { 375 static if(is(T == Slot)) { 376 assert(!item.empty, "Slot can't be empty"); 377 immutable operation = "slot == item"; 378 } else { 379 assert(item !is null, "Item can't be null"); 380 immutable operation = "slot.item == item"; 381 } 382 uint removed = 0; 383 foreach(size_t index, Slot slot; this[]) { 384 if(!slot.empty) { 385 if(mixin(operation)) { 386 this[index] = Slot(null); 387 removed++; 388 } 389 } 390 } 391 return removed; 392 } 393 394 /// ditto 395 public @safe uint opOpAssign(string op, T)(T[] items) if(op == "-" && (is(T == Slot) || is(T == Item))) { 396 uint removed = 0; 397 foreach(T item ;items) { 398 removed += this.opOpAssign!op(item); 399 } 400 return removed; 401 } 402 403 /** 404 * Matches the first occurence and returns the pointer to the slot. 405 * Paramenter types: 406 * string = checks for an item with the same name 407 * string[] = checks for an item with one of the names in the array 408 * Item = checks for an item with the same name and properties (custom name, enchantments, ...) 409 * Slot = checks for the item (see above) and the count of the item 410 * Returns: a pointer to first occurence found, or null if no occurences were found 411 * Standards: 412 * Use "Slot(null) in inventory" to check for an empty slot. 413 * Example: 414 * --- 415 * // check for an empty slot 416 * if(Slot(null) in inventory) { 417 * d("there's space!"); 418 * } 419 * --- 420 */ 421 public @trusted Slot* opBinary(string op, T)(T item) if(op == "in" && (is(T == Slot) || is(T : Item) || is(T == string) || is(T == string[]) || is(T == immutable(string)[]))) { 422 static if(is(T == Slot)) { 423 immutable operation = "slot == item"; 424 } else { 425 immutable operation = "!slot.empty && slot.item == item"; 426 } 427 foreach(Slot slot ; this[]) { 428 if(mixin(operation)) return &slot; 429 } 430 return null; 431 } 432 433 /** 434 * Groups an item type into the given slot, if possible. 435 * Params: 436 * index = the index of the slot where the items should be grouped 437 * Example: 438 * --- 439 * auto inventory = new Inventory(10); 440 * inventory[0..3] = Slot(new Items.Cookie(), 30); 441 * inventory.group(0); 442 * assert(inventory[0].count == 64); 443 * assert(inventory[1].count == 0); 444 * assert(inventory[2].count == 26); 445 * --- 446 */ 447 public @trusted void group(size_t index) { 448 Slot target = this[index]; 449 if(!target.empty && !target.full) { 450 Item item = target.item; 451 uint count = target.count; 452 foreach(size_t i, Slot slot; this[]) { 453 if(i != index && !slot.empty && slot.item == item) { 454 uint c = count + slot.count; 455 if(c >= item.max) { 456 count = item.max; 457 this[i] = Slot(item, (c - item.max) & ubyte.max); 458 break; 459 } else { 460 count = c; 461 this[i] = Slot(null); 462 } 463 } 464 } 465 if(count != target.count) this[index] = Slot(item, count & ubyte.max); 466 } 467 } 468 469 /** 470 * Performs a basic math operation on every slot's count in 471 * the inventory, if not empty. 472 * Example: 473 * --- 474 * auto inventory = new Inventory(new Items.Apple(), Slot(new Items.Cookie(), 12)); 475 * inventory += 40; 476 * assert(inventory == [Slot(new Items.Apple(), 64), Slot(new Items.Cookie(), 52)]); 477 * inventory -= 52; 478 * assert(inventory == [Slot(new Items.Apple(), 12), Slot(null)]); 479 * inventory *= 4; 480 * assert(inventory == [Slot(new Items.Apple(), 48), Slot(null)]); 481 * inventory /= 24; 482 * assert(inventory == [Slot(new Items.Apple(), 2), Slot(null)]); 483 * --- 484 */ 485 public @safe void opOpAssign(string op, T)(T number) if(is(T : int) && (op == "+" || op == "-" || op == "*" || op == "/")) { 486 foreach(size_t index, Slot slot; this[]) { 487 if(!slot.empty) { 488 mixin("int count = slot.count " ~ op ~ " number;"); 489 this[index] = Slot(slot.item, count <= 0 ? 0 : (count > slot.item.max ? slot.item.max : (count & ubyte.max))); 490 } 491 } 492 } 493 494 /** 495 * Checks whether or not the inventory is empty. 496 * Returns: true if the inventory is empty, false otherwise 497 * Example: 498 * --- 499 * if(inventory.empty) { 500 * inventory[5] = new Items.Beetroot(); 501 * assert(!inventory.empty); 502 * } 503 * --- 504 */ 505 public @property @safe bool empty() { 506 foreach(Slot slot ; this[]) { 507 if(!slot.empty) return false; 508 } 509 return true; 510 } 511 512 /** 513 * Removes every item from inventory if empty is true. 514 * Example: 515 * --- 516 * if(!inventory.empty) { 517 * inventory.empty = true; 518 * } 519 * --- 520 */ 521 public @property @safe bool empty(bool empty) { 522 if(!empty) { 523 return false; 524 } else { 525 this[0..$] = Slot(null); 526 return true; 527 } 528 } 529 530 /** 531 * Compares the inventory with an array of slots. 532 * Returns: true if the length and the item at every index is equals to the array's ones 533 * Example: 534 * --- 535 * auto inventory = new Inventory(Slot(null), Slot(new Items.Apple())); 536 * assert(inventory == [Slot(null), Slot(new Items.Apple())]); 537 * assert(inventory != [Slot(null), Slot(new Items.Apple(), 12)]); 538 * assert(inventory != [Slot(null), Slot(new Items.Apple("{\"customName\":\"test\"}"))]); 539 * --- 540 */ 541 public bool opEquals(Slot[] slots) { 542 if(this.length != slots.length) return false; 543 foreach(size_t i ; 0..this.length) { 544 if(this[i] != slots[i]) return false; 545 } 546 return true; 547 } 548 549 /// ditto 550 public override bool opEquals(Object object) { 551 return cast(Inventory)object ? this.opEquals(cast(Slot[])cast(Inventory)object) : false; 552 } 553 554 /** 555 * Returns a string with representing the inventory and its 556 * array of slots. 557 */ 558 public override string toString() { 559 return "Inventory(" ~ to!string(this.length) ~ ", " ~ to!string(this[]) ~ ")"; 560 } 561 562 } 563 564 unittest { 565 566 import selery.item.items : Items; 567 568 // creation 569 auto inventory = new Inventory(10); 570 assert(inventory.empty && inventory.length == 10); 571 572 // assignment 573 inventory[2] = Slot(new Items.Apple(), 64); 574 assert(!inventory.empty && inventory[2] == Slot(new Items.Apple(), 64)); 575 576 // range assignment 577 inventory[1..4] = Slot(new Items.Cookie(), 60); 578 assert(inventory[3] == Slot(new Items.Cookie(), 60)); 579 580 // concatenation 581 assert((inventory ~ inventory).length == 20); 582 assert((inventory ~ Slot(null))[$-1] == Slot(null)); 583 584 // adding an item 585 inventory += Slot(new Items.Cookie(), 1); 586 assert(inventory[1] == Slot(new Items.Cookie(), 61)); 587 inventory += Slot(new Items.Cookie("{\"customName\":\"Special Cookie\"}"), 1); 588 assert(inventory[1].count == 61 && inventory[0].item.customName == "Special Cookie"); 589 590 // removing an item 591 inventory -= Slot(new Items.Cookie(), 61); 592 assert(inventory[1].empty); 593 inventory -= new Items.Cookie(); 594 assert(inventory[2].empty && !inventory[0].empty); 595 inventory -= Items.COOKIE; 596 assert(inventory.empty); 597 598 // math 599 inventory += Slot(new Items.Cookie(), 12); 600 inventory += 12; // 24 601 inventory -= 8; // 16 602 inventory *= 4; // 64 603 inventory /= 32; // 2 604 assert(inventory[0] == Slot(new Items.Cookie(), 2)); 605 606 // group 607 inventory[2..4] = Slot(new Items.Cookie(), 32); 608 inventory[1] = Slot(new Items.Cookie("{\"customName\":\"Ungroupable\"}"), 32); 609 inventory.group(2); 610 assert(inventory[0].empty && inventory[2].count == 64 && inventory[3].count == 2); 611 612 } 613 614 /** 615 * A part of an inventory with all the methods of a normal inventory. 616 * It's given by default using by calling the Inventory's method opIndex 617 * with a valid range. 618 * 619 * This kind of inventory can be used to perform operations only on 620 * a part of the full inventory. 621 * Example: 622 * --- 623 * auto inventory = new Inventory(100); 624 * auto part = inventory[50..$]; 625 * part += new Items.Apple(); 626 * --- 627 */ 628 class InventoryRange : Inventory { 629 630 private Inventory inventory; 631 public immutable size_t start, end; 632 633 public @safe this(Inventory inventory, size_t start, size_t end) { 634 super(0); 635 this.inventory = inventory; 636 this.start = start; 637 this.end = end; 638 } 639 640 public override @safe Slot[] opIndex() { 641 return this.inventory[][this.start..this.end]; 642 } 643 644 public override @safe Slot opIndex(size_t index) { 645 return this.inventory[index + this.start]; 646 } 647 648 public override @safe void opIndexAssign(Slot slot, size_t index) { 649 this.inventory[index + this.start] = slot; 650 } 651 652 public override pure nothrow @property @safe @nogc size_t length() { 653 return this.end - this.start; 654 } 655 656 } 657 658 unittest { 659 660 import selery.item.items : Items; 661 662 auto inventory = new Inventory(10); 663 auto range = inventory[2..4]; 664 assert(range.length == 2); 665 666 inventory[2] = Slot(new Items.Cookie(), 12); 667 assert(inventory[2] == range[0]); 668 assert(inventory[2..4] == range); 669 670 } 671 672 /** 673 * Group of inventories that acts as one. 674 * 675 * This class can be used to change the operation order in a normal 676 * inventory. 677 * Example: 678 * --- 679 * auto inv = new Inventory(4); 680 * auto ig = new InventoryGroup(inv[2..$], inv[0..2]); 681 * ig += new Items.Apple(); 682 * assert(inv == [Slot(null), Slot(null), Slot(new Items.Apple()), Slot(null)]); 683 * --- 684 */ 685 class InventoryGroup : Inventory { 686 687 private Inventory[] inventories; 688 private size_t n_length; 689 690 public this(Inventory[] inventories ...) { 691 super(0); 692 this.inventories = inventories; 693 foreach(Inventory inventory ; inventories) { 694 this.n_length += inventory.length; 695 } 696 } 697 698 public override @safe Slot[] opIndex() { 699 Slot[] ret; 700 ret.reserve(this.length); 701 foreach(Inventory inventory ; this.inventories) { 702 ret ~= inventory[]; 703 } 704 return ret; 705 } 706 707 public override @safe Slot opIndex(size_t index) { 708 size_t i = index; 709 foreach(Inventory inventory ; this.inventories) { 710 if(i < inventory.length) { 711 return inventory[i]; 712 } else { 713 i -= inventory.length; 714 } 715 } 716 throw new InventoryRangeError(index, this); 717 } 718 719 public override @safe void opIndexAssign(Slot slot, size_t index) { 720 size_t i = index; 721 foreach(Inventory inventory ; this.inventories) { 722 if(i < inventory.length) { 723 return inventory[i] = slot; 724 } else { 725 i -= inventory.length; 726 } 727 } 728 throw new InventoryRangeError(index, this); 729 } 730 731 public override pure nothrow @property @safe @nogc size_t length() { 732 return this.n_length; 733 } 734 735 } 736 737 unittest { 738 739 import selery.item.items : Items; 740 741 auto inventory = new Inventory(10); 742 auto igroup = new InventoryGroup(inventory[4..$], inventory[0..4]); 743 744 igroup += Slot(new Items.Cookie(), 1); 745 assert(inventory[0].empty && inventory[4] == Slot(new Items.Cookie(), 1)); 746 747 } 748 749 /** 750 * Special inventory that notifies the holder when a slot 751 * is updated. 752 */ 753 class NotifiedInventory : Inventory { 754 755 protected InventoryHolder holder; 756 757 public @safe this(InventoryHolder holder, size_t slots) { 758 super(slots); 759 this.holder = holder; 760 } 761 762 public override @safe void opIndexAssign(Slot slot, size_t index) { 763 super.opIndexAssign(slot, index); 764 this.holder.slotUpdated(index); 765 } 766 767 } 768 769 /** 770 * Inventory held by an entity that also contains slots 771 * for armour and item's holding. 772 */ 773 class PlayerInventory : Inventory { 774 775 public static immutable ubyte HELD = 1u; 776 public static immutable ubyte INVENTORY = 2u; 777 public static immutable ubyte ARMOR = 4u; 778 public static immutable ubyte ALL = HELD | INVENTORY | ARMOR; 779 780 public ubyte update = ALL; 781 public bool[] slot_updates; 782 public ubyte update_viewers = 0; 783 784 protected Human holder; 785 786 private Hotbar m_hotbar = Hotbar(9); 787 private uint m_selected = 0; 788 789 public @safe this(Human holder) { 790 super(36 + 4); 791 this.slot_updates.length = 36; 792 this.holder = holder; 793 this.reset(); 794 } 795 796 public final @safe @nogc void reset() { 797 foreach(uint i ; 0..9) { 798 this.m_hotbar[i] = i; 799 } 800 this.m_selected = 0; 801 } 802 803 public @property @safe @nogc ref Hotbar hotbar() { 804 return this.m_hotbar; 805 } 806 807 public @property @safe @nogc uint selected() { 808 return this.m_selected; 809 } 810 811 public @property @safe @nogc uint selected(uint selected) { 812 this.update_viewers |= HELD; 813 return this.m_selected = selected; 814 } 815 816 /** 817 * Gets the slot the entity has in its hand. 818 * Example: 819 * --- 820 * if(player.inventory.held == Items.SWORD) { 821 * player.sendMessage("Attack!"); 822 * } 823 * --- 824 */ 825 public @property @safe Slot held() { 826 return this.hotbar[this.selected] == 255 ? Slot(null) : this[this.hotbar[this.selected]]; 827 } 828 829 /** 830 * Sets the slot the entity has in its hand. 831 * Example: 832 * --- 833 * if(player.inventory.held.empty) { 834 * player.inventory.held = new Items.Apple(); 835 * player.sendMessage("Hey, hold this"); 836 * } 837 * --- 838 */ 839 public @property @safe Slot held(Slot item) { 840 this[this.hotbar[this.selected] - 9] = item; 841 //this.update |= HELD; 842 this.update_viewers |= HELD; 843 return this.held; 844 } 845 846 // called when a player is using this item but it didn't send any MobEquipment packet (because it's a shitty buggy game) 847 public bool heldFromHotbar(Slot item) { 848 if(item == this.held) return true; 849 foreach(size_t index ; 0..this.hotbar.length) { 850 Slot cmp = super.opIndex(this.hotbar[index] - 9); 851 if(!cmp.empty && cmp == item || cmp.empty && item.empty) { 852 this.selected = to!uint(index); 853 return true; 854 } 855 } 856 return false; 857 } 858 859 public @safe void resetSlotUpdates() { 860 foreach(size_t index ; 0..this.slot_updates.length) { 861 this.slot_updates[index] = false; 862 } 863 } 864 865 alias opIndex = super.opIndex; 866 867 public override @safe Slot opIndex(size_t index) { 868 auto test = this[][index]; // test access violation 869 return super.opIndex(index); 870 } 871 872 public override @safe void opIndexAssign(Slot item, size_t index) { 873 //this.update |= INVENTORY; 874 if(index < this.slot_updates.length) this.slot_updates[index] = true; 875 if(index == this.selected) this.update_viewers |= HELD; 876 auto test = this[][index]; // test access violation 877 super.opIndexAssign(item, index); 878 } 879 880 /** 881 * Sets a slot using a string, creating the Item object from 882 * the holder's world's items. 883 * 884 * See the superclass's opIndexAssign documentation for more 885 * informations about this function. 886 * Example: 887 * --- 888 * player.inventory[1] = Items.APPLE; 889 * assert(player.inventory[1] == Slot(new Items.Apple()); 890 * --- 891 */ 892 public void opIndexAssign(item_t item, size_t index) { 893 this[index] = this.holder.world.items.get(item); 894 } 895 896 public override @property @safe @nogc size_t length() { 897 return super.length - 4; 898 } 899 900 /*public override @property @safe size_t length(size_t length) { 901 this.slot_updates.length = length; 902 return super.length(length + 4); 903 }*/ 904 905 public @property @safe Slot helmet() { 906 return super[super.length-4]; 907 } 908 909 public @property @safe Slot helmet(Slot helmet) { 910 super.opIndexAssign(helmet, super.length-4); 911 this.update |= ARMOR; 912 this.update_viewers |= ARMOR; 913 return this.helmet; 914 } 915 916 public @property @safe Slot chestplate() { 917 return super[super.length-3]; 918 } 919 920 public @property @safe Slot chestplate(Slot chestplate) { 921 super.opIndexAssign(chestplate, super.length-3); 922 this.update |= ARMOR; 923 this.update_viewers |= ARMOR; 924 return this.chestplate; 925 } 926 927 public @property @safe Slot leggings() { 928 return super[super.length-2]; 929 } 930 931 public @property @safe Slot leggings(Slot leggings) { 932 super.opIndexAssign(leggings, super.length-2); 933 this.update |= ARMOR; 934 this.update_viewers |= ARMOR; 935 return this.leggings; 936 } 937 938 public @property @safe Slot boots() { 939 return super[super.length-1]; 940 } 941 942 public @property @safe Slot boots(Slot boots) { 943 super.opIndexAssign(boots, super.length-1); 944 this.update |= ARMOR; 945 this.update_viewers |= ARMOR; 946 return this.boots; 947 } 948 949 alias cap = this.helmet; 950 alias tunic = this.chestplate; 951 alias pants = this.leggings; 952 953 public @property @safe Inventory armor() { 954 return super.opIndex(Slice(super.length-4, super.length)); 955 } 956 957 public @property @safe Slot armor(uint type) { 958 return super.opIndex(this.length + type); 959 } 960 961 public @property @safe Slot[] armor(uint type, Slot armor) { 962 super.opIndexAssign(armor, this.length + type); 963 this.update |= ARMOR; 964 this.update_viewers |= ARMOR; 965 return cast(Slot[])this.armor; 966 } 967 968 public @property @safe bool hasArmor() { 969 return !this.helmet.empty || !this.chestplate.empty || !this.leggings.empty || !this.boots.empty; 970 } 971 972 public @property @trusted uint protection() { 973 uint ret = 0; 974 foreach(Slot slot ; this.armor) { 975 if(!slot.empty && cast(Armor)(cast(Object)slot.item)) ret += (cast(Armor)(cast(Object)slot.item)).protection; 976 } 977 return ret; 978 } 979 980 public @property @safe Slot[] full() { 981 return super[]; 982 } 983 984 private struct Hotbar { 985 986 private uint[] m_hotbar; 987 988 public @safe this(size_t length) { 989 this.m_hotbar.length = length; 990 } 991 992 public @property @safe @nogc size_t length() { 993 return this.m_hotbar.length; 994 } 995 996 public @property @safe @nogc size_t opDollar() { 997 return this.length; 998 } 999 1000 public @safe @nogc uint opIndex(size_t index) { 1001 return this.m_hotbar[index]; 1002 } 1003 1004 public @safe @nogc void opIndexAssign(uint value, size_t index) { 1005 this.m_hotbar[index] = value; 1006 } 1007 1008 public @property @safe @nogc uint[] hotbar() { 1009 return this.m_hotbar; 1010 } 1011 1012 alias hotbar this; 1013 1014 } 1015 1016 } 1017 1018 interface InventoryHolder { 1019 1020 public @trusted void slotUpdated(size_t slot); 1021 1022 }