wardrobe.ts 27 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132
  1. /// <reference path="../wardrobe/OutfitItem/OutfitItem.ts" />
  2. /// <reference path="../wardrobe/OutfitItem/Clothes.ts" />
  3. /// <reference path="../wardrobe/OutfitItem/Purse.ts" />
  4. /// <reference path="../wardrobe/OutfitItem/Shoes.ts" />
  5. const enum EWearableBMI{
  6. NOT_APPLICABLE = -2,
  7. ANY = -1,
  8. TOO_LOSE = 0,
  9. TOO_TIGHT = 1,
  10. ALMOST_TOO_LOSE = 2,
  11. ALMOST_TOO_TIGHT = 3,
  12. OKAY = 4
  13. }
  14. setup.wardrobeSettings = {
  15. bmiSizeMaxDifference: 2
  16. }
  17. setup.outfitsDefaults={
  18. clothes:{
  19. h:100,
  20. bmi:'$pc.bmi'
  21. }
  22. }
  23. setup.wardrobeWearableFunctions = {};
  24. class Wardrobe{
  25. _ownedItems:{[itemId:string]:OutfitOwnerShipData} = {}
  26. /**
  27. * Owned Item
  28. * @param {string} itemId
  29. */
  30. item(itemId:string):OutfitOwned{
  31. let item = OutfitItem.get(itemId) as OutfitItemClothes; // Trick Typescript
  32. let owneditem:OutfitOwned = Object.assign(item,{l:0,bmi:-2,h:-2},this._ownedItems[itemId]); // Item needs to be first, otherwise all its functions are broken
  33. return owneditem;
  34. }
  35. /**
  36. * Owned Items
  37. * @readonly
  38. * @type {{[key:string]:OutfitItem}}
  39. */
  40. get items():{[key:string]:OutfitOwned}{
  41. let result:{[key:string]:OutfitOwned} = {};
  42. for(const [itemId, itemData] of Object.entries(this._ownedItems)){
  43. //let item = OutfitItem.get(itemId);
  44. //Object.assign({l:0,bmi:-2,h:-2},item,itemData);
  45. result[itemId] = this.item(itemId);
  46. }
  47. return result;
  48. }
  49. itemsFiltered(filter:Filter){
  50. return setup.filterDict(this.items,filter);
  51. }
  52. //#region Worn Items
  53. _wornItems:WornOutfitDefinition = {
  54. bra: null,
  55. coat: null,
  56. clothes: null,
  57. panties: null,
  58. purse: null,
  59. shoes: null
  60. }
  61. get wornItems(){
  62. return {
  63. bra: this.bra,
  64. coat: this.coat,
  65. clothes: this.clothes,
  66. panties: this.panties,
  67. purse: this.purse,
  68. shoes: this.shoes
  69. }
  70. }
  71. get wornItemIds(){
  72. return this._wornItems;
  73. }
  74. get bra():OutfitItemBra{
  75. let braId = this.wornItemIds.bra;
  76. if(braId)
  77. return this.item(braId) as OutfitItemBra;
  78. return new OutfitItemBra();
  79. }
  80. get clothes():OutfitItemClothes|OutfitItemSwimwear{
  81. let clothesId = this.wornItemIds.clothes;
  82. if(clothesId)
  83. return this.item(clothesId) as OutfitItemClothes;
  84. return new OutfitItemClothes();
  85. }
  86. get coat():OutfitItemCoat{
  87. let coatId = this.wornItemIds.coat;
  88. if(coatId)
  89. return this.item(coatId) as OutfitItemCoat;
  90. return new OutfitItemCoat();
  91. }
  92. get panties():OutfitItemPanties{
  93. let pantiesId = this.wornItemIds.panties;
  94. if(pantiesId)
  95. return this.item(pantiesId) as OutfitItemPanties;
  96. return new OutfitItemPanties();
  97. }
  98. get purse():OutfitItemPurse{
  99. let purseId = this.wornItemIds.purse;
  100. if(purseId)
  101. return this.item(purseId) as OutfitItemPurse;
  102. return new OutfitItemPurse();
  103. }
  104. get shoes():OutfitItemShoes{
  105. let shoesId = this.wornItemIds.shoes;
  106. if(shoesId)
  107. return this.item(shoesId) as OutfitItemShoes;
  108. return new OutfitItemShoes();
  109. }
  110. //#endregion
  111. _lastWorn:{[key:string]:WornOutfitDefinition} = {
  112. last:{
  113. bra: null,
  114. coat: null,
  115. clothes: null,
  116. panties: null,
  117. purse: null,
  118. shoes: null
  119. }
  120. }
  121. _outfits:{[key:string]:WornOutfitDefinition} = {
  122. }
  123. outfitSports = '';
  124. outfitSchool = '';
  125. get outfits(){
  126. return this._outfits;
  127. }
  128. clothingDecreaseHealth(v){
  129. let clothesId = this._wornItems.clothes;
  130. if(clothesId in this._ownedItems){
  131. if(this._ownedItems[clothesId].h > 0){ //Health -1 is save
  132. this._ownedItems[clothesId].h = Math.max(0,this._ownedItems[clothesId].h - v);
  133. }
  134. }
  135. }
  136. /*get clothingIsNude(){
  137. return (!this._wornItems.clothes);
  138. }*/
  139. /*get clothingWorn(){
  140. return this._wornItems.clothes;
  141. }*/
  142. /*get clothingworntype(){
  143. if(!this._wornItems.clothes)
  144. return 'nude';
  145. let identificator = this.itemData(this._wornItems.clothes) as OutfitDefinitionClothes;
  146. return identificator.vendor+'_'+identificator.subtype;
  147. }*/
  148. /*
  149. get clothingwornnumber(){
  150. if(!this._wornItems.clothes)
  151. return 0;
  152. let identificator = this.itemData(this._wornItems.clothes) as OutfitDefinitionClothes;
  153. return identificator.index;
  154. }*/
  155. /*get coatworntype(){
  156. if(!this._wornItems.coat)
  157. return 'none';
  158. let identificator = this.itemData(this._wornItems.coat);
  159. return identificator.vendor;
  160. }*/
  161. /*get coatwornnumber(){
  162. if(!this._wornItems.coat)
  163. return 0;
  164. let identificator = this.itemData(this._wornItems.coat);
  165. return identificator.index;
  166. }*/
  167. /*get isWearingBra(){
  168. return (!(!this._wornItems.bra));
  169. }*/
  170. /*get isWearingPanties(){
  171. return (!(!this._wornItems.panties));
  172. }*/
  173. get isNaked(){
  174. return (!this.clothes.isValidItem && !this.bra.isValidItem && !this.panties.isValidItem);
  175. }
  176. /*get PCloQuality(){
  177. if(this.clothingIsNude){
  178. let bmi = State.variables.pc.bmi;
  179. if(bmi >= 19 && bmi < 30)
  180. return 3;
  181. return 1;
  182. }
  183. return this.wornItemData('clothes','quality',0)
  184. }
  185. get PCloThinness(){return this.wornItemData('clothes','thinness',0)}
  186. get PCloTopCut(){
  187. const pc = State.variables.pc;
  188. let wiD = this.wornItemData('clothes','CloTopCut',0);
  189. if(wiD > 1){
  190. if(pc.tits == 2 || pc.tits == 3)
  191. wiD += 1;
  192. else if(pc.tits >= 4)
  193. wiD += 2;
  194. if(wiD > 2 && pc.tits >= 6)
  195. wiD += 1;
  196. }
  197. return wiD;
  198. }
  199. get PCloBra(){return this.wornItemData('clothes','includesBra',0)}
  200. get PCloOnePiece(){return this.wornItemData('clothes','isOnepiece',0)}
  201. get PCloPants(){return this.wornItemData('clothes','pantsShortness',0)}
  202. get PCloSkirt(){return this.wornItemData('clothes','skirtShortness',0)}
  203. get skirtLength(){return this.PCloSkirt;}
  204. get PCloPanties(){return this.wornItemData('clothes','includesPanties',0)}
  205. get PCloDress(){return this.wornItemData('clothes','isDress',0)}
  206. get PCloStyle(){return this.wornItemData('clothes','style',0)}
  207. get PCloStyle2(){return this.wornItemData('clothes','style2',0)}
  208. get PCloStyle3(){return this.wornItemData('clothes','style3',0)}
  209. get PCloInhibit(){return this.wornItemData('clothes','inhibition',0)}
  210. get PCloBimbo(){return this.wornItemData('clothes','bimbo',0)}
  211. get PCloCoverTop(){return this.wornItemData('clothes','coverTop',4)}
  212. get PCloCoverBack(){return this.wornItemData('clothes','coverBack',4)}
  213. get PCloCoverFront(){return this.wornItemData('clothes','coverFront',4)}
  214. //get PCloCoverFront(){return this.wornItemData('clothes','coverFront',0)}
  215. */
  216. /*get PXCloThinness(){
  217. let PCloThinness = this.PCloThinness;
  218. switch(PCloThinness){
  219. case 1: return 150;
  220. case 2: return 200;
  221. case 3: return 250;
  222. case 4: return 300;
  223. case 5: return 350;
  224. case 6: return 400;
  225. }
  226. if(PCloThinness > 6)
  227. return 400;
  228. return 0;
  229. }*/
  230. /*get PXCloTopCut(){
  231. if(this.PCloBra == 1) return 400;
  232. if(this.PCloBra == 2) return 500;
  233. let PCloTopCut = this.PCloTopCut;
  234. switch(PCloTopCut){
  235. case 1: return 100;
  236. case 2: return 150;
  237. case 3: return 200;
  238. case 4: return 250;
  239. case 5: return 300;
  240. case 6: return 350;
  241. case 7: return 400;
  242. }
  243. if(PCloTopCut > 7)
  244. return 400;
  245. return 0;
  246. }*/
  247. /*get PXCloBottomShortness(){
  248. if(this.PCloPanties == 1) return 400;
  249. let PCloSkirt = this.PCloSkirt;
  250. if(PCloSkirt > 0){
  251. switch(PCloSkirt){
  252. case 1: return 100;
  253. case 2: return 150;
  254. case 3: return 200;
  255. case 4: return 250;
  256. case 5: return 300;
  257. case 6: return 350;
  258. case 7: return 400;
  259. }
  260. if(this.PCloThinness > 7)
  261. return 400;
  262. }
  263. let PCloPants = this.PCloPants;
  264. if(PCloPants > 0){
  265. switch(PCloPants){
  266. case 1: return 100;
  267. case 2: return 150;
  268. case 3: return 200;
  269. case 4: return 250;
  270. case 5: return 300;
  271. case 6: return 350;
  272. case 7: return 400;
  273. }
  274. if(PCloPants > 7)
  275. return 400;
  276. }
  277. return 0;
  278. }*/
  279. /*get PCloswimwear(){
  280. if(!this._wornItems.clothes)
  281. return 0;
  282. let identificator = this.itemData(this._wornItems.clothes) as OutfitDefinitionClothes;
  283. if(identificator.subtype == 'bikini' || identificator.subtype == 'swimsuit' || identificator.subtype == 'swim_one' || identificator.subtype == 'swim_two')
  284. return 1;
  285. return 0;
  286. }*/
  287. /*get isWearingSwimwear(){
  288. return !(!this.PCloswimwear);
  289. }*/
  290. /*get CoverTop(){return Math.max(this.PCloCoverTop + this.PBraCover - 4,0);}
  291. get CoverBack(){return Math.max(this.PCloCoverBack + this.PPanCoverBack - 4,0);}
  292. get CoverFront(){return Math.max(this.PCloCoverFront + this.PPanCoverFront - 4,0);}
  293. get isSchoolOutfit(){
  294. return this.wornItemData('clothes','subtype','') === 'school';
  295. }
  296. get isSportsOutfit(){
  297. return this.clothingworntype === 'danilovich_outfit';
  298. }*/
  299. // ----- Bra -----
  300. /*get braworntype(){
  301. if(!this._wornItems.bra)
  302. return 'none';
  303. let identificator = this.itemData(this._wornItems.bra);
  304. return identificator.vendor;
  305. }
  306. get brawornnumber(){
  307. if(!this._wornItems.bra)
  308. return 0;
  309. let identificator = this.itemData(this._wornItems.bra);
  310. return identificator.index;
  311. }
  312. get PBraMaterial(){return this.wornItemData('bra','material',0)}
  313. get PBraType(){return this.wornItemData('bra','subtype',0)}
  314. get PBraFun(){return this.wornItemData('bra','fun',0)}
  315. get PBraQuality(){return this.wornItemData('bra','quality',0)}
  316. get PBraThinness(){return this.wornItemData('bra','thinness',0)}
  317. get PBraCover(){return this.wornItemData('bra','cover',0)}
  318. get bra_appearanceBonus(){
  319. //Todo: Should be saved in const data
  320. switch(this.braworntype){
  321. case 'lusso': return 4;
  322. case 'fashionista': return 2;
  323. }
  324. return 0;
  325. }*/
  326. // ----- Panties -----
  327. /*
  328. get pantyworntype(){ //<<set $pantyworntype = $location_var[$here][1]>>
  329. if(!this._wornItems.panties)
  330. return 'none';
  331. let identificator = this.itemData(this._wornItems.panties);
  332. return identificator.vendor;
  333. }
  334. get pantywornnumber(){ //<<set $pantywornnumber = $location_var[$here][2]>>
  335. if(!this._wornItems.panties)
  336. return 0;
  337. let identificator = this.itemData(this._wornItems.panties);
  338. return identificator.index;
  339. }
  340. get PPanMaterial(){return this.wornItemData('panties','material',0)}
  341. get PPantyFun(){return this.wornItemData('panties','fun',0)}
  342. get PPanQuality(){return this.wornItemData('panties','quality',0)}
  343. get PPanThinness(){return this.wornItemData('panties','thinness',0)}
  344. get PPanCoverFront(){return this.wornItemData('panties','coverFront',0)}
  345. get PPanCoverBack(){return this.wornItemData('panties','coverBack',0)}
  346. get PPanType(){return this.wornItemData('panties','subtype',0)}
  347. get panties_appearanceBonus(){
  348. //Todo: Should be saved in const data
  349. switch(this.pantyworntype){
  350. case 'lusso': return 4;
  351. case 'fashionista': return 2;
  352. }
  353. return 0;
  354. }*/
  355. // ----- Shoes -----
  356. /**
  357. * Is the character currently wearing shoes? They could be taken manually or autoatically, because the character is inside.
  358. * @type {boolean}
  359. */
  360. get isWearingShoes(){
  361. //return !(!this._wornItems.shoes);
  362. if(!this.shoes.isValidItem)
  363. return false;
  364. if(!State.variables.settings.shoes_keepOnPrivateIndoors && !State.variables.location.isPublic && !State.variables.location.isOutdoors)
  365. return false;
  366. return true;
  367. }
  368. itemImage(itemId:string):string{
  369. return OutfitItem.get(itemId).image;
  370. }
  371. /*get shoeworntype(){ //<<set $pantyworntype = $location_var[$here][1]>>
  372. if(!this._wornItems.shoes)
  373. return 'none';
  374. let identificator = this.itemData(this._wornItems.shoes);
  375. return identificator.vendor;
  376. }
  377. get shoewornnumber(){ //<<set $pantywornnumber = $location_var[$here][2]>>
  378. if(!this._wornItems.shoes)
  379. return 0;
  380. let identificator = this.itemData(this._wornItems.shoes);
  381. return identificator.index;
  382. }
  383. get PShoQuaility(){
  384. if(this.isWearingShoes)
  385. return this.wornItemData('shoes','quality',0)
  386. return 0;
  387. }
  388. get PShoCut(){
  389. if(this.isWearingShoes)
  390. return this.wornItemData('shoes','cut',0)
  391. return 0;
  392. }
  393. get PShoHeels(){
  394. if(this.isWearingShoes)
  395. return this.wornItemData('shoes','heels',0)
  396. return 0;
  397. }
  398. get PShoStyle(){
  399. if(this.isWearingShoes)
  400. return this.wornItemData('shoes','style',0)
  401. return 0;
  402. }*/
  403. /**
  404. * Return heel height of worn shoes in mm.
  405. * @readonly
  406. * @type {(0 | 60 | 100 | 150 | 200 | 240 | 280 | 300)}
  407. */
  408. /*get shoesHeight(){
  409. switch(this.PShoHeels){
  410. case 0: return 0;
  411. case 1: return 0;
  412. case 2: return 60;
  413. case 3: return 100;
  414. case 4: return 150;
  415. case 5: return 200;
  416. case 6: return 240;
  417. case 7: return 280;
  418. default: return 300;
  419. }
  420. }*/
  421. /**
  422. * Appearance Bonus of worn shoes.
  423. * @date 6/16/2023 - 7:05:32 AM
  424. *
  425. * @readonly
  426. * @type {(25 | 50 | 100 | 150 | 200 | 300 | 400 | 0)}
  427. */
  428. /*get PXShoHeels(){
  429. let PShoHeels = this.PShoHeels;
  430. if(PShoHeels > 0){
  431. switch(PShoHeels){
  432. case 1: return 25;
  433. case 2: return 50;
  434. case 3: return 100;
  435. case 4: return 150;
  436. case 5: return 200;
  437. case 6: return 300;
  438. case 7: return 400;
  439. }
  440. if(PShoHeels > 7)
  441. return 400;
  442. }
  443. return 0;
  444. }*/
  445. /*get shoes_appearanceBonus(){
  446. return this.PShoQuaility;
  447. }*/
  448. // ----- Coats -----
  449. /*get PCoatWarm(){return this.wornItemData('coat','warm',0)}
  450. get PCoatQuality(){return this.wornItemData('coat','quality',0)}
  451. get coat_appearanceBonus(){
  452. let location = State.variables.location;
  453. if(location.isOutdoors)
  454. return this.PCoatQuality - 2;
  455. return 0;
  456. }*/
  457. // ----- Purses -----
  458. /*get purseEquipped(){
  459. return (this._wornItems.purse) ? true : false;
  460. }
  461. */
  462. get outfitQuality(){
  463. if(this.isWearingShoes)
  464. return Math.floor((this.clothes.quality * 2 + this.shoes.quality) / 3);
  465. return this.clothes.quality;
  466. }
  467. get outfitIndecency(){
  468. return setup.func('outfitIndecency');
  469. }
  470. /**
  471. * Low-Level adds an item to the wardrobe.
  472. * @param {string} itemId
  473. * @param {{}} [metadata={}]
  474. */
  475. add(itemId:string,metadata: {}={}){
  476. let constData = this.itemConstantData(itemId);
  477. let type_defaults = setup.outfitsDefaults[constData.type] || {};
  478. let vendor_defaults = setup.outfitsDefaults[constData.vendor] || {};
  479. metadata = Object.assign({},type_defaults,vendor_defaults,metadata);
  480. let iterateObject = clone(metadata);
  481. for (const [key, value] of Object.entries(iterateObject)) {
  482. if(typeof value === "string" && value.startsWith('$'))
  483. metadata[key] = State.getVar(value);
  484. }
  485. this._ownedItems[itemId] = metadata;
  486. console.log("ITEM ADDED",itemId,metadata);
  487. }
  488. /**
  489. * Low-Level removes an item from the wardrobe.
  490. * @param {string} itemId
  491. */
  492. delete(itemId:string){
  493. delete this._ownedItems[itemId];
  494. }
  495. deleteOutfit(outfitId:string){
  496. return delete this.outfits[outfitId];
  497. }
  498. dispose(itemId:string){
  499. this.delete(itemId);
  500. }
  501. disposeWorn(type:string){
  502. let itemId = this._wornItems[type];
  503. if(itemId){
  504. this.strip(type);
  505. this.dispose(itemId);
  506. }
  507. }
  508. /**
  509. * All defined items.
  510. */
  511. get allItems(): {[key: string]: OutfitDefinition}{
  512. return setup.outfits;
  513. }
  514. allItemsFiltered(filter:Filter){
  515. return setup.filterDict(this.allItems,filter);
  516. }
  517. allItemIdsFiltered(filter:Filter,count=undefined,randomOrder=false){
  518. let itemIds = Object.keys(this.allItemsFiltered(filter));
  519. if(randomOrder)
  520. itemIds.shuffle();
  521. if(count)
  522. itemIds = itemIds.slice(0,count);
  523. return itemIds;
  524. }
  525. /**
  526. * Returns all items (owned or not) that match filters.
  527. * @param {*} filters
  528. * @returns
  529. */
  530. /*getItems(filters={}):{[key:string]:OutfitDefinition}{
  531. let items:{[key:string]:OutfitDefinition} = this.filterItems(this.allItems,filters);
  532. return items;
  533. }*/
  534. /*getItemIds(filters={},count=undefined,randomOrder=false){
  535. let itemIds = Object.keys(this.getItems(filters))
  536. if(randomOrder)
  537. itemIds.shuffle();
  538. if(count)
  539. itemIds = itemIds.slice(0,count);
  540. return itemIds;
  541. }*/
  542. /*filterItems(items:{[key:string]:OutfitDefinition},filters:Filter):{[key:string]:OutfitDefinition}{
  543. return setup.filterDict(items,filters);
  544. }*/
  545. // Gets a list of owned items
  546. /*getList(filters={}){
  547. let results = [];
  548. itemloop: for (const [item_id, item_data] of Object.entries(this._ownedItems)) {
  549. let itemData = this.get(item_id);
  550. let vendor = itemData.vendor;
  551. let type = itemData.type;
  552. let subtype = itemData.subtype;
  553. let index = itemData.index;
  554. filterloop: for (const [filter_key, filter_value] of Object.entries(filters)) {
  555. if(filter_value === null || filter_value === undefined)
  556. continue filterloop;
  557. if(filter_key == 'type'){
  558. if(type != filter_value)
  559. continue itemloop;
  560. continue filterloop;
  561. }else if(filter_key == 'vendor'){
  562. if(vendor != filter_value)
  563. continue itemloop;
  564. continue filterloop;
  565. }else if(filter_key == 'subtype'){
  566. if(subtype != filter_value)
  567. continue itemloop;
  568. continue filterloop;
  569. }else if(filter_key == 'index'){
  570. if(index != filter_value)
  571. continue itemloop;
  572. continue filterloop;
  573. }else if(filter_key == 'location'){
  574. if(Array.isArray(filter_value)){
  575. if(!filter_value.includes(itemData[filter_key]))
  576. continue itemloop;
  577. continue filterloop;
  578. }
  579. if(filter_value == -1)
  580. continue filterloop;
  581. if(filter_value == 0){
  582. if(!("location" in itemData) || itemData.location == 0)
  583. continue filterloop;
  584. continue itemloop;
  585. }
  586. if(!("location" in itemData))
  587. continue itemloop;
  588. if(itemData.location != filter_value)
  589. continue itemloop;
  590. continue filterloop;
  591. }else{
  592. if(Array.isArray(filter_value)){
  593. if(!filter_value.includes(itemData[filter_key] || 0))
  594. continue itemloop;
  595. continue filterloop;
  596. }else if(['string','number'].includes(typeof filter_value)){
  597. if(itemData[filter_key] != filter_value)
  598. continue itemloop;
  599. continue filterloop;
  600. }else{
  601. console.warn('Unreckognized filter format in $wardrobe.getlist():',filter_value);
  602. }
  603. }
  604. }
  605. let result = {
  606. id: item_id,
  607. type: type,
  608. vendor: vendor,
  609. subtype: subtype,
  610. index: index,
  611. image: this.itemImage(item_id),
  612. location: ("l" in item_data) ? item_data.l : 0,
  613. health: ("h" in item_data) ? item_data.h : -1,
  614. }
  615. results.push(result);
  616. }
  617. console.log("wardrobe.getList",filters,results);
  618. return results;
  619. }*/
  620. has(itemId){
  621. return (itemId in this._ownedItems);
  622. }
  623. inhibition(itemId:string){
  624. return OutfitItem.get(itemId).inhibition;
  625. }
  626. isWearing(itemId:string):boolean{
  627. for(const wornItemId of Object.values(this._wornItems)){
  628. if(itemId == wornItemId)
  629. return true;
  630. }
  631. return false;
  632. }
  633. isWeargingItemAtSlot(type){
  634. return(!(this._wornItems[type] == null));
  635. }
  636. /**
  637. * @deprecated
  638. * @param {string} itemId
  639. * @returns {OutfitDefinition}
  640. */
  641. itemConstantData(itemId:string):OutfitDefinition{
  642. if(!setup.outfits[itemId])
  643. return null;
  644. let raw = setup.outfits[itemId];
  645. return raw;
  646. }
  647. /**
  648. * Return the constant data of an item
  649. * @deprecated
  650. * @param {string} itemId
  651. * @returns
  652. */
  653. itemData(itemId:string):OutfitDefinition{
  654. let constantData = this.itemConstantData(itemId);
  655. return constantData;
  656. }
  657. /*itemImage(itemId){
  658. let identificator = this.itemData(itemId);
  659. let template = setup.outfitItemImagePath[identificator.type];
  660. if(!template)
  661. console.error("ERROR: template not found",identificator);
  662. if(typeof template === "string")
  663. return template.formatUnicorn({vendor:identificator.vendor,id:identificator.index});
  664. if(!template[identificator.vendor]?.[identificator.subtype])
  665. console.error("ERROR: item-image-template is undefined",identificator,template[identificator.vendor]);
  666. return template[identificator.vendor][identificator.subtype].formatUnicorn({vendor:identificator.vendor,id:identificator.index});
  667. }*/
  668. /*
  669. itemImageAtSlot(slotId){
  670. return this.itemImage(this._wornItems[slotId]);
  671. }*/
  672. itemPrice(itemId,baseprice,pricemod=0){
  673. let itemData = this.itemData(itemId);
  674. let type = itemData.type;
  675. if(type != 'shoes' && type != 'clothes')
  676. return baseprice;
  677. let quality = itemData.quality;
  678. let price_raw = (baseprice * ((5 * quality) + 100) / 100) * 1000 / (1250 - pricemod) * 3 / 2;
  679. let price_rounded = Math.round(price_raw / 50) * 50;
  680. return price_rounded;
  681. }
  682. lastWornSet(key){
  683. this._lastWorn[key] = clone(this._wornItems);
  684. }
  685. //#region Outfit
  686. outfitAdd(key, outfitData=null){
  687. if(!this._outfits)
  688. this._outfits = {};
  689. if(outfitData)
  690. this._outfits[key] = clone(outfitData);
  691. else
  692. this._outfits[key] = clone(this._wornItems);
  693. }
  694. outfitIsWearable(key){
  695. if(!(key in this._outfits)){
  696. return false;
  697. }
  698. let slots = ['bra','clothes','panties','shoes'];
  699. for(let slot of slots){
  700. let wearable = this.wearable(this._outfits[key][slot]);
  701. if(!wearable.total){
  702. return false;
  703. }
  704. }
  705. return true;
  706. }
  707. //#endregion
  708. get isWearingSwimwear():boolean{
  709. return (this.clothes.type == 'swimwear');
  710. }
  711. get ownsSwimmingItem():boolean{
  712. //return (this.getList({subtype:'bikini'}).length + this.getList({subtype:'swimsuit'}).length > 0) ? true : false;
  713. return (Object.keys(this.itemsFiltered({type:'swimwear'})).length > 0);
  714. }
  715. /**
  716. * Sends an item to the specified location.
  717. * Side effect: adds the item to the inventory if it isn't already in it.
  718. * @param {*} itemIdentification
  719. * @param {number} location
  720. */
  721. sendToLocation(itemId,location){
  722. let set = {l:location};
  723. this.setMetadata(itemId,set);
  724. }
  725. setMetadata(itemId,metadata){
  726. let temp = Object.assign({}, this._ownedItems[itemId], metadata);
  727. console.log("Item METADATA SET:",itemId,metadata,this._ownedItems[itemId],temp);
  728. this._ownedItems[itemId] = temp;
  729. }
  730. strip(type:string){
  731. let item:OutfitItem = this[type];
  732. if(!item.isValidItem)
  733. return;
  734. for(const slot of item.slots)
  735. this._wornItems[slot] = null;
  736. }
  737. stripAll(){
  738. this.strip('bra');
  739. this.strip('clothes');
  740. this.strip('panties');
  741. this.strip('shoes');
  742. }
  743. wearable(itemId:string){
  744. let result = {
  745. owned: true,
  746. health: true,
  747. inhibition: true,
  748. hips: true,
  749. various:[],
  750. total: true
  751. }
  752. var itemData:OutfitOwned;
  753. if(!this.has(itemId)){
  754. result.owned = false;
  755. result.health = false;
  756. result.hips = false;
  757. result.total = false;
  758. return result;
  759. }else{
  760. itemData = this.item(itemId);
  761. if(itemData.h == 0)
  762. result.health = false;
  763. if(itemData.bmi > 0){
  764. result.hips = !([EWearableBMI.TOO_LOSE,EWearableBMI.TOO_TIGHT].includes(this.wearableBMI(itemId)));
  765. }
  766. }
  767. if(!this.wearbleInhibition(itemId))
  768. result.inhibition = false;
  769. result.total = (result.owned && result.health && result.inhibition && result.hips);
  770. for(const wardrobeWearableFunction of Object.values(setup.wardrobeWearableFunctions)){
  771. let functionValue = wardrobeWearableFunction(itemData);
  772. if(functionValue){
  773. result.total = false;
  774. result.various.push(functionValue);
  775. }
  776. }
  777. return result;
  778. }
  779. wearableBMI(itemId:string):EWearableBMI{
  780. const itemData = this.item(itemId);
  781. if(itemData.bmi === undefined)
  782. return EWearableBMI.NOT_APPLICABLE;
  783. if(!itemData.bmi)
  784. return EWearableBMI.ANY;
  785. if(itemData.bmi < 0)
  786. return EWearableBMI.NOT_APPLICABLE;
  787. const pcbmi = State.variables.pc.bmi;
  788. if(itemData.bmi - pcbmi > setup.wardrobeSettings.bmiSizeMaxDifference)
  789. return EWearableBMI.TOO_TIGHT;
  790. if(pcbmi - itemData.bmi > setup.wardrobeSettings.bmiSizeMaxDifference)
  791. return EWearableBMI.TOO_LOSE;
  792. if(itemData.bmi - pcbmi > setup.wardrobeSettings.bmiSizeMaxDifference * 0.75)
  793. return EWearableBMI.ALMOST_TOO_TIGHT;
  794. if(pcbmi - itemData.bmi > setup.wardrobeSettings.bmiSizeMaxDifference * 0.75)
  795. return EWearableBMI.ALMOST_TOO_LOSE;
  796. return EWearableBMI.OKAY;
  797. }
  798. //Return 0 if inhibition blocks, 1 if exciting and 2 in every other case
  799. wearbleInhibition(itemId){
  800. const inhibition = this.inhibition(itemId);
  801. let playerInhib = State.variables.pc.pcs_inhib;
  802. if(inhibition <= playerInhib)
  803. return 2;
  804. if(inhibition - 10 <= playerInhib)
  805. return 1;
  806. return 0;
  807. }
  808. /**
  809. * Wear the specified item. Send it to the default wardrobe section. Adds the item to the inventory, if it isn't in it.
  810. * @param {*} itemIdentification
  811. * @param {boolean} [replace=true] replace what's currently worn? If false only adds the item to the inventory.
  812. */
  813. wear(itemId,replace=true):boolean{
  814. if(!this.has(itemId))
  815. this.add(itemId);
  816. //let itemType = this.itemData(itemId).type;
  817. if(!replace){
  818. let item = OutfitItem.get(itemId);
  819. for(const slot of item.slots){
  820. let itemAtSlot:OutfitItem = this[slot];
  821. if(itemAtSlot.isValidItem)
  822. return false;
  823. }
  824. }
  825. this.sendToLocation(itemId,0);
  826. console.log("WEARING NOW",itemId);
  827. this._wear(itemId);
  828. return true;
  829. }
  830. wearTemporary(itemId){
  831. let itemType = this.itemData(itemId).type;
  832. this.strip(itemType);
  833. console.log("WEARING NOW",itemId,itemType);
  834. this._wear(itemId);
  835. }
  836. /*wear_clothes_legacy(type,vendorAndSubtype,index){
  837. let parts = vendorAndSubtype.split('_',2);
  838. this.wear([type,parts[0],index,parts[1]]);
  839. }*/
  840. //Caution: Unlike wear, this will not change the location of the worn item.
  841. _wear(itemOrId:string|OutfitItem){
  842. let item:OutfitItem;
  843. if(typeof itemOrId == "string")
  844. item = OutfitItem.get(itemOrId);
  845. else
  846. item = itemOrId;
  847. for(const slot of item.slots){
  848. this.strip(slot);
  849. this._wornItems[slot] = item.id;
  850. this._lastWorn['last'][slot] = item.id;
  851. }
  852. /*let itemType = this.itemData(itemId).type;
  853. this._wornItems[itemType] = itemId;
  854. this._lastWorn['last'][itemType] = itemId;*/
  855. /*if(this.PCloBra == 1)
  856. this.strip('bra');
  857. */
  858. }
  859. /*wear_last(type){
  860. this._wear(this._lastWorn['last'][type]);
  861. }*/
  862. hasLastWorn(key){
  863. return !(!this._lastWorn[key]);
  864. }
  865. wearLastWorn(key,consume=false){
  866. if(!this._lastWorn[key]){
  867. console.error("The last item set with key "+key+" has not been set!");
  868. }
  869. this._wornItems = clone(this._lastWorn[key]);
  870. if(consume)
  871. delete this._lastWorn[key];
  872. }
  873. wearOutfit(key){
  874. this._wornItems = clone(this._outfits[key]);
  875. }
  876. wornItemData(type,index,def:any=0){
  877. if(!this._wornItems[type])
  878. return def;
  879. let id = this._wornItems[type];
  880. let data = this.itemData(id);
  881. let returnValue = data[index]
  882. if(returnValue == undefined || returnValue == null)
  883. return def;
  884. return returnValue;
  885. }
  886. _shopitems = {} //{shopid:{expireday:day,pricemod:pricemod,list:[]}}
  887. shopItemIds(shopid:string,filters,count=30,expireday=undefined):string[]
  888. {
  889. if(shopid in this._shopitems){
  890. if(this._shopitems[shopid].expireday >= State.variables.time.daystart){
  891. let cachedList = this._shopitems[shopid].list;
  892. console.warn('Returning cached list',cachedList);
  893. return cachedList;
  894. }
  895. }
  896. let pricemod = rand(0,500);
  897. expireday ??= State.variables.time.daystart;
  898. let entrys:string[] = this.allItemIdsFiltered(filters,count,true);
  899. entrys = entrys.filter(itemId=>!this.has(itemId));
  900. this.shopItemListSet(shopid,expireday,pricemod,entrys);
  901. return entrys;
  902. }
  903. shopItemList(shopid,filters,baseprice,count=30,expireday=null):{[itemId:string]:OutfitItem}{
  904. let result:{[itemId:string]:OutfitItem} = {};
  905. for(const itemId of this.shopItemIds(shopid,filters,count,expireday)){
  906. let item = OutfitItem.get(itemId);
  907. Object.assign(item,{id: itemId,price: this.itemPrice(itemId,baseprice)});
  908. result[itemId] = item;
  909. }
  910. return result;
  911. }
  912. shopItemListSet(shopid,expireday,pricemod,items){
  913. this._shopitems[shopid] = {
  914. expireday: expireday,
  915. pricemod:pricemod,
  916. list: items
  917. }
  918. }
  919. filters = {
  920. shop_cost:true,
  921. shop_owned:false,
  922. shop_inhib_toohigh:false,
  923. shop_inhib_high:true,
  924. shop_inhib_low:true
  925. };
  926. /*execute_every_1_hour(){
  927. }
  928. execute_every_1_day(){
  929. }*/
  930. _init(wardrobe){
  931. Object.keys(wardrobe).forEach(function (pn) {
  932. this[pn] = clone(wardrobe[pn]);
  933. }, this);
  934. return this;
  935. }
  936. clone = function () {
  937. return (new Wardrobe())._init(this);
  938. };
  939. toJSON = function () {
  940. var ownData = {};
  941. Object.keys(this).forEach(function (pn) {
  942. if(typeof this[pn] !== "function")
  943. ownData[pn] = clone(this[pn]);
  944. }, this);
  945. return JSON.reviveWrapper('(new Wardrobe())._init($ReviveData$)', ownData);
  946. };
  947. }
  948. window.Wardrobe = Wardrobe;
  949. setup.outfits = {};