// ==UserScript==
// @name FlatMMO Data Pages Beautifier
// @namespace http://tampermonkey.net/
// @version 1.1
// @description Beautifies FlatMMO mining and woodcutting data pages with complete tool and resource level requirements
// @author Pizza1337
// @match https://flatmmo.com/data/mining.html
// @match https://flatmmo.com/data/woodcutting.html
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// Get current page type
const pageType = window.location.pathname.includes('mining') ? 'mining' : 'woodcutting';
const toolType = pageType === 'mining' ? 'pickaxe' : 'axe';
const otherPage = pageType === 'mining' ? 'woodcutting' : 'mining';
const otherPageUrl = pageType === 'mining' ?
'https://flatmmo.com/data/woodcutting.html' :
'https://flatmmo.com/data/mining.html';
// Define available tools
const tools = ['bronze', 'iron', 'silver', 'gold', 'promethium', 'titanium', 'ancient'];
// Define level brackets
const levelBrackets = [
{min: 1, max: 9, label: '1-9'},
{min: 10, max: 19, label: '10-19'},
{min: 20, max: 29, label: '20-29'},
{min: 30, max: 39, label: '30-39'},
{min: 40, max: 49, label: '40-49'},
{min: 50, max: 59, label: '50-59'},
{min: 60, max: 69, label: '60-69'},
{min: 70, max: 79, label: '70-79'},
{min: 80, max: 89, label: '80-89'},
{min: 90, max: 99, label: '90-99'},
{min: 100, max: 100, label: '100'}
];
// Define tool level requirements
const toolLevelRequirements = {
mining: {
'bronze_pickaxe': 1,
'iron_pickaxe': 10,
'silver_pickaxe': 20,
'gold_pickaxe': 30,
'promethium_pickaxe': 50,
'titanium_pickaxe': 65,
'ancient_pickaxe': 101
},
woodcutting: {
'bronze_axe': 1,
'iron_axe': 10,
'silver_axe': 20,
'gold_axe': 30,
'promethium_axe': 50,
'titanium_axe': 65,
'ancient_axe': 101
}
};
const toolReqs = toolLevelRequirements[pageType];
// Define level requirements for resources
const resourceLevelRequirements = {
mining: {
'coal': 1,
'copper': 1,
'iron': 5,
'silver': 15,
'gold': 30,
'promethium': 50,
'titanium': 65,
'giant_coal': 1,
'giant_copper': 1,
'giant_iron': 5
},
woodcutting: {
'tree': 1,
'oak_tree': 10,
'willow_tree': 20,
'maple_tree': 30,
'mangrove_tree': 50,
'haunted_tree': 65
}
};
const levelReqs = resourceLevelRequirements[pageType];
// Parse the original table data BEFORE clearing the page
function parseTableData() {
const data = [];
const tables = document.getElementsByTagName('table');
if (tables.length > 0) {
const table = tables[0];
const rows = table.getElementsByTagName('tr');
for (let i = 1; i < rows.length; i++) {
const cells = rows[i].getElementsByTagName('td');
if (cells.length >= 7) {
const resourceName = cells[0].textContent.trim();
const toolName = cells[2].textContent.trim();
let xpValue = parseInt(cells[1].textContent.trim()) || 0;
if (resourceName === 'giant_coal') xpValue = 10;
if (resourceName === 'giant_copper') xpValue = 15;
if (resourceName === 'giant_iron') xpValue = 40;
const originalTicks = parseInt(cells[4].textContent.trim()) || 0;
const actualTicks = originalTicks + 1;
const ticksPerHour = 3600 / (actualTicks * 0.5);
const actualXpPerHour = xpValue * ticksPerHour;
data.push({
resource: resourceName,
xp: xpValue,
tool: toolName,
toolLevelRequired: toolReqs[toolName] || 1,
level: parseInt(cells[3].textContent.trim()) || 0,
ticks: actualTicks,
xpPerHour: actualXpPerHour,
levelRequired: levelReqs[resourceName] || 1
});
}
}
}
return data;
}
// Store the data BEFORE modifying the page
const tableData = parseTableData();
if (tableData.length === 0) {
alert('Error: Could not parse data from the page. Please refresh and try again.');
return;
}
// Store original order of resources
const resourceOrder = {};
const uniqueResources = [...new Set(tableData.map(item => item.resource))];
uniqueResources.forEach((resource, index) => {
resourceOrder[resource] = index;
});
// Fetch user level from hiscores API
async function fetchUserLevel(username) {
return new Promise((resolve) => {
const apiUrl = `https://flatmmo.com/api/hiscores/${pageType}.php`;
GM_xmlhttpRequest({
method: 'GET',
url: apiUrl,
headers: { 'Accept': 'application/json' },
onload: function(response) {
try {
const data = JSON.parse(response.responseText);
const searchUsername = username.toLowerCase().trim();
for (let i = 0; i < data.length; i++) {
const entry = data[i];
const entryUsername = entry.username ? entry.username.toLowerCase().trim() : '';
if (entryUsername === searchUsername) {
const levelField = `${pageType}_level`;
const level = parseInt(entry[levelField]);
if (!isNaN(level) && level > 0) {
resolve(level);
return;
}
}
}
resolve(null);
} catch (error) {
console.error('Error parsing API response:', error);
resolve(null);
}
},
onerror: function(error) {
console.error('Failed to fetch from API:', error);
resolve(null);
}
});
});
}
// Inject modern styles
GM_addStyle(`
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Poppins', system-ui, -apple-system, sans-serif; background: #0a0e27; color: #e8e6e3; padding: 0; min-height: 100vh; overflow-x: hidden; }
body::before { content: ''; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: radial-gradient(circle at 20% 50%, rgba(120, 80, 255, 0.15) 0%, transparent 50%), radial-gradient(circle at 80% 80%, rgba(0, 255, 136, 0.1) 0%, transparent 50%), radial-gradient(circle at 40% 20%, rgba(0, 212, 255, 0.1) 0%, transparent 50%); pointer-events: none; z-index: 1; }
.container { max-width: 1600px; margin: 0 auto; padding: 20px; position: relative; z-index: 2; }
.header { text-align: center; padding: 60px 20px 40px; position: relative; }
.header h1 { font-size: 4em; font-weight: 700; margin: 0; background: linear-gradient(135deg, #667eea 0%, #00ff88 50%, #00d4ff 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; text-transform: uppercase; letter-spacing: 3px; animation: glow 3s ease-in-out infinite; }
@keyframes glow { 0%, 100% { filter: brightness(1); } 50% { filter: brightness(1.2); } }
.header p { margin-top: 10px; color: #8892b0; font-size: 1.2em; }
.page-switcher { position: absolute; top: 20px; right: 20px; z-index: 10; }
.page-switcher a { display: inline-flex; align-items: center; gap: 8px; padding: 12px 24px; background: linear-gradient(135deg, rgba(102, 126, 234, 0.2), rgba(0, 255, 136, 0.2)); border: 1px solid rgba(0, 255, 136, 0.3); border-radius: 12px; color: #00ff88; text-decoration: none; font-weight: 500; transition: all 0.3s; }
.page-switcher a:hover { transform: translateY(-2px); box-shadow: 0 10px 30px rgba(0, 255, 136, 0.3); background: linear-gradient(135deg, rgba(102, 126, 234, 0.3), rgba(0, 255, 136, 0.3)); }
.user-section { background: rgba(255, 255, 255, 0.03); backdrop-filter: blur(20px); border-radius: 25px; padding: 25px; margin-bottom: 30px; border: 1px solid rgba(255, 255, 255, 0.08); box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); }
.user-input-container { display: flex; gap: 15px; align-items: center; justify-content: center; flex-wrap: wrap; }
.user-input-container label { color: #00ff88; font-weight: 500; }
.user-input-container input { padding: 10px 15px; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 12px; color: white; font-family: inherit; width: 200px; }
.user-input-container button { padding: 10px 20px; background: linear-gradient(135deg, #667eea, #00ff88); border: none; border-radius: 12px; color: white; font-weight: 600; cursor: pointer; transition: all 0.3s; }
.user-input-container button:hover { transform: translateY(-2px); box-shadow: 0 10px 30px rgba(0, 255, 136, 0.3); }
.user-level-display { margin-top: 15px; text-align: center; padding: 15px; background: rgba(0, 255, 136, 0.1); border-radius: 12px; border: 1px solid rgba(0, 255, 136, 0.2); }
.user-level-display .level-text { font-size: 1.2em; color: #00ff88; font-weight: 600; }
.tool-selector { background: rgba(255, 255, 255, 0.03); backdrop-filter: blur(20px); border-radius: 25px; padding: 30px; margin-bottom: 40px; border: 1px solid rgba(255, 255, 255, 0.08); box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); }
.tool-selector h2 { margin: 0 0 25px 0; background: linear-gradient(90deg, #00ff88, #00d4ff); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-size: 1.5em; font-weight: 600; text-align: center; }
.tool-grid { display: flex; justify-content: center; flex-wrap: wrap; gap: 15px; margin-bottom: 25px; }
.tool-option { display: flex; flex-direction: column; align-items: center; padding: 20px; background: rgba(255, 255, 255, 0.05); border: 2px solid transparent; border-radius: 20px; cursor: pointer; transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); position: relative; overflow: hidden; min-width: 100px; }
.tool-option::before { content: ''; position: absolute; top: 50%; left: 50%; width: 100%; height: 100%; background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%); transform: translate(-50%, -50%) scale(0); transition: transform 0.5s; }
.tool-option:hover::before { transform: translate(-50%, -50%) scale(2); }
.tool-option:hover { transform: translateY(-5px) scale(1.05); box-shadow: 0 15px 40px rgba(0, 255, 136, 0.3); }
.tool-option.active { background: linear-gradient(135deg, rgba(102, 126, 234, 0.2), rgba(0, 255, 136, 0.2)); border-color: #00ff88; box-shadow: 0 0 30px rgba(0, 255, 136, 0.4); transform: scale(1.05); }
.tool-option img { width: 56px; height: 56px; margin-bottom: 10px; filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.4)); position: relative; z-index: 1; }
.tool-option span { font-size: 0.95em; font-weight: 500; text-transform: capitalize; position: relative; z-index: 1; }
.filters { display: flex; gap: 20px; align-items: center; justify-content: center; flex-wrap: wrap; }
.filter-group { display: flex; align-items: center; gap: 10px; background: rgba(255, 255, 255, 0.05); padding: 10px 20px; border-radius: 15px; }
.filter-group label { color: #00ff88; font-weight: 500; font-size: 0.9em; }
.filter-group select { background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); color: white; padding: 8px 12px; border-radius: 10px; font-family: inherit; cursor: pointer; transition: all 0.3s; }
.filter-group select:hover { background: rgba(255, 255, 255, 0.15); border-color: rgba(0, 255, 136, 0.5); }
.filter-group select:focus { outline: none; border-color: #00ff88; box-shadow: 0 0 10px rgba(0, 255, 136, 0.3); }
.filter-group select option { background: #1a1f3a; color: white; }
.level-brackets { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }
.bracket-btn { padding: 8px 16px; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 10px; color: #8892b0; cursor: pointer; transition: all 0.3s; font-weight: 500; font-size: 0.9em; }
.bracket-btn:hover { background: rgba(255, 255, 255, 0.1); color: white; transform: translateY(-2px); }
.bracket-btn.active { background: linear-gradient(135deg, #667eea, #00ff88); border-color: transparent; color: white; }
.bracket-btn.auto-selected { background: linear-gradient(135deg, #00ff88, #00d4ff); border-color: transparent; color: white; box-shadow: 0 0 20px rgba(0, 255, 136, 0.4); }
.view-toggle { display: flex; gap: 10px; justify-content: center; margin: 30px 0; }
.view-btn { padding: 12px 24px; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 12px; color: white; cursor: pointer; transition: all 0.3s; font-weight: 500; }
.view-btn.active { background: linear-gradient(135deg, #667eea, #00ff88); border-color: transparent; }
.view-btn:hover:not(.active) { background: rgba(255, 255, 255, 0.1); }
.stats-header { display: flex; justify-content: center; margin: 40px 0; }
.stat-card { background: linear-gradient(135deg, rgba(102, 126, 234, 0.2), rgba(0, 255, 136, 0.2)); backdrop-filter: blur(10px); border-radius: 25px; padding: 30px 40px; text-align: center; border: 2px solid rgba(0, 255, 136, 0.3); transition: all 0.3s; position: relative; overflow: hidden; min-width: 400px; box-shadow: 0 20px 60px rgba(0, 255, 136, 0.2); }
.stat-card::before { content: ''; position: absolute; top: -50%; left: -50%; width: 200%; height: 200%; background: linear-gradient(45deg, transparent, rgba(255, 255, 255, 0.1), transparent); animation: shimmer 3s ease-in-out infinite; }
@keyframes shimmer { 0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); } 50% { transform: translateX(0%) translateY(0%) rotate(45deg); } 100% { transform: translateX(100%) translateY(100%) rotate(45deg); } }
.stat-card:hover { transform: translateY(-5px) scale(1.02); box-shadow: 0 25px 70px rgba(102, 126, 234, 0.3); }
.stat-value { font-size: 3em; font-weight: 700; background: linear-gradient(135deg, #ffd700, #00ff88, #00d4ff); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 10px; text-shadow: 0 0 30px rgba(0, 255, 136, 0.5); }
.stat-label { color: #00ff88; font-size: 1.2em; font-weight: 600; text-transform: uppercase; letter-spacing: 2px; margin-bottom: 15px; }
.stat-sublabel { color: #e8e6e3; font-size: 1.1em; margin-top: 5px; font-weight: 500; }
.stat-resource { color: #00d4ff; font-size: 1.3em; font-weight: 600; text-transform: capitalize; }
.stat-level { color: #ffd700; font-weight: 700; }
.resources-container { margin-top: 40px; }
.resource-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 25px; animation: fadeIn 0.5s ease-in; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
.resource-card { background: rgba(255, 255, 255, 0.03); backdrop-filter: blur(10px); border-radius: 20px; border: 1px solid rgba(255, 255, 255, 0.08); overflow: hidden; transition: all 0.3s; cursor: pointer; }
.resource-card:hover { transform: translateY(-5px) scale(1.02); box-shadow: 0 20px 50px rgba(0, 255, 136, 0.2); border-color: rgba(0, 255, 136, 0.3); }
.resource-header { padding: 20px; background: linear-gradient(135deg, rgba(102, 126, 234, 0.2), rgba(0, 255, 136, 0.1)); border-bottom: 1px solid rgba(255, 255, 255, 0.08); display: flex; justify-content: space-between; align-items: center; }
.resource-name { font-size: 1.3em; font-weight: 600; color: #00d4ff; text-transform: capitalize; display: flex; align-items: center; gap: 12px; }
.resource-icon { width: 40px; height: 40px; filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3)); object-fit: contain; }
.resource-levels { padding: 20px; max-height: 300px; overflow-y: auto; }
.resource-levels::-webkit-scrollbar { width: 6px; }
.resource-levels::-webkit-scrollbar-track { background: rgba(255, 255, 255, 0.05); border-radius: 3px; }
.resource-levels::-webkit-scrollbar-thumb { background: linear-gradient(135deg, #667eea, #00ff88); border-radius: 3px; }
.level-entry { display: grid; grid-template-columns: auto 1fr auto; gap: 15px; padding: 12px; margin-bottom: 10px; background: rgba(255, 255, 255, 0.02); border-radius: 12px; align-items: center; transition: all 0.3s; }
.level-entry:hover { background: rgba(255, 255, 255, 0.05); transform: translateX(5px); }
.level-entry:last-child { margin-bottom: 0; }
.level-badge { background: linear-gradient(135deg, #ff6b6b, #ff8e53); color: white; padding: 6px 12px; border-radius: 20px; font-weight: 600; font-size: 0.85em; }
.level-stats { display: flex; gap: 20px; align-items: center; }
.stat-item { display: flex; flex-direction: column; align-items: center; }
.stat-item-value { font-weight: 600; color: #00ff88; font-size: 0.95em; }
.stat-item-label { font-size: 0.75em; color: #8892b0; text-transform: uppercase; letter-spacing: 1px; }
.xp-hour-badge { background: linear-gradient(135deg, #667eea, #764ba2); padding: 8px 16px; border-radius: 12px; font-weight: 600; font-size: 0.9em; white-space: nowrap; }
.resource-list { background: rgba(255, 255, 255, 0.03); backdrop-filter: blur(10px); border-radius: 20px; border: 1px solid rgba(255, 255, 255, 0.08); overflow: hidden; animation: fadeIn 0.5s ease-in; }
.list-header { display: grid; grid-template-columns: 2fr 1fr 1fr 1fr 1fr 1.5fr; padding: 20px; background: linear-gradient(135deg, rgba(102, 126, 234, 0.2), rgba(0, 255, 136, 0.1)); border-bottom: 2px solid rgba(0, 255, 136, 0.3); font-weight: 600; color: #00ff88; text-transform: uppercase; font-size: 0.9em; letter-spacing: 1px; }
.list-item { display: grid; grid-template-columns: 2fr 1fr 1fr 1fr 1fr 1.5fr; padding: 20px; border-bottom: 1px solid rgba(255, 255, 255, 0.05); align-items: center; transition: all 0.3s; cursor: pointer; }
.list-item:hover { background: rgba(255, 255, 255, 0.05); transform: translateX(10px); }
.list-item:last-child { border-bottom: none; }
.list-resource { display: flex; align-items: center; gap: 12px; font-weight: 600; color: #00d4ff; text-transform: capitalize; }
.list-level { color: #ff6b6b; font-weight: 600; }
.list-ticks { color: #00ff88; }
.list-xp { color: #ffd700; font-weight: 500; }
.sort-indicator { display: inline-block; margin-left: 5px; transition: transform 0.3s; }
.sort-asc::after { content: '▲'; font-size: 0.8em; }
.sort-desc::after { content: '▼'; font-size: 0.8em; }
.hidden { display: none !important; }
.empty-state { text-align: center; padding: 60px 20px; color: #8892b0; }
.empty-state h3 { font-size: 1.5em; margin-bottom: 10px; color: #64748b; }
`);
// Create new UI
document.body.innerHTML = '';
const container = document.createElement('div');
container.className = 'container';
const pageSwitcher = document.createElement('div');
pageSwitcher.className = 'page-switcher';
pageSwitcher.innerHTML = `<a href="${otherPageUrl}">${otherPage === 'mining' ? '⛏️' : '🪓'} Switch to ${otherPage.charAt(0).toUpperCase() + otherPage.slice(1)}</a>`;
container.appendChild(pageSwitcher);
const header = document.createElement('div');
header.className = 'header';
header.innerHTML = `<h1>${pageType.charAt(0).toUpperCase() + pageType.slice(1)}</h1><p>Optimize your XP gains with the perfect tool and level combination</p>`;
container.appendChild(header);
const userSection = document.createElement('div');
userSection.className = 'user-section';
userSection.innerHTML = `<div class="user-input-container"><label>Username:</label><input type="text" id="usernameInput" placeholder="Enter your username"><button id="fetchLevelBtn">Fetch Level</button><button id="clearUserBtn" style="background: linear-gradient(135deg, #ff6b6b, #ff8e53);">Clear</button></div><div id="userLevelDisplay" class="user-level-display hidden"><div class="level-text">Loading...</div></div>`;
container.appendChild(userSection);
const toolSelector = document.createElement('div');
toolSelector.className = 'tool-selector';
toolSelector.innerHTML = `<h2>⚒️ Select Your Tool</h2><div class="tool-grid" id="toolGrid"></div><div class="filters"><div class="filter-group"><label>Level Range:</label><div class="level-brackets" id="levelBrackets"></div></div><div class="filter-group"><label>Sort by:</label><select id="sortBy"><option value="resource">Resource (Original Order)</option><option value="xpPerHour">XP per Hour</option></select></div></div>`;
container.appendChild(toolSelector);
const toolGrid = toolSelector.querySelector('#toolGrid');
const levelBracketsContainer = toolSelector.querySelector('#levelBrackets');
tools.forEach(tool => {
const toolFullName = `${tool}_${toolType}`;
const toolOption = document.createElement('div');
toolOption.className = 'tool-option';
toolOption.dataset.tool = toolFullName;
const toolLevelReq = toolReqs[toolFullName] || 1;
toolOption.innerHTML = `<img src="https://flatmmo.com/images/items/${toolFullName}.png" alt="${tool} ${toolType}" onerror="this.src='data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2256%22 height=%2256%22 viewBox=%220 0 56 56%22><rect width=%2256%22 height=%2256%22 fill=%22%23444%22 rx=%228%22/><text x=%2228%22 y=%2235%22 text-anchor=%22middle%22 fill=%22%23aaa%22 font-size=%2224%22>⚒️</text></svg>'"><span>${tool}</span><span class="tool-level-req" data-req-level="${toolLevelReq}" style="font-size: 0.75em; color: #ff6b6b;">Lvl ${toolLevelReq}</span>`;
toolOption.addEventListener('click', () => filterByTool(toolFullName));
toolGrid.appendChild(toolOption);
});
const allLevelsBtn = document.createElement('button');
allLevelsBtn.className = 'bracket-btn active';
allLevelsBtn.dataset.min = '1';
allLevelsBtn.dataset.max = '100';
allLevelsBtn.textContent = 'All Levels';
allLevelsBtn.addEventListener('click', () => selectLevelBracket({min: 1, max: 100, label: 'All Levels'}));
levelBracketsContainer.appendChild(allLevelsBtn);
levelBrackets.forEach(bracket => {
const bracketBtn = document.createElement('button');
bracketBtn.className = 'bracket-btn';
bracketBtn.dataset.min = bracket.min;
bracketBtn.dataset.max = bracket.max;
bracketBtn.textContent = bracket.label;
bracketBtn.addEventListener('click', () => selectLevelBracket(bracket));
levelBracketsContainer.appendChild(bracketBtn);
});
const viewToggle = document.createElement('div');
viewToggle.className = 'view-toggle';
viewToggle.innerHTML = `<button class="view-btn active" data-view="cards">📊 Card View</button><button class="view-btn" data-view="list">📋 List View</button>`;
container.appendChild(viewToggle);
const statsHeader = document.createElement('div');
statsHeader.className = 'stats-header';
statsHeader.id = 'statsHeader';
container.appendChild(statsHeader);
const resourcesContainer = document.createElement('div');
resourcesContainer.className = 'resources-container';
resourcesContainer.id = 'resourcesContainer';
container.appendChild(resourcesContainer);
document.body.appendChild(container);
// State management
let currentTool = GM_getValue('flatmmo_last_tool_' + pageType, 'all');
let minLevelFilter = 1;
let maxLevelFilter = 100;
let currentView = 'cards';
let sortBy = 'resource';
let sortOrder = 'asc';
let currentUsername = GM_getValue('flatmmo_username', '');
let currentUserLevel = null;
function selectLevelBracket(bracket) {
minLevelFilter = bracket.min;
maxLevelFilter = bracket.max;
document.querySelectorAll('.bracket-btn').forEach(btn => {
btn.classList.remove('active', 'auto-selected');
if (btn.dataset.min == bracket.min && btn.dataset.max == bracket.max) {
btn.classList.add('active');
}
});
applyFilters();
}
function filterByTool(tool) {
document.querySelectorAll('.tool-option').forEach(opt => {
opt.classList.toggle('active', opt.dataset.tool === tool);
});
if (currentTool === tool) {
currentTool = 'all';
document.querySelectorAll('.tool-option').forEach(opt => opt.classList.remove('active'));
} else {
currentTool = tool;
}
GM_setValue('flatmmo_last_tool_' + pageType, currentTool);
applyFilters();
}
function findBestBracketForLevel(level) {
for (let bracket of levelBrackets) {
if (level >= bracket.min && level <= bracket.max) {
return bracket;
}
}
return {min: 1, max: 100, label: 'All Levels'};
}
function updateToolLevelColors() {
document.querySelectorAll('.tool-option').forEach(option => {
const levelSpan = option.querySelector('.tool-level-req');
if (levelSpan) {
const requiredLevel = parseInt(levelSpan.dataset.reqLevel, 10);
if (currentUserLevel && currentUserLevel >= requiredLevel) {
levelSpan.style.color = '#00ff88';
} else {
levelSpan.style.color = '#ff6b6b';
}
}
});
}
async function fetchAndSetUserLevel(username) {
const levelDisplay = document.getElementById('userLevelDisplay');
const levelText = levelDisplay.querySelector('.level-text');
levelDisplay.classList.remove('hidden');
levelText.textContent = 'Fetching level...';
const level = await fetchUserLevel(username);
if (level !== null && level > 0) {
currentUserLevel = level;
levelText.innerHTML = `<strong style="color: #00d4ff;">${username}</strong><span style="color: #8892b0;">•</span> ${pageType.charAt(0).toUpperCase() + pageType.slice(1)} Lvl: <strong style="color: #ffd700;">${level}</strong>`;
const bestBracket = findBestBracketForLevel(level);
minLevelFilter = bestBracket.min;
maxLevelFilter = bestBracket.max;
document.querySelectorAll('.bracket-btn').forEach(btn => {
btn.classList.remove('active', 'auto-selected');
if (btn.dataset.min == bestBracket.min && btn.dataset.max == bestBracket.max) {
btn.classList.add('auto-selected');
}
});
updateToolLevelColors();
applyFilters();
} else {
levelText.innerHTML = `<span style="color: #ff6b6b;">⚠️ Username not found in hiscores</span><br><span style="color: #8892b0; font-size: 0.9em;">Make sure the username exists in the ${pageType} hiscores</span>`;
currentUserLevel = null;
updateToolLevelColors();
applyFilters(); // Re-apply filters to update colors in summary card
}
}
function updateStats(data) {
const statsHeader = document.getElementById('statsHeader');
if (data.length === 0) {
statsHeader.innerHTML = `<div class="stat-card"><div class="stat-label">⚠️ No Data Available</div><div class="stat-sublabel">Try adjusting your filters or check level requirements</div></div>`;
return;
}
const bestXp = Math.max(...data.map(d => d.xpPerHour));
const bestItem = data.find(d => d.xpPerHour === bestXp);
// Determine colors based on user level
const resourceReqColor = (currentUserLevel && currentUserLevel >= bestItem.levelRequired) ? '#00ff88' : '#ff6b6b';
const toolReqColor = (currentUserLevel && currentUserLevel >= bestItem.toolLevelRequired) ? '#00ff88' : '#ff6b6b';
statsHeader.innerHTML = `
<div class="stat-card">
<div class="stat-label">🏆 Best XP Per Hour</div>
<div class="stat-value">${Math.round(bestXp).toLocaleString()}</div>
<div class="stat-sublabel">
<span class="stat-resource">${bestItem.resource.replace(/_/g, ' ')}</span> at <span class="stat-level">Lvl ${bestItem.level}</span>
</div>
<div class="stat-sublabel" style="margin-top: 5px;">
<span style="color: ${resourceReqColor};">Resource requires Lvl ${bestItem.levelRequired}</span>
<span style="color: #8892b0;"> • </span>
<span style="color: ${toolReqColor};">Tool requires Lvl ${bestItem.toolLevelRequired}</span>
</div>
<div class="stat-sublabel" style="margin-top: 10px; opacity: 0.8;">
${bestItem.tool.replace(/_/g, ' ')} • ${bestItem.ticks} ticks • ${bestItem.xp} XP per resource
</div>
</div>`;
}
function getResourceImage(resource) {
if (pageType === 'woodcutting') {
const treeImages = { 'tree': 'https://flatmmo.wiki/images/7/76/Normal_tree.png', 'oak_tree': 'https://flatmmo.wiki/images/c/cf/Oak_tree.png', 'willow_tree': 'https://flatmmo.wiki/images/1/19/Willow_tree.png', 'maple_tree': 'https://flatmmo.wiki/images/4/4b/Maple_tree.png', 'mangrove_tree': 'https://flatmmo.wiki/images/6/67/Mangrove_tree.png', 'haunted_tree': 'https://flatmmo.wiki/images/a/a7/Haunted_tree.png' };
return treeImages[resource] || `https://flatmmo.com/images/items/${resource}.png`;
} else if (pageType === 'mining') {
const rockImages = { 'giant_coal': 'https://flatmmo.wiki/images/f/f8/Coal_rock.png', 'giant_copper': 'https://flatmmo.wiki/images/8/89/Copper_rock.png', 'giant_iron': 'https://flatmmo.wiki/images/d/db/Iron_rock.png' };
return rockImages[resource] || `https://flatmmo.com/images/items/${resource}.png`;
}
return `https://flatmmo.com/images/items/${resource}.png`;
}
function renderCardView(data) {
const container = document.getElementById('resourcesContainer');
if (data.length === 0) {
container.innerHTML = `<div class="empty-state"><h3>No resources found</h3><p>Try adjusting your filters or selecting a different tool</p><p style="color: #ff6b6b; margin-top: 10px;">Note: Resources and tools you can't use at your level are hidden</p></div>`;
return;
}
const groupedData = {};
data.forEach(item => { if (!groupedData[item.resource]) { groupedData[item.resource] = []; } groupedData[item.resource].push(item); });
Object.keys(groupedData).forEach(resource => groupedData[resource].sort((a, b) => a.level - b.level));
const sortedResources = Object.keys(groupedData).sort((a, b) => { if (sortBy === 'xpPerHour') { const maxA = Math.max(...groupedData[a].map(item => item.xpPerHour)); const maxB = Math.max(...groupedData[b].map(item => item.xpPerHour)); return sortOrder === 'asc' ? maxA - maxB : maxB - maxA; } return resourceOrder[a] - resourceOrder[b]; });
const cardsHtml = sortedResources.map(resource => {
const levels = groupedData[resource];
const levelReq = levels[0].levelRequired;
const levelReqColor = (currentUserLevel && currentUserLevel >= levelReq) ? '#00ff88' : '#ff6b6b';
return `<div class="resource-card"><div class="resource-header"><div class="resource-name"><img src="${getResourceImage(resource)}" class="resource-icon" onerror="this.style.display='none'">${resource.replace(/_/g, ' ')}</div><div style="color: ${levelReqColor}; font-size: 0.9em;">Requires Lvl ${levelReq}</div></div><div class="resource-levels">${levels.map(level => `<div class="level-entry"><div class="level-badge">Lvl ${level.level}</div><div class="level-stats"><div class="stat-item"><span class="stat-item-value">${level.ticks}</span><span class="stat-item-label">Ticks</span></div><div class="stat-item"><span class="stat-item-value">${level.xp}</span><span class="stat-item-label">XP</span></div></div><div class="xp-hour-badge">${Math.round(level.xpPerHour).toLocaleString()} xp/h</div></div>`).join('')}</div></div>`;
}).join('');
container.innerHTML = `<div class="resource-cards">${cardsHtml}</div>`;
}
function renderListView(data) {
const container = document.getElementById('resourcesContainer');
if (data.length === 0) {
container.innerHTML = `<div class="empty-state"><h3>No resources found</h3><p>Try adjusting your filters or selecting a different tool</p><p style="color: #ff6b6b; margin-top: 10px;">Note: Resources and tools you can't use at your level are hidden</p></div>`;
return;
}
const sortedData = [...data].sort((a, b) => { if (sortBy === 'xpPerHour') { return sortOrder === 'asc' ? a.xpPerHour - b.xpPerHour : b.xpPerHour - a.xpPerHour; } const orderDiff = resourceOrder[a.resource] - resourceOrder[b.resource]; if (orderDiff !== 0) return orderDiff; return a.level - b.level; });
const listHtml = `<div class="resource-list"><div class="list-header"><div class="sortable" data-sort="resource">Resource <span class="sort-indicator ${sortBy === 'resource' ? `sort-${sortOrder}` : ''}"></span></div><div>Req. Lvl</div><div>Your Lvl</div><div>Ticks</div><div>XP</div><div class="sortable" data-sort="xpPerHour">XP/Hour <span class="sort-indicator ${sortBy === 'xpPerHour' ? `sort-${sortOrder}` : ''}"></span></div></div>${sortedData.map(item => `<div class="list-item"><div class="list-resource"><img src="${getResourceImage(item.resource)}" class="resource-icon" onerror="this.style.display='none'">${item.resource.replace(/_/g, ' ')}</div><div class="list-level" style="color: #ff6b6b;">${item.levelRequired}</div><div class="list-level">${item.level}</div><div class="list-ticks">${item.ticks}</div><div class="list-xp">${item.xp}</div><div class="xp-hour-badge">${Math.round(item.xpPerHour).toLocaleString()} xp/h</div></div>`).join('')}</div>`;
container.innerHTML = listHtml;
container.querySelectorAll('.sortable').forEach(header => {
header.style.cursor = 'pointer';
header.addEventListener('click', () => {
const newSortBy = header.dataset.sort;
if (sortBy === newSortBy) { sortOrder = sortOrder === 'asc' ? 'desc' : 'asc'; } else { sortBy = newSortBy; sortOrder = sortBy === 'xpPerHour' ? 'desc' : 'asc'; }
document.getElementById('sortBy').value = sortBy;
applyFilters();
});
});
}
function applyFilters() {
let filteredData = tableData;
if (currentTool !== 'all') {
filteredData = filteredData.filter(row => row.tool === currentTool);
}
filteredData = filteredData.filter(row => {
const isInBracket = row.level >= minLevelFilter && row.level <= maxLevelFilter;
const isResourceAvailable = row.levelRequired <= maxLevelFilter;
const isToolAvailable = row.toolLevelRequired <= maxLevelFilter;
return isInBracket && isResourceAvailable && isToolAvailable;
});
updateStats(filteredData);
if (currentView === 'cards') {
renderCardView(filteredData);
} else {
renderListView(filteredData);
}
}
// Event listeners
document.getElementById('fetchLevelBtn').addEventListener('click', async () => {
const username = document.getElementById('usernameInput').value.trim();
if (username) {
GM_setValue('flatmmo_username', username);
currentUsername = username;
await fetchAndSetUserLevel(username);
} else {
alert('Please enter a username');
}
});
document.getElementById('clearUserBtn').addEventListener('click', () => {
GM_setValue('flatmmo_username', '');
currentUsername = '';
currentUserLevel = null;
updateToolLevelColors();
document.getElementById('usernameInput').value = '';
document.getElementById('userLevelDisplay').classList.add('hidden');
selectLevelBracket({min: 1, max: 100, label: 'All Levels'});
});
document.getElementById('usernameInput').addEventListener('keypress', async (e) => {
if (e.key === 'Enter') {
const username = e.target.value.trim();
if (username) {
GM_setValue('flatmmo_username', username);
currentUsername = username;
await fetchAndSetUserLevel(username);
}
}
});
document.getElementById('sortBy').addEventListener('change', (e) => {
sortBy = e.target.value;
sortOrder = sortBy === 'xpPerHour' ? 'desc' : 'asc';
applyFilters();
});
document.querySelectorAll('.view-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.view-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentView = btn.dataset.view;
applyFilters();
});
});
// Initialize UI and load saved data
function initialize() {
if (currentTool !== 'all') {
const lastSelectedToolEl = document.querySelector(`.tool-option[data-tool="${currentTool}"]`);
if (lastSelectedToolEl) {
lastSelectedToolEl.classList.add('active');
}
}
if (currentUsername) {
document.getElementById('usernameInput').value = currentUsername;
fetchAndSetUserLevel(currentUsername);
} else {
applyFilters();
}
}
initialize();
})();