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