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 }