Source: Apparel/WornApparel.js

/*****************************************************************************
 * Copyright (c) 2019 Echo Hollow / L. Adamson. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without 
 *  modification, are permitted provided that the following conditions are met:
 * 
 *     1. Redistributions of source code must retain the above copyright 
 *        notice, this list of conditions and the following disclaimer.
 *     2. Redistributions in binary form must reproduce the above copyright 
 *        notice, this list of conditions and the following disclaimer in the 
 *        documentation and/or other materials provided with the distribution.
 *     3. All advertising materials mentioning features or use of this software 
 *        must display the following acknowledgement:
 *          "This product includes software developed by Echo Hollow."
 *     4. Neither the name of the copyright holder nor the names of its 
 *        contributors may be used to endorse or promote products derived from 
 *        this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR 
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 
 * EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *****************************************************************************/

const PersistentObject = LibEcho.Persistence.PersistentObject;
const ClothingSlot = LibEcho.Apparel.ClothingSlot;
const GenericApparel = LibEcho.Apparel.GenericApparel;
const Wardrobe = LibEcho.Apparel.Wardrobe;

/**
 * This class, derived from {@link LibEcho.Inventory.Wardrobe|Wardrobe}, is meant to track worn {@link LibEcho.Apparel|Apparel}. It overrides preAdd() so that only objects of type {@link LibEcho.Apparel.GenericApparel|GenericApparel} (or subclasses thereof) may be added to the {@link LibEcho.Inventory|Inventory}. The {@link LibEcho.Inventory.Wardrobe|Wardrobe} parent class also overrides sort() so that the contents are sorted in order of {@link LibEcho.Apparel.ClothingSlot|ClothingSlot} value rather than alphabetically.
 * 
 * @extends LibEcho.Inventory.Wardrobe
 * @memberof LibEcho.Apparel
 */
class WornApparel extends Wardrobe
{
	static defineCoveredBy( slot, resolverFunction )
	{
		var coveredBy = ( WornApparel._coveredBy = WornApparel._coveredBy || {} );
		coveredBy[slot] = resolverFunction;
	}
	
    /**
     * <i>You should never instantiate a {@link LibEcho.Persistence.PersistentObject|PersistentObject} directly!  Instead, you must define it with {@link LibEcho.PersistentObject.define|PersistentObject.define()}, and then instantiate it with the {@link LibEcho.PersistentObject.instantiate|obj()} function when you want to use it.  <i><b>You must also never store {@link LibEcho.Persistence.PersistentObject|PersistentObjects} in Sugarcube variables!</b></i>  Just store their ids, instantiate them with {@link LibEcho.Persistence.obj|obj()} as needed, and then let them be garbage-collected.</i>
     * 
     * @param {string} id - The id of the {@link LibEcho.Persistence|Persistence} defaults entry to be associated with this instance.
     */
	constructor( id )
	{
		super( id );
		
		WornApparel.defineCoveredBy( ClothingSlot.UNDERPANTS, function(inv) {
			var o;
			// LEGS always cover UNDERPANTS.
			o = inv.atSlot( ClothingSlot.LEGS );
			if( o )
				return [ o.id ];
			// Long TORSO covers UNDERPANTS.
			o = inv.atSlot( ClothingSlot.TORSO );
			if( o && o.long )
				return [ o.id ];
			// Long OVER covers UNDERPANTS.
			o = inv.atSlot( ClothingSlot.OVER );
			if( o && o.long )
				return [ o.id ];
			return [];
		});
		WornApparel.defineCoveredBy( ClothingSlot.UNDERSHIRT, function(inv) {
			var o;
			// TORSO always covers UNDERSHIRT.
			o = inv.atSlot( ClothingSlot.TORSO );
			if( o )
				return [ o.id ];
			// OVER always covers UNDERSHIRT.
			o = inv.atSlot( ClothingSlot.OVER );
			if( o )
				return [ o.id ];
			return [];
		});
		WornApparel.defineCoveredBy( ClothingSlot.UNDERLEGS, function(inv) {
			// Tall FEET + Long LEGS covers UNDERLEGS.
			// Tall FEET + Long OVER covers UNDERLEGS.
			var feet = inv.atSlot( ClothingSlot.FEET );
			if( !feet || !feet.tall )
				return [];
			var o;
			o = inv.atSlot( ClothingSlot.LEGS );
			if( o && o.long )
				return [ o.id, feet.id ];
			o = inv.atSlot( ClothingSlot.OVER );
			if( o && o.long )
				return [ o.id, feet.id ];
			return [];
		});
		WornApparel.defineCoveredBy( ClothingSlot.TORSO, function(inv) {
			var o;
			// OVER always covers TORSO.
			o = inv.atSlot( ClothingSlot.OVER );
			if( o )
				return [ o.id ];
			return [];
		});
		WornApparel.defineCoveredBy( ClothingSlot.LEGS, function(inv) {
			var o;
			// Long OVER covers LEGS.
			o = inv.atSlot( ClothingSlot.OVER );
			if( o && o.long )
				return [ o.id ];
			return [];
		});
		WornApparel.defineCoveredBy( ClothingSlot.FEET, function(inv) {
			// Nothing ever covers FEET.
			return [];
		});
		WornApparel.defineCoveredBy( ClothingSlot.OVER, function(inv) {
			// Nothing ever covers OVER.
			return [];
		});
		WornApparel.defineCoveredBy( ClothingSlot.GLOVES, function(inv) {
			// Nothing ever covers GLOVES.
			return [];
		});
		WornApparel.defineCoveredBy( ClothingSlot.HEAD, function(inv) {
			// Nothing ever covers HEAD.
			return [];
		});
		WornApparel.defineCoveredBy( ClothingSlot.EARS, function(inv) {
			var o;
			// Long HEAD covers EARS.
			o = inv.atSlot( ClothingSlot.HEAD );
			if( o && o.long )
				return [ o.id ];
			return [];
		});
		WornApparel.defineCoveredBy( ClothingSlot.NECK, function(inv) {
			var o;
			// Tall TORSO covers NECK.
			o = inv.atSlot( ClothingSlot.TORSO );
			if( o && o.tall )
				return [ o.id ];
			// Tall OVER covers NECK.
			o = inv.atSlot( ClothingSlot.OVER );
			if( o && o.tall )
				return [ o.id ];
			return [];
		});
		WornApparel.defineCoveredBy( ClothingSlot.WRIST, function(inv) {
			var o;
			// Long GLOVES covers WRIST.
			o = inv.atSlot( ClothingSlot.GLOVES );
			if( o && o.long )
				return [ o.id ];
			return [];
		});
		WornApparel.defineCoveredBy( ClothingSlot.FINGER, function(inv) {
			var o;
			// GLOVES always covers FINGER.
			o = inv.atSlot( ClothingSlot.GLOVES );
			if( o )
				return [ o.id ];
			return [];
		});
	}
	
    /**
     * This method is overridden, to prevent anything that doesn't ultimately inherit from {@link LibEcho.Apparel.GenericApparel|GenericApparel} from being added to the {@link LibEcho.Inventory|Inventory}.
     * 
     * @param {string} id - The id of the object to be added to the {@link LibEcho.Inventory|Inventory}.
	 * @returns {boolean} true if the {@link LibEcho.Persistence.PersistentObject|PersistentObject} referenced by 'id' is an instanceof {@link LibEcho.Apparel.GenericApparel|GenericApparel}, or false if it is not.
	 * @throws Error if the class of the {@link LibEcho.Persistence|Persistence} object referenced by id is not an instanceof {@link LibEcho.Apparel.GenericApparel|GenericApparel} (or subclass thereof).
     */
	preAdd( item )
	{
		if( !(item instanceof GenericApparel) )
			return false;
		return true;
	}
	
    /**
     * This method is overridden, to remove all {@link LibEcho.Apparel.GenericApparel|Apparel} objects that occupy the same {@link LibEcho.Apparel.ClothingClot|ClothingClot} as the object that was added.
     * 
     * @param {string} id - The id of the object to be added to the {@link LibEcho.Inventory|Inventory}.
	 * @returns {array} a list of the items that were removed to make room for the new item, or an empty array if nothing was removed.
	 * @throws Error if the class of the {@link LibEcho.Persistence|Persistence} object referenced by id is not an instanceof {@link LibEcho.Apparel.GenericApparel|GenericApparel} (or subclass thereof).
     */
	postAdd( item )
	{
		var removed = [];
		top: for( var worn of this.contents() )
		{
			if( !worn.equals(item) )
			{
				for( var wslot of worn.slots )
				{
					for( var oslot of item.slots )
					{
						if( wslot == oslot )
						{
							this.remove( worn, true, false );
							removed.push( worn );
							continue top;
						}
					}
				}
			}
		}
		return removed;
	}
	
	atSlot( slot )
	{
		for( var o of this.contents() )
			if( o.slots.includes(slot) )
				return o;
		return undefined;
	}
	
	hiddenBy( item )
	{
		if( (typeof item) == "string" )
			item = fetchPersistentObject( item ); // FIXME This ought to be removed.
		if( !this.has(item) )
			throw new Error( "hiddenBy: "+this.id+" does not contain "+item.id+"!" );
		if( !(item instanceof GenericApparel) )
			throw new Error( item.id+" is not an instance of GenericApparel!" );
		var l = [];
		for( var slot of item.slots )
		{
			var coveredBy = ( WornApparel._coveredBy = WornApparel._coveredBy || {} );
			for( var t of coveredBy[slot](this) )
			{
				if( t && !t.transparent && !t.exposing )
					l.pushUnique( t );
			}
		}
//		// Still have to make sure all the slots are covered!
//		var coveredBySlots = [];
//		for( var to of il )
//			for( var slot of to.slots )
//				coveredBySlots.pushUnique( slot );
//alert( coveredBySlots.commaList() );
//		for( var slot of o.slots )
//			if( !coveredBySlots.includes(slot) )
//				return [];
		return l;
	}
	
	hiding( item )
	{
		if( (typeof item) == "string" )
			item = fetchPersistentObject( item ); // FIXME This ought to be removed.
		if( !this.has(item) )
			throw new Error( "hiding: "+this.id+" does not contain "+item.id+"!" );
		if( !(item instanceof GenericApparel) )
			throw new Error( item.id+" is not an instance of GenericApparel!" );
		var l = [];
		for( var citem of this.contents() )
		{
			if( citem.equals(item) )
				continue;
			if( this.hiddenBy(citem,true).includes(item) )
				l.pushUnique( citem );
		}
		return l;
	}
	
	outlinedBy( item )
	{
		if( (typeof item) == "string" )
			item = fetchPersistentObject( item ); // FIXME This ought to be removed.
		if( !this.has(item) )
			throw new Error( "outlinedBy: "+this.id+" does not contain "+item.id+"!" );
		if( !(item instanceof GenericApparel) )
			throw new Error( item.id+" is not an instance of GenericApparel!" );
		var l = [];
		for( var citem of this.hiddenBy(item) )
			if( citem.thin )
				l.pushUnique( citem );
		return l;
	}
	
	showingOutlineOf( item )
	{
		if( (typeof item) == "string" )
			item = fetchPersistentObject( item ); // FIXME This ought to be removed.
		if( !this.has(item) )
			throw new Error( "showingOutlineOf: "+this.id+" does not contain "+item.id+"!" );
		if( !(item instanceof GenericApparel) )
			throw new Error( item.id+" is not an instance of GenericApparel!" );
		if( !item.thin )
			return [];
		// Should thin items beneath other thin items not show an outline?  Ie, the whole point of a thong being to not show an outline?
		return this.hiding( item );
	}
	
	contentsHidden( filterClass=undefined, filterFunction=undefined )
	{
		var rval = [];
		for( var item of this.contents(filterClass,filterFunction) )
			if( this.hiddenBy(item).length > 0 )
				rval.push( item );
		return rval;
	}
	
	contentsVisible( filterClass=undefined, filterFunction=undefined )
	{
		var rval = [];
		for( var item of this.contents(filterClass,filterFunction) )
		{
			if( this.hiddenBy(item).length <= 0 )
				rval.push( item );
		}
		return rval;
	}
	
	contentsOutlined( filterClass=undefined, filterFunction=undefined )
	{
		var rval = [];
		for( var item of this.contents(filterClass,filterFunction) )
			if( !this.outlinedBy(item).length<=0 && $.inArray(item,this.contentsVisible(filterClass,filterFunction)) < 0 )
				rval.push( item );
		return rval;
	}
	
	/**
	 * Overridden to display concealed items.  FIXME
	 * 
     * @param {boolean} showAll - If true, all items in the inventory are printed.  If false, only visible items are printed.  If undefined, acts as true if this WornApparel's parent is the $viewpointCharacter, otherwise acts as false.
	 *
	 * @returns {string} a serial-comma-separated-list of the {@link LibEcho.Inventory|Inventory's} contents' .aName fields.
	 */
	toString()
	{
		if( this.override )
			return this.override;
		return this.contents().aList();
	}
	
	/**
	 * Overridden to display concealed items.  Like toString(), but using .theName instead.  FIXME
	 * 
     * @param {boolean} showAll - If true, all items in the inventory are printed.  If false, only visible items are printed.  If undefined, acts as true if this WornApparel's parent is the $viewpointCharacter, otherwise acts as false.
	 *
	 * @returns {string} a serial-comma-separated-list of the {@link LibEcho.Inventory|Inventory's} contents' .theName fields.
	 */
	theString()
	{
		if( this.override )
			return this.override;
		return this.contents().theList();
	}
	
}

module.exports = WornApparel;