display: none !important;
}
+.right {
+ float: right;
+}
+
.flex {
display: flex;
justify-content: space-around;
line-height: 110%;
}
+.progress-bar.vertical {
+ height: 100%;
+ width: 10px;
+}
+
#map {
width: 75%;
margin: 0rem auto 1rem;
p {
margin: 1rem;
}
+}
+
+
+.inventory-listing {
+
+ .requirements {
+ display: none !important;
+ }
+
+ .requirement-hover {
+ cursor: help;
+ position: relative;
+ font-size: 0.8rem;
+ font-weight: normal;
+ }
+
+ .requirement-hover:hover+.requirements,
+ .requirements:hover {
+ padding: 0.5rem;
+ border-radius: 4px;
+ font-size: 0.8rem;
+ width: max-content;
+ max-width: 200px;
+ position: absolute;
+ background: #2d2d2d;
+ color: #fff;
+ display: grid !important;
+ top: 100%;
+ right: 0;
+ transition: opacity 0.2s;
+ z-index: 100;
+ }
+
+}
+
+.icon-durability-container {
+ position: relative;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ height: 64px;
+}
+
+.icon-durability-container .icon {
+ margin-right: 3px;
}
\ No newline at end of file
#explore .shop-inventory-listing {
margin: 2rem auto 1rem;
width: 90%;
+}
+
+.shop-inventory-listing {
+
+ .list-item:nth-child(even) .requirements {
+ background-color: #f5e6d3;
+ }
+
+ .requirements {
+ background-color: #faf3ea;
+
+ border-radius: 6px;
+ padding: 0.5rem;
+ margin-top: 0.5rem;
+
+ &:before {
+ content: 'REQUIREMENTS';
+ font-weight: bold;
+ display: block;
+ margin-bottom: 0.3rem;
+ color: #6d251c;
+ grid-column: 1 / -1;
+ }
+ }
}
\ No newline at end of file
display: flex;
text-align: left;
padding: 0.5rem;
+ overflow: hidden;
&:nth-child(even) {
background-color: #f2f0ec;
.icon-wrapper {
position: relative;
margin-right: 0.5rem;
+ width: 77px;
.icon {
width: 64px;
}
.actions {
- width: 64px;
margin-top: 0.2rem;
button {
padding: 0 0.4rem;
line-height: 1rem;
flex-grow: 2;
+ width: 100%;
&>div {
margin-bottom: 4px;
.stat-mods {
font-size: 0.8rem;
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
+ gap: 0.2rem;
}
.requirements {
font-size: 0.8rem;
-
- &::before {
- content: 'REQ: ';
- font-weight: bold;
- text-transform: uppercase;
- }
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
+ gap: 0.2rem;
.requirement-title {
font-weight: bold;
endingColor?: string;
title?: string
displayPercent?: boolean;
+ vertical?: boolean;
+ noContent?: boolean;
}
export function ProgressBar(current: number, max: number, id: string, opts: ProgressBarOptions) {
display.push(`${percent}%`);
}
- return `<div class="progress-bar" id="${id}" style="background: linear-gradient(to right, ${opts.startingColor}, ${endingColor} ${percent}%, transparent ${percent}%, transparent)"
-title="${title} ${display.join(" - ")}">${title} ${display.join(" - ")}</div>`;
+ return `<div class="progress-bar ${opts.vertical ? 'vertical' : ''}" id="${id}" style="background: linear-gradient(${opts.vertical ? 'to top' : 'to right'}, ${opts.startingColor}, ${endingColor} ${percent}%, transparent ${percent}%, transparent)"
+title="${title} ${display.join(" - ")}">${opts.noContent ? '' : `${title} ${display.join(" - ")}`}</div>`;
}
-import { EquipmentSlot, meetsRequirements } from "@shared/inventory";
+import { EquipmentSlot, InventoryItem, meetsRequirements, requirementMap } from "@shared/inventory";
import { EquippedItemDetails } from "@shared/equipped";
-import { PlayerItem } from "@shared/items";
+import { Item, PlayerItem } from "@shared/items";
import { capitalize } from "lodash";
import { ProgressBar } from "./components/progress-bar";
import { Player } from "@shared/player";
}).join("<br>");
}
+function renderRequirements(requirements: InventoryItem['requirements'], player: Player): string {
+ return `
+ <div class="requirements">
+ ${Object.entries(requirements).filter(([key, val]) => val !== 0).map(([key, val]) => {
+ return renderRequirement(key, val, player[key]);
+ }).join('')}
+ </div>
+ `;
+}
function renderRequirement(name: string, val: number | string, currentVal?: number): string {
let colorIndicator = '';
function renderInventoryItem(player: Player, item: EquippedItemDetails , action: (item: EquippedItemDetails) => string): string {
const itemLevel = levelFromExp(item.current_exp);
- const requirements = meetsRequirements(item, player);
+ const meetsAllRequirements = meetsRequirements(item, player);
return `<div class="list-item">
<div class="icon-wrapper">
- <div class="icon ${slugify(getDurabilityApproximation(item))}" style="background-image: url('${icon(item.icon)}')" title="Durability: ${item.currentAp}/${item.maxAp} (${Math.round((item.currentAp / item.maxAp) * 100)}%)">
+ <div class="icon-durability-container">
+ <div class="icon ${slugify(getDurabilityApproximation(item))}" style="background-image: url('${icon(item.icon)}')" title="Durability: ${item.currentAp}/${item.maxAp} (${Math.round((item.currentAp / item.maxAp) * 100)}%)">
+ </div>
+ ${ProgressBar(item.currentAp, item.maxAp, `durability-${item.item_id}`, {
+ startingColor: '#7be67b',
+ endingColor: '#7be67b',
+ title: 'Durability',
+ vertical: true,
+ noContent: true
+ })}
</div>
<div class="actions">
${action(item)}
</div>
</div>
<div class="details">
- <div class="name">${item.name} <span class="right">${item.profession} (<span class="requirement-hover">REQ</span>)</span></div>
+ <div class="name">${item.name} <span class="right" style="position: relative;">${item.profession} <span class="requirement-hover${meetsAllRequirements ? '' : ' error'}">(REQ)</span>${renderRequirements(item.requirements, player)}</span></div>
<div class="stat-mods">
${item.boosts.defence ? renderStatBoost('DEF', item.boosts.defence) : ''}
${item.boosts.strength ? renderStatBoost('STR', item.boosts.strength) : ''}
${item.boosts.damage ? renderStatBoostWithPlayerIncrease(player, item.affectedSkills.includes('restoration_magic') ? 'HP' : 'DMG', item) : ''}
${item.boosts.damage_mitigation ? renderStatBoost('MIT', item.boosts.damage_mitigation.toString(), true) : ''}
</div>
- <div class="requirements" title="Requirements">
- ${item.requirements.level ? renderRequirement('LVL', item.requirements.level): ''}
- ${item.requirements.strength ? renderRequirement('STR', item.requirements.strength): ''}
- ${item.requirements.constitution ? renderRequirement('CON', item.requirements.constitution): ''}
- ${item.requirements.dexterity ? renderRequirement('DEX', item.requirements.dexterity): ''}
- ${item.requirements.intelligence ? renderRequirement('INT', item.requirements.intelligence): ''}
- </div>
${ProgressBar(item.current_exp - expToLevel(itemLevel), expToLevel(itemLevel + 1) - expToLevel(itemLevel), `exp-${item.item_id}`, {
startingColor: '#7be67b',
endingColor: '#7be67b',
return inventory.map(item => {
return renderInventoryItem(player, item, item => {
+
+ const meetsAllRequirements = meetsRequirements(item, player);
if(item.is_equipped) {
return `<button type="button" class="unequip-item red" hx-post="/player/unequip/${item.item_id}">Unequip</button>`;
}
else {
+ if(!meetsAllRequirements) {
+ return `<button type="button" disabled="disabled" class="equip-item disabled">Locked</button>`;
+ }
+
if(['ANY_HAND', 'LEFT_HAND', 'RIGHT_HAND'].includes(item.equipment_slot)) {
const str: string[] = [
`<button type="button" class="equip-item" hx-post="/player/equip/${item.item_id}/LEFT_HAND">Equip L</button>`,
return `
<div class="details">
<div class="name">${item.name}${item.equipment_slot === 'TWO_HANDED' ? ' (2H)': ''}</div>
- <div class="requirements">
- ${item.requirements.level ? renderRequirement('LVL', item.requirements.level, player.level): ''}
- ${item.requirements.strength ? renderRequirement('STR', item.requirements.strength, player.strength): ''}
- ${item.requirements.constitution ? renderRequirement('CON', item.requirements.constitution, player.constitution): ''}
- ${item.requirements.dexterity ? renderRequirement('DEX', item.requirements.dexterity, player.dexterity): ''}
- ${item.requirements.intelligence ? renderRequirement('INT', item.requirements.intelligence, player.intelligence): ''}
- ${renderRequirement('PRF', item.profession)}
- </div>
<div class="stat-mods">
${item.boosts.defence ? renderStatBoost('DEF', item.boosts.defence) : ''}
${item.boosts.strength ? renderStatBoost('STR', item.boosts.strength) : ''}
${renderStat(isSpell ? 'Uses': 'Durability', isSpell ? 'Uses': 'DUR', item.maxAp, { unsigned: true })}
</div>
${item.hasOwnProperty('id') ? `<div class="store-cost">${item.cost.toLocaleString()}G</div>` : ''}
+ <div class="requirements">
+ ${item.requirements.level ? renderRequirement('LVL', item.requirements.level, player.level): ''}
+ ${item.requirements.strength ? renderRequirement('STR', item.requirements.strength, player.strength): ''}
+ ${item.requirements.constitution ? renderRequirement('CON', item.requirements.constitution, player.constitution): ''}
+ ${item.requirements.dexterity ? renderRequirement('DEX', item.requirements.dexterity, player.dexterity): ''}
+ ${item.requirements.intelligence ? renderRequirement('INT', item.requirements.intelligence, player.intelligence): ''}
+ ${renderRequirement('PRF', item.profession)}
+ </div>
</div>
`
+import { Player } from "./player";
import {Profession} from "./profession";
import {SkillID} from "./skills";
import { max } from 'lodash';
return "About to Break";
}
}
+
+export function requirementMap(item: InventoryItem, player: Player): Map<string, boolean> {
+ const requirements = new Map<string, boolean>();
+ Object.entries(item.requirements).forEach(([key, value]) => {
+ requirements.set(key, player[key] >= value);
+ });
+ return requirements;
+}
+
+export function meetsRequirements(item: InventoryItem, player: Player): boolean {
+ const requirements = requirementMap(item, player);
+ return Array.from(requirements.values()).every(val => val);
+}
\ No newline at end of file