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 }