chore(release): 0.3.1
[risinglegends.git] / src / server / views / repair.ts
1 import { capitalize } from "lodash";
2 import { Player } from "../../shared/player";
3 import { repairCost } from "../../shared/inventory";
4 import { LocationWithCity } from "../../shared/map";
5 import { EquippedItemDetails } from "../../shared/equipped";
6 import { ProgressBar } from "./components/progress-bar";
7 import * as City from './components/city';
8 import { BackToTown } from "./components/button";
9
10 function renderStatBoost(name: string, val: number | string): string {
11   let valSign: string = '';
12   if(typeof val === 'number') {
13     valSign = val > 0 ? '+' : '-';
14   }
15   return `<span class="requirement-title">${name}</span>: <span class="requirement-value ${typeof val === 'number' ? (val > 0 ? "success": "error") : ""}">${valSign}${val}</span>`;
16 }
17
18 function renderRequirement(name: string, val: number | string, currentVal?: number): string {
19   let colorIndicator = '';
20   if(currentVal) {
21     colorIndicator = currentVal >= val ? 'success' : 'error';
22   }
23   return `<span class="requirement-title">${name}</span>: <span class="requirement-value ${colorIndicator}">${val.toLocaleString()}</span>`;
24 }
25
26 export function renderEquipmentDetails(item: EquippedItemDetails, player: Player): string {
27   return `
28     <div class="details">
29       <div class="name">${item.name}${item.equipment_slot === 'TWO_HANDED' ? ' (2H)': ''}</div>
30       <div class="requirements">
31       ${item.requirements.level ? renderRequirement('LVL', item.requirements.level, player.level): ''}
32       ${item.requirements.strength ? renderRequirement('STR', item.requirements.strength, player.strength): ''}
33       ${item.requirements.constitution ? renderRequirement('CON', item.requirements.constitution, player.constitution): ''}
34       ${item.requirements.dexterity ? renderRequirement('DEX', item.requirements.dexterity, player.dexterity): ''}
35       ${item.requirements.intelligence ? renderRequirement('INT', item.requirements.intelligence, player.intelligence): ''}
36       ${renderRequirement('PRF', item.profession)}
37       </div>
38       <div class="stat-mods">
39       ${item.boosts.defence ? renderStatBoost('DEF', item.boosts.defence) : ''}
40       ${item.boosts.strength ? renderStatBoost('STR', item.boosts.strength) : ''}
41       ${item.boosts.constitution ? renderStatBoost('CON', item.boosts.constitution) : ''}
42       ${item.boosts.dexterity ? renderStatBoost('DEX', item.boosts.dexterity) : ''}
43       ${item.boosts.intelligence ? renderStatBoost('INT', item.boosts.intelligence) : ''}
44       ${item.boosts.damage ? renderStatBoost(item.affectedSkills.includes('restoration_magic') ? 'HP' : 'DMG', item.boosts.damage) : ''}
45       ${item.boosts.damage_mitigation ? renderStatBoost('MIT', item.boosts.damage_mitigation.toString())+'%' : ''}
46       ${ProgressBar(item.currentAp, item.maxAp, `${item.item_id}-ap`, {
47 displayPercent: false,
48 title: item.type === 'SPELL' ? 'Uses' : 'Durability',
49 startingColor: '#7be67b',
50 endingColor: '#7be67b'
51 })}
52       </div>
53       <div class="store-cost">${repairCost(item).toLocaleString()}G to Repair</div>
54     </div>
55 `
56
57 }
58
59 function renderEquipmentToRepair(item: EquippedItemDetails, action: (item: EquippedItemDetails) => string, player: Player): string {
60     return `<div class="store-list">
61     <div class="store-icon" style="background-image: url('${item.icon ? `/assets/img/icons/equipment/${item.icon}` : 'https://via.placeholder.com/64x64'}')">
62       <div class="store-actions">${action(item)}</div>
63     </div>
64     ${renderEquipmentDetails(item, player)}
65     </div>`;
66 }
67
68
69
70 export function renderRepairService(equipment: EquippedItemDetails[], player: Player, location: LocationWithCity): string {
71   const listing: Record<string, string> = {};
72   const listingTypes = new Set<string>();
73
74   if(equipment.length === 0) {
75     return `
76       ${City.Title(location.city_name)}
77       ${City.Details(location.name, `You don't have any equipment that needs repairing.`)}
78     `;
79   }
80
81   equipment.forEach(item => {
82     if(item.maxAp <= 0) {
83       return;
84     }
85     const filter = item.type === 'ARMOUR' ? item.equipment_slot : item.type;
86
87     listingTypes.add(filter);
88     if(!listing[filter]) {
89       listing[filter] = '';
90     }
91
92     listing[filter] += renderEquipmentToRepair(item, i => {
93       return `<button type="button" hx-post="/city/services/${location.id}/repair/${i.item_id}" hx-target="#explore">Repair</button>`
94     }, player);
95
96   });
97
98   let activeTab: string = listingTypes.keys().next().value;
99
100   const nav: string[] = [];
101   const finalListing: string[] = [];
102
103   listingTypes.forEach(type => {
104     nav.push(`<a href="#" data-filter="${type}" class="${activeTab === type ? 'active': ''}">${capitalize(type)}</a>`);
105     finalListing.push(`<div class="filter-result ${activeTab === type ? 'active': 'hidden'}" data-filter="${type}" id="filter_${type}">${listing[type]}</div>`);
106   });
107
108   let html = `
109     ${City.Title(location.city_name)}
110     ${City.Details(location.name, `
111 <div class="shop-inventory-listing filter-container">
112     <nav class="filter" id="shop-inventory-listing">${nav.join("")}</nav><div class="inventory-listing listing">
113       ${finalListing.join("\n")}
114     </div>
115   </div>
116   ${BackToTown()}
117 `)}`;
118
119   return html;
120 }