1 import { EquipmentSlot } from "shared/inventory";
2 import { EquippedItemDetails } from "../../shared/equipped";
3 import { PlayerItem } from "../../shared/items";
4 import { capitalize } from "lodash";
5 import { ProgressBar } from "./components/progress-bar";
7 function icon(icon_name?: string): string {
8 const placeholder = 'https://placehold.co/64x64/af936c/6d5f4d';
9 const icon = icon_name ? `/assets/img/icons/equipment/${icon_name}` : placeholder;
14 function renderEquipmentPlacementGrid(items: EquippedItemDetails[]) {
16 const map: Record<EquipmentSlot, EquippedItemDetails> = items.filter(item => item.is_equipped).reduce((acc, item) => {
17 acc[item.equipment_slot] = item;
22 <table id="character-equipment-placement">
26 <td style="background-image: url('${icon(map.HEAD?.icon)}');" title="${map.HEAD ? map.HEAD.name : 'Empty'}">
27 ${map.HEAD ? (map.HEAD.icon ? '' : map.HEAD.name) : 'HEAD'}
29 <td style="background-image: url('${icon(map.ARMS?.icon)}');" title="${map.ARMS ? map.ARMS.name : 'Empty'}">
30 ${map.ARMS ? (map.ARMS.icon ? '' : map.ARMS.name) : 'ARMS'}
34 <td style="background-image: url('${icon(map.LEFT_HAND ? map.LEFT_HAND.icon : map.TWO_HANDED?.icon)}');" title="${map.LEFT_HAND ? map.LEFT_HAND.name : (map.TWO_HANDED ? map.TWO_HANDED.name : '')}">
35 ${map.LEFT_HAND ? (map.LEFT_HAND.icon ? '' : map.LEFT_HAND.name) : (map.TWO_HANDED ? (map.TWO_HANDED.icon ? '' : map.TWO_HANDED.name) : 'L_HAND')}
37 <td style="background-image: url('${icon(map.CHEST?.icon)}');" title="${map.CHEST ? map.CHEST.name : ''}">
38 ${map.CHEST ? (map.CHEST.icon ? '' : map.CHEST.name) : 'CHEST'}
40 <td style="background-image: url('${icon(map.RIGHT_HAND ? map.RIGHT_HAND.icon : map.TWO_HANDED?.icon)}');" title="${map.RIGHT_HAND ? map.RIGHT_HAND.name : (map.TWO_HANDED ? map.TWO_HANDED.name : '')}">
41 ${map.RIGHT_HAND ? (map.RIGHT_HAND.icon ? '' : map.RIGHT_HAND.name) : (map.TWO_HANDED ? (map.TWO_HANDED.icon ? '' : map.TWO_HANDED.name) : 'R_HAND')}
47 <td style="background-image: url('${icon(map.LEGS?.icon)}');" title="${map.LEGS ? map.LEGS.name : ''}">
48 ${map.LEGS ? (map.LEGS.icon ? '' : map.LEGS.name) : 'LEGS'}
59 function renderInventoryItems(items: PlayerItem[]): string {
60 return items.map(item => {
62 <div class="player-item" hx-get="/modal/items/${item.item_id}" hx-target="#modal-wrapper">
63 <img src="/assets/img/icons/items/${item.icon_name}" title="${item.name}">
64 <span class="amount">${item.amount.toLocaleString()}</span>
70 function renderRequirement(name: string, val: number | string, currentVal?: number): string {
71 let colorIndicator = '';
73 colorIndicator = currentVal >= val ? 'success' : 'error';
75 return `<span class="requirement-title">${name}</span>: <span class="requirement-value ${colorIndicator}">${val.toLocaleString()}</span>`;
78 function renderStatBoost(name: string, val: number | string): string {
79 let valSign: string = '';
80 if(typeof val === 'number') {
81 valSign = val > 0 ? '+' : '-';
83 return `<span class="requirement-title">${name}</span>: <span class="requirement-value ${typeof val === 'number' ? (val > 0 ? "success": "error") : ""}">${valSign}${val}</span>`;
86 function renderInventoryItem(item: EquippedItemDetails , action: (item: EquippedItemDetails) => string): string {
87 return `<div class="store-list">
88 <div class="inventory-icon" style="background-image: url('${icon(item.icon)}')">
91 <div class="name">${item.name}</div>
92 <div class="requirements">
93 ${item.requirements.level ? renderRequirement('LVL', item.requirements.level): ''}
94 ${item.requirements.strength ? renderRequirement('STR', item.requirements.strength): ''}
95 ${item.requirements.constitution ? renderRequirement('CON', item.requirements.constitution): ''}
96 ${item.requirements.dexterity ? renderRequirement('DEX', item.requirements.dexterity): ''}
97 ${item.requirements.intelligence ? renderRequirement('INT', item.requirements.intelligence): ''}
98 ${renderRequirement('PRF', item.profession)}
100 <div class="stat-mods">
101 ${item.boosts.defence ? renderStatBoost('DEF', item.boosts.defence) : ''}
102 ${item.boosts.strength ? renderStatBoost('STR', item.boosts.strength) : ''}
103 ${item.boosts.constitution ? renderStatBoost('CON', item.boosts.constitution) : ''}
104 ${item.boosts.dexterity ? renderStatBoost('DEX', item.boosts.dexterity) : ''}
105 ${item.boosts.intelligence ? renderStatBoost('INT', item.boosts.intelligence) : ''}
106 ${item.boosts.damage ? renderStatBoost('DMG', item.boosts.damage) : ''}
107 ${item.boosts.damage_mitigation ? renderStatBoost('MIT', item.boosts.damage_mitigation.toString())+'%' : ''}
108 ${ProgressBar(item.currentAp, item.maxAp, `dur-${item.item_id}`, {
109 startingColor: '#7be67b',
110 endingColor: '#7be67b',
111 displayPercent: false,
112 title: item.type === 'SPELL' ? 'Uses' : 'Durability'
115 ${item.hasOwnProperty('id') ? `<div>${item.cost.toLocaleString()}G</div>` : ''}
117 <div class="inventory-actions">
123 function renderInventorySection(inventory: EquippedItemDetails[]): string {
124 const used_slots = inventory.filter(i => i.is_equipped).map(i => i.equipment_slot);
126 return inventory.map(item => {
127 return renderInventoryItem(item, item => {
128 if(item.is_equipped) {
129 return `<button type="button" class="unequip-item red" hx-post="/player/unequip/${item.item_id}">Unequip</button>`;
132 if(['ANY_HAND', 'LEFT_HAND', 'RIGHT_HAND'].includes(item.equipment_slot)) {
133 const str: string[] = [
134 `<button type="button" class="equip-item" hx-post="/player/equip/${item.item_id}/LEFT_HAND">Equip L</button>`,
135 `<button type="button" class="equip-item" hx-post="/player/equip/${item.item_id}/RIGHT_HAND">Equip R</button>`
138 if(used_slots.includes('LEFT_HAND') && !used_slots.includes('RIGHT_HAND')) {
141 else if(used_slots.includes('RIGHT_HAND') && !used_slots.includes('LEFT_HAND')) {
144 else if(used_slots.includes('LEFT_HAND') && used_slots.includes('RIGHT_HAND')) {
150 return `<button type="button" class="equip-item" hx-post="/player/equip/${item.item_id}/${item.equipment_slot}">Equip</button>`;
158 export function renderInventoryPage(inventory: EquippedItemDetails[], items: PlayerItem[], requestedSlot: string = 'ARMOUR') {
159 const sectionedInventory: {ARMOUR: EquippedItemDetails[], WEAPON: EquippedItemDetails[], SPELL: EquippedItemDetails[], ITEMS: PlayerItem[]} = {
166 inventory.forEach(item => {
167 sectionedInventory[item.type].push(item);
169 items.forEach(item => {
170 sectionedInventory.ITEMS.push(item);
174 <div id="inventory-page" hx-target="section#inventory" hx-swap="true">
175 <div id="character-summary">
176 ${renderEquipmentPlacementGrid(inventory)}
178 <div id="inventory-section" class="filter-container">
179 <nav class="filter" id="inventory-section">
180 ${Object.keys(sectionedInventory).map((type) => {
181 return `<a href="#" data-filter="${type}" class="${type === requestedSlot ? 'active': ''}">${capitalize(type)}</a>`;
185 <div class="inventory-listing listing">
186 ${Object.keys(sectionedInventory).map((type) => {
187 return `<div class="filter-result inventory-${type} ${type === requestedSlot ? 'active': 'hidden'}" data-filter="${type}" id="filter_${type}">
188 ${type === 'ITEMS' ? renderInventoryItems(sectionedInventory[type]) : renderInventorySection(sectionedInventory[type])}