chore(release): 0.2.6
[risinglegends.git] / src / server / views / stores.ts
1 import { ShopEquipment } from "../../shared/inventory";
2 import { ShopItem, Item } from "../../shared/items";
3 import { capitalize } from "lodash";
4 import { Player } from "../../shared/player";
5
6 function renderStatBoost(name: string, val: number | string): string {
7   let valSign: string = '';
8   if(typeof val === 'number') {
9     valSign = val > 0 ? '+' : '-';
10   }
11   return `<span class="requirement-title">${name}</span>: <span class="requirement-value ${typeof val === 'number' ? (val > 0 ? "success": "error") : ""}">${valSign}${val}</span>`;
12 }
13
14 function renderRequirement(name: string, val: number | string, currentVal?: number): string {
15   let colorIndicator = '';
16   if(currentVal) {
17     colorIndicator = currentVal >= val ? 'success' : 'error';
18   }
19   return `<span class="requirement-title">${name}</span>: <span class="requirement-value ${colorIndicator}">${val.toLocaleString()}</span>`;
20 }
21
22 function renderShopItem(item: (ShopItem & Item), action: (item: (ShopItem & Item)) => string): string {
23     return `<div class="store-list">
24     <div>
25       <img src="/assets/img/icons/items/${item.icon_name}" title="${item.name}">
26     </div>
27     <div class="details">
28       <div class="name">${item.name}</div>
29       <div class="requirements">
30         ${item.description}
31       </div>
32       ${item.hasOwnProperty('id') ? `<div>${item.price_per_unit.toLocaleString()}G</div>` : ''}
33     </div>
34     <div class="store-actions">
35       ${action(item)}
36     </div>
37     </div>`;
38 }
39
40 export function renderEquipmentDetails(item: ShopEquipment, player: Player): string {
41   return `
42     <div class="details">
43       <div class="name">${item.name}${item.equipment_slot === 'TWO_HANDED' ? ' (2H)': ''}</div>
44       <div class="requirements">
45       ${item.requirements.level ? renderRequirement('LVL', item.requirements.level, player.level): ''}
46       ${item.requirements.strength ? renderRequirement('STR', item.requirements.strength, player.strength): ''}
47       ${item.requirements.constitution ? renderRequirement('CON', item.requirements.constitution, player.constitution): ''}
48       ${item.requirements.dexterity ? renderRequirement('DEX', item.requirements.dexterity, player.dexterity): ''}
49       ${item.requirements.intelligence ? renderRequirement('INT', item.requirements.intelligence, player.intelligence): ''}
50       ${renderRequirement('PRF', item.profession)}
51       </div>
52       <div class="stat-mods">
53       ${item.boosts.strength ? renderStatBoost('STR', item.boosts.strength) : ''}
54       ${item.boosts.constitution ? renderStatBoost('CON', item.boosts.constitution) : ''}
55       ${item.boosts.dexterity ? renderStatBoost('DEX', item.boosts.dexterity) : ''}
56       ${item.boosts.intelligence ? renderStatBoost('INT', item.boosts.intelligence) : ''}
57       ${item.boosts.damage ? renderStatBoost(item.affectedSkills.includes('restoration_magic') ? 'HP' : 'DMG', item.boosts.damage) : ''}
58       ${item.boosts.damage_mitigation ? renderStatBoost('MIT', item.boosts.damage_mitigation.toString())+'%' : ''}
59       ${['WEAPON','SPELL'].includes(item.type) ? '' : renderStatBoost('AP', item.maxAp.toString())}
60       </div>
61       ${item.hasOwnProperty('id') ? `<div>${item.cost.toLocaleString()}G</div>` : ''}
62     </div>
63 `
64
65 }
66
67 function renderShopEquipment(item: ShopEquipment, action: (item: ShopEquipment) => string, player: Player): string {
68     return `<div class="store-list">
69     <div>
70       <img src="${item.icon ? `/assets/img/icons/equipment/${item.icon}` : 'https://via.placeholder.com/64x64'}">
71     </div>
72     ${renderEquipmentDetails(item, player)}
73     <div class="store-actions">
74       ${action(item)}
75     </div>
76     </div>`;
77 }
78
79
80
81 export async function renderStore(equipment: ShopEquipment[], items: (ShopItem & Item)[], player: Player): Promise<string> {
82   const listing: Record<string, string> = {};
83   const listingTypes = new Set<string>();
84
85   equipment.forEach(item => {
86     const filter = item.type === 'ARMOUR' ? item.equipment_slot : item.type;
87
88     listingTypes.add(filter);
89     if(!listing[filter]) {
90       listing[filter] = '';
91     }
92
93     listing[filter] += renderShopEquipment(item, i => {
94       const id = `data-id="${i.id}"`;
95       return `<button type="button" hx-get="/location/${i.location_id}/equipment/${i.id}/overview" hx-target="#modal-wrapper">Buy</button>`
96     }, player);
97
98   });
99
100   if(items && items.length) {
101     listingTypes.add('ITEMS');
102     listing['ITEMS'] = '';
103     items.forEach(item => {
104       listing['ITEMS'] += renderShopItem(item, i => {
105         return `<button type="button" hx-get="/location/${i.location_id}/items/${i.id}/overview" hx-target="#modal-wrapper">Buy</button>`;
106       });
107     });
108   }
109
110   let activeTab: string = listingTypes.keys().next().value;
111
112   const nav: string[] = [];
113   const finalListing: string[] = [];
114
115   listingTypes.forEach(type => {
116     nav.push(`<a href="#" data-filter="${type}" class="${activeTab === type ? 'active': ''}">${capitalize(type)}</a>`);
117     finalListing.push(`<div class="filter-result ${activeTab === type ? 'active': 'hidden'}" data-filter="${type}" id="filter_${type}">${listing[type]}</div>`);
118   });
119
120   let html = `<div class="shop-inventory-listing filter-container">
121     <nav class="filter" id="shop-inventory-listing">${nav.join("")}</nav><div class="inventory-listing listing">
122       ${finalListing.join("\n")}
123     </div>
124   </div>`;
125
126   return html;
127 }