chore(release): 0.2.5
[risinglegends.git] / src / server / views / inventory.ts
1 import { EquipmentSlot, InventoryType } from "shared/inventory";
2 import { EquippedItemDetails } from "../../shared/equipped";
3 import { PlayerItem } from "../../shared/items";
4 import { capitalize } from "lodash";
5
6 function renderEquipmentPlacementGrid(items: EquippedItemDetails[]) {
7   const placeholder = 'https://via.placeholder.com/64x64';
8   // @ts-ignore
9   const map: Record<EquipmentSlot, EquippedItemDetails> = items.filter(item => item.is_equipped).reduce((acc, item) => {
10     acc[item.equipment_slot] = item;
11     return acc;
12   }, {});
13
14   const html = `
15 <table id="character-equipment-placement">
16 <tr>
17 <td>
18 </td>
19 <td style="background-image: url('${placeholder}');" title="${map.HEAD ? map.HEAD.name : 'Empty'}">
20 ${map.HEAD ? map.HEAD.name : 'HEAD'}
21 </td>
22 <td style="background-image: url('${placeholder}');" title="${map.ARMS ? map.ARMS.name : 'Empty'}">
23 ${map.ARMS ? map.ARMS.name : 'ARMS'}
24 </td>
25 </tr>
26 <tr>
27 <td style="background-image: url('${placeholder}');" title="${map.LEFT_HAND ? map.LEFT_HAND.name : (map.TWO_HANDED ? map.TWO_HANDED.name : '')}">
28 ${map.LEFT_HAND ? map.LEFT_HAND.name : (map.TWO_HANDED ? map.TWO_HANDED.name : 'L_HAND')}
29 </td>
30 <td style="background-image: url('${placeholder}');" title="${map.CHEST ? map.CHEST.name : ''}">
31 ${map.CHEST ? map.CHEST.name : 'CHEST'}
32 </td>
33 <td style="background-image: url('${placeholder}');" title="${map.RIGHT_HAND ? map.RIGHT_HAND.name : (map.TWO_HANDED ? map.TWO_HANDED.name : '')}">
34 ${map.RIGHT_HAND ? map.RIGHT_HAND.name : (map.TWO_HANDED ? map.TWO_HANDED.name : 'R_HAND')}
35 </td>
36 </tr>
37 <tr>
38 <td>
39 </td>
40 <td style="background-image: url('${placeholder}');" title="${map.LEGS ? map.LEGS.name : ''}">
41 ${map.LEGS ? map.LEGS.name : 'LEGS'}
42 </td>
43 <td>
44 </td>
45 </tr>
46 </table>
47 `;
48
49   return html;
50 }
51
52 function renderInventoryItems(items: PlayerItem[]): string {
53   return items.map(item => {
54     return `
55 <div class="player-item" hx-get="/modal/items/${item.item_id}" hx-target="#modal-wrapper">
56 <img src="/assets/img/icons/items/${item.icon_name}" title="${item.name}">
57 <span class="amount">${item.amount.toLocaleString()}</span>
58 </div>`;
59   }).join("<br>");
60 }
61
62
63 function renderRequirement(name: string, val: number | string, currentVal?: number): string {
64   let colorIndicator = '';
65   if(currentVal) {
66     colorIndicator = currentVal >= val ? 'success' : 'error';
67   }
68   return `<span class="requirement-title">${name}</span>: <span class="requirement-value ${colorIndicator}">${val.toLocaleString()}</span>`;
69 }
70
71 function renderStatBoost(name: string, val: number | string): string {
72   let valSign: string = '';
73   if(typeof val === 'number') {
74     valSign = val > 0 ? '+' : '-';
75   }
76   return `<span class="requirement-title">${name}</span>: <span class="requirement-value ${typeof val === 'number' ? (val > 0 ? "success": "error") : ""}">${valSign}${val}</span>`;
77 }
78
79 function generateProgressBar(current: number, max: number, color: string, displayPercent: boolean = true): string {
80   let percent = 0;
81   if(max > 0) {
82     percent = Math.floor((current / max) * 100);
83   }
84   const display = `${displayPercent? `${percent}% - `: ''}`;
85   return `<div class="progress-bar" style="background: linear-gradient(to right, ${color}, ${color} ${percent}%, transparent ${percent}%, transparent)" title="${display}${current}/${max}">${display}${current}/${max}</div>`;
86 }
87
88 function renderInventoryItem(item: EquippedItemDetails , action: (item: EquippedItemDetails) => string): string {
89   return `<div class="store-list">
90     <div>
91       <img src="https://via.placeholder.com/64x64">
92     </div>
93     <div class="details">
94       <div class="name">${item.name}</div>
95       <div class="requirements">
96         ${item.requirements.level ? renderRequirement('LVL', item.requirements.level): ''}
97         ${item.requirements.strength ? renderRequirement('STR', item.requirements.strength): ''}
98         ${item.requirements.constitution ? renderRequirement('CON', item.requirements.constitution): ''}
99         ${item.requirements.dexterity ? renderRequirement('DEX', item.requirements.dexterity): ''}
100         ${item.requirements.intelligence ? renderRequirement('INT', item.requirements.intelligence): ''}
101         ${renderRequirement('PRF', item.profession)}
102       </div>
103       <div class="stat-mods">
104         ${item.boosts.strength ? renderStatBoost('STR', item.boosts.strength) : ''}
105         ${item.boosts.constitution ? renderStatBoost('CON', item.boosts.constitution) : ''}
106         ${item.boosts.dexterity ? renderStatBoost('DEX', item.boosts.dexterity) : ''}
107         ${item.boosts.intelligence ? renderStatBoost('INT', item.boosts.intelligence) : ''}
108         ${item.boosts.damage ? renderStatBoost('DMG', item.boosts.damage) : ''}
109         ${item.boosts.damage_mitigation ? renderStatBoost('MIT', item.boosts.damage_mitigation.toString())+'%' : ''}
110         ${['WEAPON','SPELL'].includes(item.type) ? '': generateProgressBar(item.currentAp, item.maxAp, '#7be67b')}
111       </div>
112       ${item.hasOwnProperty('id') ? `<div>${item.cost.toLocaleString()}G</div>` : ''}
113       </div>
114       <div class="store-actions">
115         ${action(item)}
116       </div>
117     </div>`;
118 }
119
120 function renderInventorySection(inventory: EquippedItemDetails[]): string {
121   return inventory.map(item => {
122     return renderInventoryItem(item, item => {
123       if(item.is_equipped) {
124         return `<button type="button" class="unequip-item error" hx-post="/player/unequip/${item.item_id}">Unequip</button>`;
125       }
126       else {
127         if(item.equipment_slot === 'ANY_HAND') {
128           return `<button type="button" class="equip-item" hx-post="/player/equip/${item.item_id}/LEFT_HAND">Equip L</button>
129 <button type="button" class="equip-item" hx-post="/player/equip/${item.item_id}/RIGHT_HAND">Equip R</button>`;
130         }
131         else if(item.equipment_slot === 'LEFT_HAND') {
132           return `<button type="button" class="equip-item" hx-post="/player/equip/${item.item_id}/${item.equipment_slot}">Equip Left</button>`;
133         }
134         else if(item.equipment_slot === 'RIGHT_HAND') {
135           return `<button type="button" class="equip-item" hx-post="/player/equip/${item.item_id}/${item.equipment_slot}">Equip Right</button>`;
136         }
137         else {
138           return `<button type="button" class="equip-item" hx-post="/player/equip/${item.item_id}/${item.equipment_slot}">Equip</button>`;
139         }
140       }
141     })
142   }).join("\n");
143 }
144
145
146 export function renderInventoryPage(inventory: EquippedItemDetails[], items: PlayerItem[], requestedSlot: string = 'ARMOUR') {
147   const sectionedInventory: {ARMOUR: EquippedItemDetails[], WEAPON: EquippedItemDetails[], SPELL: EquippedItemDetails[], ITEMS: PlayerItem[]} = {
148     ARMOUR: [],
149     WEAPON: [],
150     SPELL: [],
151     ITEMS: []
152   };
153
154   inventory.forEach(item => {
155     sectionedInventory[item.type].push(item);
156   });
157   items.forEach(item => {
158     sectionedInventory.ITEMS.push(item);
159   });
160
161   const html = `
162   <div id="inventory-page" hx-target="section#inventory" hx-swap="true">
163     <div id="character-summary">
164       ${renderEquipmentPlacementGrid(inventory)}
165     </div>
166     <div id="inventory-section" class="filter-container">
167       <nav class="filter" id="inventory-section">
168         ${Object.keys(sectionedInventory).map((type) => {
169           return `<a href="#" data-filter="${type}" class="${type === requestedSlot ? 'active': ''}">${capitalize(type)}</a>`;
170         }).join("")}
171       </nav>
172
173       <div class="inventory-listing listing">
174         ${Object.keys(sectionedInventory).map((type) => {
175           return `<div class="filter-result inventory-${type} ${type === requestedSlot ? 'active': 'hidden'}" data-filter="${type}" id="filter_${type}">
176           ${type === 'ITEMS' ? renderInventoryItems(sectionedInventory[type]) : renderInventorySection(sectionedInventory[type])}
177           </div>`;
178         }).join("")}
179       </div>
180     </div>
181   </div>
182   `;
183
184   return html;
185
186 }