|
@@ -0,0 +1,287 @@
|
|
|
|
+
|
|
|
|
+type ItemReference = string | OutfitItem;
|
|
|
|
+
|
|
|
|
+function outfitItemId(item:ItemReference){
|
|
|
|
+ if(typeof item == "string")
|
|
|
|
+ return item;
|
|
|
|
+ if(typeof item == "object")
|
|
|
|
+ return item.id;
|
|
|
|
+ throw new Error("Invalid Argument");
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+type FilterData = {[fieldId:string]:number|string|boolean|{
|
|
|
|
+ max?: number;
|
|
|
|
+ min?: number;
|
|
|
|
+}}
|
|
|
|
+
|
|
|
|
+class Filter{
|
|
|
|
+ private _filterData = [];
|
|
|
|
+ public get filterData():FilterData{
|
|
|
|
+ return Object.fromEntries(this._filterData);
|
|
|
|
+ }
|
|
|
|
+ private set filterData(v:FilterData){
|
|
|
|
+ function easeOfChecking(val:any):number{
|
|
|
|
+ switch(typeof val){
|
|
|
|
+ case 'boolean': return 0;
|
|
|
|
+ case 'number': return 1;
|
|
|
|
+ case 'string': return 3;
|
|
|
|
+ case 'object': return 10;
|
|
|
|
+ default:
|
|
|
|
+ return 100;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ this._filterData = Object.entries(v).toSorted((a,b)=>easeOfChecking(a) - easeOfChecking(b));
|
|
|
|
+ }
|
|
|
|
+ constructor(filterData: FilterData){
|
|
|
|
+ this.filterData = filterData;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public passes(val:any){
|
|
|
|
+ if(typeof val != "object")
|
|
|
|
+ return false;
|
|
|
|
+ for(const [fieldId, checkValue] of this._filterData){
|
|
|
|
+ if(val[fieldId] === undefined)
|
|
|
|
+ return false;
|
|
|
|
+ switch(typeof checkValue){
|
|
|
|
+ case "string":
|
|
|
|
+ case "number":
|
|
|
|
+ case "boolean":
|
|
|
|
+ if(val[fieldId] !== checkValue)
|
|
|
|
+ return false;
|
|
|
|
+ break;
|
|
|
|
+ case "object":
|
|
|
|
+ throw new Error("NOT IMPLEMENTED");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+class Wardrobe extends GameObject{
|
|
|
|
+
|
|
|
|
+ /*private $itemProxy=undefined;
|
|
|
|
+ get item(){
|
|
|
|
+ this.$itemProxy ??= new Proxy(this, {
|
|
|
|
+ get(wardrobeObject, staticItemId){
|
|
|
|
+ if(typeof staticItemId == "symbol")
|
|
|
|
+ return undefined;
|
|
|
|
+ if(!setup.wardrobeStatic[staticItemId])
|
|
|
|
+ return undefined;
|
|
|
|
+
|
|
|
|
+ return new Proxy(new WardrobeItem(staticItemId),{
|
|
|
|
+ get(wardrobeItem, propertyKey){
|
|
|
|
+ if(typeof propertyKey == "symbol")
|
|
|
|
+ return undefined;
|
|
|
|
+ return wardrobeItem[propertyKey];
|
|
|
|
+ },
|
|
|
|
+ set(wardrobeItem, propertyKey, v){
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+ ownKeys(wardrobeObject) {
|
|
|
|
+ return Object.keys(wardrobeObject.ownedItemData);
|
|
|
|
+ },
|
|
|
|
+ }) as unknown as any;
|
|
|
|
+ return this.$itemProxy;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private ownedItemData:{[key:string]: OwnedWardrobeItemData} = {};
|
|
|
|
+ private $ownedItemProxy=undefined;
|
|
|
|
+ get owned(){
|
|
|
|
+ //The outer proxy catches all requests in the form of $Wardrobe.owned["itemId"].
|
|
|
|
+ //A proxy is used here to intercept calls that don't refer to itemIds, such as $Wardrobe.owned["*Coat"].
|
|
|
|
+ this.$ownedItemProxy ??= new Proxy(this, {
|
|
|
|
+ get(wardrobeObject, itemId){
|
|
|
|
+ if(typeof itemId == "symbol")
|
|
|
|
+ return undefined;
|
|
|
|
+
|
|
|
|
+ let itemIsOwned = true;
|
|
|
|
+
|
|
|
|
+ if(!wardrobeObject.ownedItemData[itemId]){
|
|
|
|
+ if(itemId.startsWith('*')){
|
|
|
|
+ const slot = itemId.substring(1);
|
|
|
|
+ if(this.worn[slot])
|
|
|
|
+ itemId = wardrobeObject.worn[slot];
|
|
|
|
+ else
|
|
|
|
+ return undefined;
|
|
|
|
+ }else if(setup.wardrobeStatic[itemId]){
|
|
|
|
+ const itemsOwnedByItemId = Object.entries(wardrobeObject.ownedItemData).filter(([id,data])=>data.itemId == itemId).map(([id,data])=>id);
|
|
|
|
+ if(!itemsOwnedByItemId.length){
|
|
|
|
+ itemIsOwned = false;
|
|
|
|
+ }else if(itemsOwnedByItemId.length == 1){
|
|
|
|
+ itemId = itemsOwnedByItemId[0];
|
|
|
|
+ }else{
|
|
|
|
+ const wornItemIds = Object.values(wardrobeObject.worn);
|
|
|
|
+ const itemsWorn = itemsOwnedByItemId.filter(itemId=>wornItemIds.includes(itemId));
|
|
|
|
+ if(itemsWorn.length != 1)
|
|
|
|
+ throw new Error(`Ambiguous wardrobe item access for id '${itemId}'`);
|
|
|
|
+ itemId = itemsWorn[0];
|
|
|
|
+ }
|
|
|
|
+ }else{
|
|
|
|
+ return undefined;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if(!itemIsOwned)
|
|
|
|
+ return undefined; //Maybe handle this differently in the future.
|
|
|
|
+
|
|
|
|
+ return new Proxy(new OwnedWardrobeItem(itemId, wardrobeObject.ownedItemData[itemId]),{
|
|
|
|
+ get(ownedWardrobeItem, propertyKey){
|
|
|
|
+ if(typeof propertyKey == "symbol")
|
|
|
|
+ return undefined;
|
|
|
|
+ return ownedWardrobeItem.get(propertyKey);
|
|
|
|
+ },
|
|
|
|
+ set(ownedWardrobeItem, propertyKey, v){
|
|
|
|
+ if(typeof propertyKey == "symbol")
|
|
|
|
+ return undefined;
|
|
|
|
+ return ownedWardrobeItem.set(propertyKey,v);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+ ownKeys(wardrobeObject) {
|
|
|
|
+ return Object.keys(wardrobeObject.ownedItemData);
|
|
|
|
+ },
|
|
|
|
+ }) as unknown as any;
|
|
|
|
+ return this.$ownedItemProxy;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ get ownedItems(){
|
|
|
|
+ return Object.fromEntries(Object.entries(this.ownedItemData).map(([inventoryId, ownedItemData])=>this.owned[inventoryId]));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public get Bra(){return this.owned["*Bra"]};
|
|
|
|
+ public get Clothes(){return this.owned["*Clothes"]};
|
|
|
|
+ public get Coat(){return this.owned["*Coat"]};
|
|
|
|
+ public get Panties(){return this.owned["*Panties"]};
|
|
|
|
+ public get Purse(){return this.owned["*Purse"]};
|
|
|
|
+ public get Shoes(){return this.owned["*Shoes"]};
|
|
|
|
+
|
|
|
|
+ private worn: {[slotId:string]: string} = {};
|
|
|
|
+
|
|
|
|
+ public unwear(itemId:string){
|
|
|
|
+ const itemData = this.item[itemId];
|
|
|
|
+ const slots = itemData.slots;
|
|
|
|
+ for(const slot of slots){
|
|
|
|
+ this.worn[slot] = undefined;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public unwearBySlot(slot:string){
|
|
|
|
+ if(this.worn[slot])
|
|
|
|
+ this.unwear(this.worn[slot]);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public wear(itemId){
|
|
|
|
+ const itemData = this.owned[itemId];
|
|
|
|
+ const slots = itemData.slots;
|
|
|
|
+ for(const slot of slots){
|
|
|
|
+ this.unwearBySlot(slot);
|
|
|
|
+ }
|
|
|
|
+ }*/
|
|
|
|
+
|
|
|
|
+ //#region Ownership
|
|
|
|
+ private _ownedItems:{[itemId:string]:OwnedOutfitItem} = {};
|
|
|
|
+ public get ownedItems(){
|
|
|
|
+ return this._ownedItems;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public ownedItemsFiltered(filter:FilterData){
|
|
|
|
+ const f = new Filter(filter);
|
|
|
|
+ return Object.fromEntries(
|
|
|
|
+ Object.entries(this._ownedItems).
|
|
|
|
+ filter(
|
|
|
|
+ ([itemId,item])=>f.passes(item)
|
|
|
|
+ )
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public add(item: ItemReference){
|
|
|
|
+ try{
|
|
|
|
+ const itemId = outfitItemId(item);
|
|
|
|
+ this._ownedItems[itemId] = new setup.OwnedOutfitItem(itemId);
|
|
|
|
+ console.log("Wardrobe Added",itemId);
|
|
|
|
+ }
|
|
|
|
+ catch(e){
|
|
|
|
+ console.error(e);
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public delete(item: ItemReference){
|
|
|
|
+ const itemId = outfitItemId(item);
|
|
|
|
+ delete this._ownedItems[itemId];
|
|
|
|
+ console.log("Wardrobe Deleted",itemId);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public ownsItem(item: ItemReference){
|
|
|
|
+ const itemId = outfitItemId(item);
|
|
|
|
+ return Object.keys(this._ownedItems).includes(itemId);
|
|
|
|
+ }
|
|
|
|
+ //#endregion
|
|
|
|
+
|
|
|
|
+ //#region Worn
|
|
|
|
+ private _wornItemIds:{[slotId:string]:string} = {};
|
|
|
|
+ public get wornItems():{[slotId:string]:OutfitItem}{
|
|
|
|
+ return Object.fromEntries(
|
|
|
|
+ Object.keys(this._wornItemIds)
|
|
|
|
+ .map(
|
|
|
|
+ slotId=>[
|
|
|
|
+ slotId,
|
|
|
|
+ this.wornAtSlot(slotId)
|
|
|
|
+ ]
|
|
|
|
+ )
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public wornAtSlot(slotId:string):OutfitItem|null{
|
|
|
|
+ const itemId = this._wornItemIds[slotId];
|
|
|
|
+ if(!itemId)
|
|
|
|
+ return null;
|
|
|
|
+ return this.getItemOwnedOrStatic(itemId);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public unwear(item: ItemReference){
|
|
|
|
+ if(!item) return;
|
|
|
|
+ const itemId = outfitItemId(item);
|
|
|
|
+ const itemData = this.getItemOwnedOrStatic(itemId);
|
|
|
|
+ for(const slot of itemData.slots)
|
|
|
|
+ this._wornItemIds[slot] = null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public wear(item: ItemReference){
|
|
|
|
+ if(!item) return;
|
|
|
|
+ const itemId = outfitItemId(item);
|
|
|
|
+ const itemData = this.getItemOwnedOrStatic(itemId);
|
|
|
|
+ for(const slot of itemData.slots){
|
|
|
|
+ const itemAtSlot = this.wornAtSlot(slot);
|
|
|
|
+ if(itemAtSlot)
|
|
|
|
+ this.unwear(itemAtSlot);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ //#endregion
|
|
|
|
+
|
|
|
|
+ //#region Data
|
|
|
|
+
|
|
|
|
+ private getItemOwnedOrStatic(itemId:string):OutfitItem{
|
|
|
|
+ if(this._ownedItems[itemId])
|
|
|
|
+ return this._ownedItems[itemId];
|
|
|
|
+ return new setup.OutfitItem(itemId);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //#endregion
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ //#region SYSTEM
|
|
|
|
+ clone = function () {
|
|
|
|
+ return (new setup.Wardrobe())._init(this.clonableFields);
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ toJSON = function () {
|
|
|
|
+ return JSON.reviveWrapper('(new setup.Wardrobe())._init($ReviveData$)', this.ownData);
|
|
|
|
+ };
|
|
|
|
+ //#endregion
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+setup.Wardrobe = Wardrobe;
|