Fix score-based ordering in suitbuilder frontend
Updated JavaScript to maintain score ordering during streaming search: - Replace addSuitToResults() with insertSuitInScoreOrder() - Add regenerateResultsDisplay() to maintain proper DOM ordering - Medal assignment (🥇🥈🥉) now based on score ranking, not arrival order - Suits with highest scores now always appear at top during live search - Updated displaySuitResults() to sort by score before displaying 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
4d19e29847
commit
e7ca39318f
1 changed files with 306 additions and 209 deletions
|
|
@ -1,7 +1,7 @@
|
|||
// Suitbuilder JavaScript - Constraint Solver Frontend Logic
|
||||
|
||||
// Configuration
|
||||
const API_BASE = '/inv';
|
||||
const API_BASE = '/inv/suitbuilder';
|
||||
let currentSuits = [];
|
||||
let lockedSlots = new Set();
|
||||
let selectedSuit = null;
|
||||
|
|
@ -25,7 +25,7 @@ function initializeSuitbuilder() {
|
|||
*/
|
||||
async function loadCharacters() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/characters/list`);
|
||||
const response = await fetch(`${API_BASE}/characters`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load characters');
|
||||
}
|
||||
|
|
@ -52,11 +52,13 @@ function displayCharacters(characters) {
|
|||
|
||||
let html = '';
|
||||
characters.forEach(character => {
|
||||
// Sanitize character name for HTML ID (replace special chars with underscores)
|
||||
const safeId = character.replace(/[^a-zA-Z0-9]/g, '_');
|
||||
html += `
|
||||
<div class="checkbox-item">
|
||||
<input type="checkbox" id="char_${character.character_name}"
|
||||
class="character-checkbox" value="${character.character_name}" checked>
|
||||
<label for="char_${character.character_name}">${character.character_name}</label>
|
||||
<input type="checkbox" id="char_${safeId}"
|
||||
class="character-checkbox" value="${character}" checked>
|
||||
<label for="char_${safeId}">${character}</label>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
|
@ -234,66 +236,181 @@ function validateConstraints(constraints) {
|
|||
* Stream optimal suits using Server-Sent Events with progressive results
|
||||
*/
|
||||
async function streamOptimalSuits(constraints) {
|
||||
// Build request parameters for the streaming constraint solver
|
||||
const params = new URLSearchParams();
|
||||
// Prepare constraint data for POST request
|
||||
const requestBody = {
|
||||
characters: constraints.characters.length > 0 ? constraints.characters : [],
|
||||
primary_set: constraints.primary_set ? parseInt(constraints.primary_set) : null,
|
||||
secondary_set: constraints.secondary_set ? parseInt(constraints.secondary_set) : null,
|
||||
required_spells: [
|
||||
...constraints.legendary_cantrips,
|
||||
...constraints.protection_spells
|
||||
],
|
||||
locked_items: {}, // TODO: implement locked items
|
||||
include_equipped: constraints.include_equipped,
|
||||
include_inventory: constraints.include_inventory,
|
||||
min_armor: constraints.min_armor ? parseInt(constraints.min_armor) : null,
|
||||
max_armor: constraints.max_armor ? parseInt(constraints.max_armor) : null,
|
||||
min_crit_damage: constraints.min_crit_damage ? parseInt(constraints.min_crit_damage) : null,
|
||||
max_crit_damage: constraints.max_crit_damage ? parseInt(constraints.max_crit_damage) : null,
|
||||
min_damage_rating: constraints.min_damage_rating ? parseInt(constraints.min_damage_rating) : null,
|
||||
max_damage_rating: constraints.max_damage_rating ? parseInt(constraints.max_damage_rating) : null,
|
||||
max_results: 10,
|
||||
search_timeout: 300
|
||||
};
|
||||
|
||||
// Character selection
|
||||
if (constraints.characters.length > 0) {
|
||||
params.append('characters', constraints.characters.join(','));
|
||||
} else {
|
||||
params.append('include_all_characters', 'true');
|
||||
console.log('Starting suit search with constraints:', requestBody);
|
||||
|
||||
// Use fetch with streaming response instead of EventSource for POST support
|
||||
const response = await fetch(`${API_BASE}/search`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(requestBody)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Search failed: ${response.statusText}`);
|
||||
}
|
||||
|
||||
// Equipment sets
|
||||
if (constraints.primary_set) {
|
||||
params.append('primary_set', constraints.primary_set);
|
||||
}
|
||||
if (constraints.secondary_set) {
|
||||
params.append('secondary_set', constraints.secondary_set);
|
||||
}
|
||||
|
||||
// Legendary cantrips
|
||||
if (constraints.legendary_cantrips.length > 0) {
|
||||
params.append('legendary_cantrips', constraints.legendary_cantrips.join(','));
|
||||
}
|
||||
|
||||
// Legendary wards
|
||||
if (constraints.protection_spells.length > 0) {
|
||||
params.append('legendary_wards', constraints.protection_spells.join(','));
|
||||
}
|
||||
|
||||
// Rating constraints
|
||||
if (constraints.min_armor) params.append('min_armor', constraints.min_armor);
|
||||
if (constraints.max_armor) params.append('max_armor', constraints.max_armor);
|
||||
if (constraints.min_crit_damage) params.append('min_crit_damage', constraints.min_crit_damage);
|
||||
if (constraints.max_crit_damage) params.append('max_crit_damage', constraints.max_crit_damage);
|
||||
if (constraints.min_damage_rating) params.append('min_damage_rating', constraints.min_damage_rating);
|
||||
if (constraints.max_damage_rating) params.append('max_damage_rating', constraints.max_damage_rating);
|
||||
|
||||
// Equipment status
|
||||
params.append('include_equipped', constraints.include_equipped.toString());
|
||||
params.append('include_inventory', constraints.include_inventory.toString());
|
||||
|
||||
// Locked slots
|
||||
if (lockedSlots.size > 0) {
|
||||
params.append('locked_slots', Array.from(lockedSlots).join(','));
|
||||
}
|
||||
|
||||
// Search depth (default to balanced)
|
||||
params.append('search_depth', 'balanced');
|
||||
|
||||
const streamUrl = `${API_BASE}/optimize/suits/stream?${params.toString()}`;
|
||||
console.log('Streaming suits with URL:', streamUrl);
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const eventSource = new EventSource(streamUrl);
|
||||
let searchStopped = false;
|
||||
let buffer = '';
|
||||
|
||||
async function readStream() {
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
|
||||
if (done) {
|
||||
resolve();
|
||||
break;
|
||||
}
|
||||
|
||||
if (searchStopped) {
|
||||
await reader.cancel();
|
||||
resolve();
|
||||
break;
|
||||
}
|
||||
|
||||
// Process SSE data
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop() || ''; // Keep incomplete line in buffer
|
||||
|
||||
let currentEventType = null;
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('event: ')) {
|
||||
currentEventType = line.substring(7).trim();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith('data: ')) {
|
||||
const data = line.substring(6);
|
||||
|
||||
try {
|
||||
const eventData = JSON.parse(data);
|
||||
|
||||
// Handle different event types based on the current event type
|
||||
if (currentEventType === 'suit') {
|
||||
handleSuitEvent(eventData);
|
||||
} else if (currentEventType === 'progress') {
|
||||
handleProgressEvent(eventData);
|
||||
} else if (currentEventType === 'complete') {
|
||||
handleCompleteEvent(eventData);
|
||||
resolve();
|
||||
return;
|
||||
} else if (currentEventType === 'error') {
|
||||
handleErrorEvent(eventData);
|
||||
reject(new Error(eventData.message || 'Search error'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset event type after processing
|
||||
currentEventType = null;
|
||||
|
||||
} catch (parseError) {
|
||||
console.warn('Failed to parse SSE data:', data, 'Event type:', currentEventType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
readStream();
|
||||
|
||||
// Event handlers
|
||||
function handleSuitEvent(data) {
|
||||
try {
|
||||
// Transform backend suit format to frontend format
|
||||
const transformedSuit = transformSuitData(data);
|
||||
|
||||
// Insert suit in score-ordered position (highest score first)
|
||||
insertSuitInScoreOrder(transformedSuit);
|
||||
|
||||
// Regenerate entire results display to maintain proper ordering
|
||||
regenerateResultsDisplay();
|
||||
|
||||
// Update count
|
||||
document.getElementById('foundCount').textContent = currentSuits.length;
|
||||
document.getElementById('resultsCount').textContent = `Found ${currentSuits.length} suit${currentSuits.length !== 1 ? 's' : ''}`;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error processing suit data:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function handleProgressEvent(data) {
|
||||
try {
|
||||
document.getElementById('foundCount').textContent = data.found || currentSuits.length;
|
||||
document.getElementById('checkedCount').textContent = data.evaluated || 0;
|
||||
document.getElementById('elapsedTime').textContent = data.elapsed || '0.0';
|
||||
} catch (error) {
|
||||
console.error('Error processing progress data:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function handleCompleteEvent(data) {
|
||||
try {
|
||||
// Hide loading indicator
|
||||
const loadingDiv = document.querySelector('.loading');
|
||||
if (loadingDiv) {
|
||||
loadingDiv.innerHTML = `✅ Search complete! Found ${data.suits_found} suits in ${data.duration}s.`;
|
||||
}
|
||||
|
||||
// Update final results count
|
||||
const countSpan = document.getElementById('resultsCount');
|
||||
if (countSpan) {
|
||||
countSpan.textContent = `Found ${currentSuits.length} suit${currentSuits.length !== 1 ? 's' : ''}`;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error processing completion data:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function handleErrorEvent(data) {
|
||||
try {
|
||||
const loadingDiv = document.querySelector('.loading');
|
||||
if (loadingDiv) {
|
||||
loadingDiv.innerHTML = `❌ Search error: ${data.message}`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error processing error data:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Add stop search functionality
|
||||
const stopButton = document.getElementById('stopSearch');
|
||||
stopButton.addEventListener('click', () => {
|
||||
searchStopped = true;
|
||||
eventSource.close();
|
||||
|
||||
// Update UI to show search was stopped
|
||||
const loadingDiv = document.querySelector('.loading');
|
||||
|
|
@ -306,118 +423,7 @@ async function streamOptimalSuits(constraints) {
|
|||
if (countSpan) {
|
||||
countSpan.textContent = `Found ${currentSuits.length} suit${currentSuits.length !== 1 ? 's' : ''} (search stopped)`;
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
|
||||
// Handle individual suit results
|
||||
eventSource.addEventListener('suit', (event) => {
|
||||
try {
|
||||
const suit = JSON.parse(event.data);
|
||||
|
||||
// Transform backend suit format to frontend format
|
||||
const transformedSuit = transformSuitData(suit);
|
||||
currentSuits.push(transformedSuit);
|
||||
|
||||
// Add suit to streaming results
|
||||
addSuitToResults(transformedSuit, currentSuits.length - 1);
|
||||
|
||||
// Update count
|
||||
document.getElementById('foundCount').textContent = currentSuits.length;
|
||||
document.getElementById('resultsCount').textContent = `Found ${currentSuits.length} suit${currentSuits.length !== 1 ? 's' : ''}`;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error processing suit data:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle progress updates
|
||||
eventSource.addEventListener('progress', (event) => {
|
||||
try {
|
||||
const progress = JSON.parse(event.data);
|
||||
document.getElementById('foundCount').textContent = progress.found || currentSuits.length;
|
||||
document.getElementById('checkedCount').textContent = progress.checked || 0;
|
||||
document.getElementById('elapsedTime').textContent = progress.elapsed || '0.0';
|
||||
} catch (error) {
|
||||
console.error('Error processing progress data:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search completion
|
||||
eventSource.addEventListener('complete', (event) => {
|
||||
try {
|
||||
const completion = JSON.parse(event.data);
|
||||
|
||||
// Hide loading indicator
|
||||
const loadingDiv = document.querySelector('.loading');
|
||||
if (loadingDiv) {
|
||||
loadingDiv.innerHTML = `✅ Search complete! Found ${completion.total_found} suits in ${completion.total_time}s.`;
|
||||
}
|
||||
|
||||
// Update final results count
|
||||
const countSpan = document.getElementById('resultsCount');
|
||||
if (countSpan) {
|
||||
countSpan.textContent = `Found ${currentSuits.length} suit${currentSuits.length !== 1 ? 's' : ''}`;
|
||||
}
|
||||
|
||||
eventSource.close();
|
||||
resolve();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error processing completion data:', error);
|
||||
eventSource.close();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle timeout
|
||||
eventSource.addEventListener('timeout', (event) => {
|
||||
try {
|
||||
const timeout = JSON.parse(event.data);
|
||||
|
||||
// Update UI to show timeout
|
||||
const loadingDiv = document.querySelector('.loading');
|
||||
if (loadingDiv) {
|
||||
loadingDiv.innerHTML = `⏰ ${timeout.message}`;
|
||||
}
|
||||
|
||||
eventSource.close();
|
||||
resolve();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error processing timeout data:', error);
|
||||
eventSource.close();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle errors
|
||||
eventSource.addEventListener('error', (event) => {
|
||||
try {
|
||||
const errorData = JSON.parse(event.data);
|
||||
console.error('Stream error:', errorData.message);
|
||||
|
||||
const loadingDiv = document.querySelector('.loading');
|
||||
if (loadingDiv) {
|
||||
loadingDiv.innerHTML = `❌ Search error: ${errorData.message}`;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error parsing error data:', error);
|
||||
}
|
||||
|
||||
eventSource.close();
|
||||
reject(new Error('Stream error occurred'));
|
||||
});
|
||||
|
||||
// Handle connection errors
|
||||
eventSource.onerror = (event) => {
|
||||
if (!searchStopped) {
|
||||
console.error('EventSource error:', event);
|
||||
eventSource.close();
|
||||
reject(new Error('Connection error during streaming'));
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -458,40 +464,65 @@ function transformSuitData(suit) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Add a single suit to the streaming results display
|
||||
* Insert a suit into the currentSuits array in score-ordered position (highest first)
|
||||
*/
|
||||
function addSuitToResults(suit, index) {
|
||||
function insertSuitInScoreOrder(suit) {
|
||||
// Find the correct position to insert the suit (highest score first)
|
||||
let insertIndex = 0;
|
||||
for (let i = 0; i < currentSuits.length; i++) {
|
||||
if (suit.score > currentSuits[i].score) {
|
||||
insertIndex = i;
|
||||
break;
|
||||
}
|
||||
insertIndex = i + 1;
|
||||
}
|
||||
|
||||
// Insert the suit at the correct position
|
||||
currentSuits.splice(insertIndex, 0, suit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate the entire results display to maintain proper score ordering
|
||||
*/
|
||||
function regenerateResultsDisplay() {
|
||||
const streamingResults = document.getElementById('streamingResults');
|
||||
if (!streamingResults) return;
|
||||
|
||||
const scoreClass = getScoreClass(suit.score);
|
||||
const medal = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : '🔸';
|
||||
// Clear existing results
|
||||
streamingResults.innerHTML = '';
|
||||
|
||||
const suitHtml = `
|
||||
<div class="suit-item" data-suit-id="${suit.id}">
|
||||
<div class="suit-header">
|
||||
<div class="suit-score ${scoreClass}">
|
||||
${medal} Suit #${suit.id} (Score: ${suit.score})
|
||||
// Re-add all suits in their current (score-ordered) positions
|
||||
currentSuits.forEach((suit, index) => {
|
||||
const scoreClass = getScoreClass(suit.score);
|
||||
const medal = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : '🔸';
|
||||
|
||||
const suitHtml = `
|
||||
<div class="suit-item" data-suit-id="${suit.id}">
|
||||
<div class="suit-header">
|
||||
<div class="suit-score ${scoreClass}">
|
||||
${medal} Suit #${suit.id} (Score: ${suit.score})
|
||||
</div>
|
||||
</div>
|
||||
<div class="suit-stats">
|
||||
${formatSuitStats(suit)}
|
||||
</div>
|
||||
<div class="suit-items">
|
||||
${formatSuitItems(suit.items)}
|
||||
${suit.missing && suit.missing.length > 0 ? `<div class="missing-items">Missing: ${suit.missing.join(', ')}</div>` : ''}
|
||||
${suit.notes && suit.notes.length > 0 ? `<div class="suit-notes">${suit.notes.join(' • ')}</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div class="suit-stats">
|
||||
${formatSuitStats(suit)}
|
||||
</div>
|
||||
<div class="suit-items">
|
||||
${formatSuitItems(suit.items)}
|
||||
${suit.missing && suit.missing.length > 0 ? `<div class="missing-items">Missing: ${suit.missing.join(', ')}</div>` : ''}
|
||||
${suit.notes && suit.notes.length > 0 ? `<div class="suit-notes">${suit.notes.join(' • ')}</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
`;
|
||||
|
||||
streamingResults.insertAdjacentHTML('beforeend', suitHtml);
|
||||
streamingResults.insertAdjacentHTML('beforeend', suitHtml);
|
||||
});
|
||||
|
||||
// Add click handler for the new suit
|
||||
const newSuitElement = streamingResults.lastElementChild;
|
||||
newSuitElement.addEventListener('click', function() {
|
||||
const suitId = parseInt(this.dataset.suitId);
|
||||
selectSuit(suitId);
|
||||
// Re-add click handlers for all suits
|
||||
document.querySelectorAll('.suit-item').forEach(item => {
|
||||
item.addEventListener('click', function() {
|
||||
const suitId = parseInt(this.dataset.suitId);
|
||||
selectSuit(suitId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -612,10 +643,13 @@ function displaySuitResults(suits) {
|
|||
return;
|
||||
}
|
||||
|
||||
countSpan.textContent = `Found ${suits.length} suit${suits.length !== 1 ? 's' : ''}`;
|
||||
// Sort suits by score (highest first) before displaying
|
||||
const sortedSuits = [...suits].sort((a, b) => b.score - a.score);
|
||||
|
||||
countSpan.textContent = `Found ${sortedSuits.length} suit${sortedSuits.length !== 1 ? 's' : ''}`;
|
||||
|
||||
let html = '';
|
||||
suits.forEach((suit, index) => {
|
||||
sortedSuits.forEach((suit, index) => {
|
||||
const scoreClass = getScoreClass(suit.score);
|
||||
const medal = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : '🔸';
|
||||
|
||||
|
|
@ -660,28 +694,50 @@ function getScoreClass(score) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Format suit items for display
|
||||
* Format suit items for display - shows ALL armor slots even if empty
|
||||
*/
|
||||
function formatSuitItems(items) {
|
||||
let html = '';
|
||||
|
||||
if (!items || Object.keys(items).length === 0) {
|
||||
return '<div class="no-items">No items in this suit</div>';
|
||||
}
|
||||
// Define all expected armor/equipment slots in logical order
|
||||
const allSlots = [
|
||||
// Armor slots
|
||||
'Head', 'Chest', 'Upper Arms', 'Lower Arms', 'Hands',
|
||||
'Abdomen', 'Upper Legs', 'Lower Legs', 'Feet',
|
||||
// Jewelry slots
|
||||
'Neck', 'Left Ring', 'Right Ring', 'Left Wrist', 'Right Wrist', 'Trinket',
|
||||
// Clothing slots
|
||||
'Shirt', 'Pants'
|
||||
];
|
||||
|
||||
Object.entries(items).forEach(([slot, item]) => {
|
||||
const needsReducing = isMultiSlotItem(item) ? '<span class="need-reducing">Need Reducing</span>' : '';
|
||||
const properties = formatItemProperties(item);
|
||||
allSlots.forEach(slot => {
|
||||
const item = items ? items[slot] : null;
|
||||
|
||||
html += `
|
||||
<div class="suit-item-entry">
|
||||
<strong>${slot}:</strong>
|
||||
<span class="item-character">${item.character_name}</span> -
|
||||
<span class="item-name">${item.name}</span>
|
||||
${properties ? `<span class="item-properties">(${properties})</span>` : ''}
|
||||
${needsReducing}
|
||||
</div>
|
||||
`;
|
||||
if (item) {
|
||||
// Item exists in this slot
|
||||
const needsReducing = isMultiSlotItem(item) ? '<span class="need-reducing">Need Reducing</span>' : '';
|
||||
const properties = formatItemProperties(item);
|
||||
const ratings = formatItemRatings(item);
|
||||
|
||||
html += `
|
||||
<div class="suit-item-entry">
|
||||
<strong>${slot}:</strong>
|
||||
<span class="item-character">${item.source_character || item.character_name || 'Unknown'}</span> -
|
||||
<span class="item-name">${item.name}</span>
|
||||
${properties ? `<span class="item-properties">(${properties})</span>` : ''}
|
||||
${ratings ? `<span class="item-ratings">[${ratings}]</span>` : ''}
|
||||
${needsReducing}
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
// Empty slot
|
||||
html += `
|
||||
<div class="suit-item-entry empty-slot">
|
||||
<strong>${slot}:</strong>
|
||||
<span class="empty-slot-text">- Empty -</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
});
|
||||
|
||||
return html;
|
||||
|
|
@ -781,6 +837,47 @@ function formatItemProperties(item) {
|
|||
return properties.join(', ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Format item ratings for display (separate from properties)
|
||||
*/
|
||||
function formatItemRatings(item) {
|
||||
const ratings = [];
|
||||
|
||||
// Armor level
|
||||
if (item.armor_level && item.armor_level > 0) {
|
||||
ratings.push(`AL ${item.armor_level}`);
|
||||
}
|
||||
|
||||
// Damage ratings
|
||||
if (item.crit_damage_rating && item.crit_damage_rating > 0) {
|
||||
ratings.push(`CD +${item.crit_damage_rating}`);
|
||||
}
|
||||
|
||||
if (item.damage_rating && item.damage_rating > 0) {
|
||||
ratings.push(`DR +${item.damage_rating}`);
|
||||
}
|
||||
|
||||
// Resist ratings
|
||||
if (item.crit_damage_resist_rating && item.crit_damage_resist_rating > 0) {
|
||||
ratings.push(`CDR +${item.crit_damage_resist_rating}`);
|
||||
}
|
||||
|
||||
if (item.damage_resist_rating && item.damage_resist_rating > 0) {
|
||||
ratings.push(`DRR +${item.damage_resist_rating}`);
|
||||
}
|
||||
|
||||
// Other ratings
|
||||
if (item.heal_boost_rating && item.heal_boost_rating > 0) {
|
||||
ratings.push(`HB +${item.heal_boost_rating}`);
|
||||
}
|
||||
|
||||
if (item.vitality_rating && item.vitality_rating > 0) {
|
||||
ratings.push(`VIT +${item.vitality_rating}`);
|
||||
}
|
||||
|
||||
return ratings.join(', ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a suit and populate the visual slots
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue