- // ==UserScript==
- // @name Elethor Spire Simulator
- // @namespace http://tampermonkey.net/
- // @version 2.2
- // @description Spire simulator
- // @author Eugene
- // @match https://elethor.com/*
- // @grant none
- // @license GPL-3.0-or-later
- // ==/UserScript==
- /*
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
- (function() {
- 'use strict';
-
- const stats = [
- 'MIN DAMAGE',
- 'MAX DAMAGE',
- 'HEALTH',
- 'BREACH',
- 'BARRIER',
- 'CRIT CHANCE',
- 'CRIT DAMAGE',
- 'BLOCK CHANCE',
- 'BLOCK AMOUNT'
- ];
- let simulationLogs = [];
- let monsterWins = 0;
- let userWins = 0;
- let selectedModifiers = {};
-
- function saveToLocalStorage(key, data) {
- try {
- localStorage.setItem(key, JSON.stringify(data));
- } catch (error) {
- console.error('Error saving to localStorage:', error);
- }
- }
-
- function loadFromLocalStorage(key) {
- try {
- const data = localStorage.getItem(key);
- return data ? JSON.parse(data) : null;
- } catch (error) {
- console.error('Error loading from localStorage:', error);
- return null;
- }
- }
-
- function calculateTotalStats() {
-
- const totals = {};
- for (const key in userStats) {
- totals[key] = parseFloat(userStats[key]) || 0;
- }
-
- console.log('Starting calculation with base stats:', {...totals});
- console.log('Processing modifiers:', Object.keys(selectedModifiers).length, selectedModifiers);
-
- const baseModifiers = {};
- const percentageStats = ['crit_chance', 'block_chance'];
-
- Object.values(selectedModifiers).forEach(modifier => {
- if (modifier.name.startsWith('Base ') && !modifier.name.includes('MINUS')) {
- const words = modifier.name.split(' '); // Split into words
- let modKey = modifier.name.replace('Base ', '').toLowerCase().replace(/ /g, '_');
-
- // If the second word is "Crit" and the third word is NOT "Chance", set it to crit_chance
- if (words.length >= 2 && words[1] === 'Crit' && words[2] !== 'Chance') {
- modKey = 'crit_damage';
- }
- const modValue = parseFloat(modifier.value);
-
- if (!baseModifiers[modKey]) baseModifiers[modKey] = [];
-
- if (percentageStats.includes(modKey)) {
- baseModifiers[modKey].push(Math.floor(modValue));
- } else {
-
- baseModifiers[modKey].push(Math.floor(modValue));
- }
-
- console.log(`Adding Base modifier: ${modifier.name} (${modValue}) from ${modifier.tree}:${modifier.anchor}`);
- }
- });
-
- for (const [key, values] of Object.entries(baseModifiers)) {
-
- if (percentageStats.includes(key)) {
- const totalValue = values.reduce((sum, val) => sum + val, 0);
- console.log(`Applying summed Base ${key} (percentage stat): ${totalValue}`);
-
- if (totals[key] !== undefined) {
- totals[key] += totalValue;
- }
- } else {
-
- const totalValue = Math.floor(values.reduce((sum, val) => sum + val, 0));
- console.log(`Applying summed Base ${key}: ${totalValue}`);
-
- if (key === 'damage') {
-
- if (totals['min_damage'] !== undefined) {
- totals['min_damage'] += totalValue;
- }
- if (totals['max_damage'] !== undefined) {
- totals['max_damage'] += totalValue;
- }
- } else if (key === 'block') {
-
- if (totals['block_amount'] !== undefined) {
- totals['block_amount'] += totalValue;
- }
- } else {
-
- if (totals[key] !== undefined) {
- totals[key] += totalValue;
- }
- }
- }
- }
-
- const baseMinusModifiers = {
- 'breach_barrier': [],
- 'barrier_breach': [],
- 'health_damage': [],
- 'damage_health': []
- };
-
- Object.values(selectedModifiers).forEach(modifier => {
- if (/^Base\s+/i.test(modifier.name) && /MINUS/i.test(modifier.name)) {
- const modStr = modifier.name.replace(/^Base\s+/i, '');
- const parts = modStr.split(/\s+MINUS\s+/i);
- if (parts.length !== 2) return;
-
- const stat1 = parts[0].toLowerCase().replace(/ /g, '_');
- const stat2 = parts[1].toLowerCase().replace(/ /g, '_');
- const modValue = parseFloat(modifier.value);
-
- const typeKey = `${stat1}_${stat2}`;
- if (baseMinusModifiers[typeKey] !== undefined) {
- baseMinusModifiers[typeKey].push({
- value: modValue,
- source: `${modifier.tree}:${modifier.anchor}`
- });
- console.log(`Collecting MINUS modifier: ${modifier.name} (${modValue}) from ${modifier.tree}:${modifier.anchor}`);
- }
- }
- });
-
- if (baseMinusModifiers['breach_barrier'].length > 0) {
- const values = baseMinusModifiers['breach_barrier'].map(mod => Math.floor(mod.value));
- const totalValue = values.reduce((sum, val) => sum + val, 0);
- console.log(`Applying BREACH MINUS BARRIER: ${values.join(' + ')} = ${totalValue}`);
-
- if (totals['breach'] !== undefined) totals['breach'] += totalValue;
- if (totals['barrier'] !== undefined) totals['barrier'] -= totalValue;
- }
-
- if (baseMinusModifiers['barrier_breach'].length > 0) {
- const values = baseMinusModifiers['barrier_breach'].map(mod => Math.floor(mod.value));
- const totalValue = values.reduce((sum, val) => sum + val, 0);
- console.log(`Applying BARRIER MINUS BREACH: ${values.join(' + ')} = ${totalValue}`);
-
- if (totals['breach'] !== undefined) totals['breach'] -= totalValue;
- if (totals['barrier'] !== undefined) totals['barrier'] += totalValue;
- }
-
- if (baseMinusModifiers['health_damage'].length > 0) {
- const values = baseMinusModifiers['health_damage'].map(mod => Math.floor(mod.value));
- const totalValue = values.reduce((sum, val) => sum + val, 0);
- console.log(`Applying HEALTH MINUS DAMAGE: ${values.join(' + ')} = ${totalValue}`);
-
- if (totals['health'] !== undefined) totals['health'] += totalValue;
- if (totals['min_damage'] !== undefined) totals['min_damage'] -= Math.floor(totalValue / 3);
- if (totals['max_damage'] !== undefined) totals['max_damage'] -= Math.floor(totalValue / 3);
- }
-
- if (baseMinusModifiers['damage_health'].length > 0) {
- const values = baseMinusModifiers['damage_health'].map(mod => Math.floor(mod.value));
- const totalValue = values.reduce((sum, val) => sum + val, 0);
- console.log(`Applying DAMAGE MINUS HEALTH: ${values.join(' + ')} = ${totalValue}`);
-
- if (totals['min_damage'] !== undefined) totals['min_damage'] += totalValue;
- if (totals['max_damage'] !== undefined) totals['max_damage'] += totalValue;
- if (totals['health'] !== undefined) totals['health'] -= Math.floor(totalValue * 3);
- }
- for (const key in totals) {
- if (totals[key] < 0) {
- console.log(`Fixing negative stat: ${key} from ${totals[key]} to 0`);
- totals[key] = 0;
- }
- }
-
- const incSums = {};
-
- Object.values(selectedModifiers).forEach(modifier => {
- if (/^Increase(d)?\s+/.test(modifier.name)) {
- const statKey = modifier.name.replace(/^Increase(d)?\s+/, '').toLowerCase().replace(/ /g, '_');
-
- if (!incSums[statKey]) incSums[statKey] = 0;
-
- const value = parseFloat(modifier.value);
- incSums[statKey] += value;
- console.log(`Adding Increased modifier: ${modifier.name} (${value}) from ${modifier.tree}:${modifier.anchor}`);
- }
- });
- for (const stat in incSums) {
-
- const totalIncrease = incSums[stat];
-
- const multiplier = 1 + totalIncrease / 100;
- console.log(`Applying Increased modifier for ${stat}: +${totalIncrease}% (x${multiplier.toFixed(2)})`);
-
- if (stat === 'damage') {
- if (totals['min_damage'] !== undefined) {
-
- totals['min_damage'] = Math.floor(totals['min_damage'] * multiplier);
- }
- if (totals['max_damage'] !== undefined) {
- totals['max_damage'] = Math.floor(totals['max_damage'] * multiplier);
- }
- } else if (stat === 'block') {
- if (totals['block_amount'] !== undefined) {
- totals['block_amount'] = Math.floor(totals['block_amount'] * multiplier);
- }
- } else {
- if (totals[stat] !== undefined) {
- totals[stat] = Math.floor(totals[stat] * multiplier);
- }
- }
- }
-
- console.log('Final calculated stats:', {...totals});
-
- const finalTotals = {};
- for (const key in totals) {
- finalTotals[key] = totals[key].toString();
- }
- return finalTotals;
- }
-
- function logSelectedModifiers() {
- console.log('Currently selected modifiers:');
- Object.entries(selectedModifiers).forEach(([key, mod]) => {
- console.log(`${key}: ${mod.name} (${mod.value})`);
- });
- }
-
- let monsterStats = {};
- let modifiersData = {};
- let userStats = {
- min_damage: 90,
- max_damage: 110,
- health: 800,
- breach: 120,
- barrier: 120,
- crit_chance: 5,
- crit_damage: 100,
- block_chance: 5,
- block_amount: 10
- };
- let autoMonsterStats = false;
- let currentFloor = 1;
-
- let simulationResultsContainer = null;
-
- function calculateDamageMultiplier(breach, barrier) {
- if (breach > barrier) {
- return 4 * (2 / Math.PI) * Math.atan((breach - barrier) / Math.sqrt(breach * barrier)) + 1;
- } else {
- return Math.max(0.25, (2 / Math.PI) * Math.atan((breach - barrier) / Math.sqrt(breach * barrier)) + 1);
- }
- }
-
- function calculateDamage(attacker, defender) {
-
- if (attacker === userStats) {
- attacker = calculateTotalStats();
- }
-
- const baseDamage = (parseFloat(attacker.min_damage) + parseFloat(attacker.max_damage)) / 2;
- const multiplier = calculateDamageMultiplier(parseFloat(attacker.breach), parseFloat(defender.barrier));
- let damage = baseDamage * multiplier;
-
- const critRoll = Math.random() * 100;
- if (critRoll <= parseFloat(attacker.crit_chance)) {
- damage = damage * (parseFloat(attacker.crit_damage) / 100);
- }
- const blockRoll = Math.random() * 100;
- if (blockRoll <= parseFloat(defender.block_chance)) {
- damage = Math.max(0, damage - parseFloat(defender.block_amount));
- }
-
- return Math.round(damage);
- }
- const simulationLogOverlay = document.createElement('div');
- simulationLogOverlay.style.cssText = `
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(22, 28, 36, 0.95);
- color: #ecf0f1;
- padding: 40px 20px 20px 20px;
- overflow-y: auto;
- display: none;
- z-index: 10000;
- box-sizing: border-box;
- `;
- simulationLogOverlay.id = 'simulation-log-overlay';
- const backButton = document.createElement('button');
- backButton.textContent = 'Back to results';
- backButton.style.cssText = `
- position: absolute;
- top: 10px;
- right: 10px;
- padding: 5px 10px;
- background: #e67e22;
- border: none;
- border-radius: 4px;
- color: white;
- cursor: pointer;
- `;
- backButton.addEventListener('click', () => {
- simulationLogOverlay.style.display = 'none';
- simulationResultsContainer.style.display = 'block';
- });
- simulationLogOverlay.appendChild(backButton);
-
- function simulateCombat() {
- const startTime = performance.now();
-
- const combatLog = document.createElement('div');
- combatLog.style.cssText = `
- margin-top: 20px;
- padding: 10px;
- background: rgba(44, 62, 80, 0.95);
- border-radius: 4px;
- max-height: 400px;
- overflow-y: auto;
- font-family: monospace;
- line-height: 1.4;
- `;
-
- const totalUserStats = calculateTotalStats();
-
- let userHealth = parseFloat(totalUserStats.health);
- let monsterHealth = parseFloat(monsterStats.health);
- let round = 1;
- let monsterDefeated = false;
-
- function logAttack(attacker, defender, isUser) {
- const baseDamage = (parseFloat(attacker.min_damage) + parseFloat(attacker.max_damage)) / 2;
- const multiplier = calculateDamageMultiplier(parseFloat(attacker.breach), parseFloat(defender.barrier));
- let damage = baseDamage * multiplier;
-
- const critRoll = Math.random() * 100;
- const isCrit = critRoll <= parseFloat(attacker.crit_chance);
- if (isCrit) {
- damage = damage * (parseFloat(attacker.crit_damage) / 100);
- }
- const blockRoll = Math.random() * 100;
- const isBlocked = blockRoll <= parseFloat(defender.block_chance);
- const originalDamage = damage;
- if (isBlocked) {
- damage = Math.max(0, damage - parseFloat(defender.block_amount));
- }
-
- const color = isUser ? '#2ecc71' : '#e74c3c';
- const attacker_name = isUser ? 'You' : 'Monster';
- const defender_name = isUser ? 'Monster' : 'You';
-
- let log = `<div style="color: ${color}; margin-bottom: 10px;">`;
- log += `<div><b>Round ${round} - ${attacker_name}'s Attack</b></div>`;
- log += `<div>Base Damage: ${Math.round(baseDamage)} (${attacker.min_damage}-${attacker.max_damage})</div>`;
- log += `<div>Breach vs Barrier: ${attacker.breach} vs ${defender.barrier} (x${multiplier.toFixed(2)})</div>`;
-
- if (isCrit) {
- log += `<div>Critical Hit! (${attacker.crit_chance}% chance, +${attacker.crit_damage}% damage)</div>`;
- }
- if (isBlocked) {
- log += `<div>Attack Blocked! (${defender.block_chance}% chance, ${defender.block_amount} blocked)</div>`;
- log += `<div>Damage Reduced: ${Math.round(originalDamage)} → ${Math.round(damage)}</div>`;
- }
-
- const finalDamage = Math.round(damage);
- const newHealth = isUser ? Math.max(0, monsterHealth - finalDamage) : Math.max(0, userHealth - finalDamage);
- log += `<div><b>Final Damage: ${finalDamage}</b></div>`;
- log += `<div>${defender_name}'s Health: ${isUser ? monsterHealth : userHealth} → ${newHealth}</div>`;
- log += '</div>';
-
- combatLog.innerHTML += log;
- return finalDamage;
- }
-
- while (userHealth > 0) {
- const userDamage = logAttack(totalUserStats, monsterStats, true);
- monsterHealth -= userDamage;
-
- if (monsterHealth <= 0) {
- monsterDefeated = true;
- break;
- }
-
- const monsterDamage = logAttack(monsterStats, totalUserStats, false);
- userHealth -= monsterDamage;
-
- round++;
- }
-
- const winner = monsterDefeated ? 'You win!' : 'Monster wins!';
- combatLog.innerHTML += `<div style="color: #f1c40f; margin-top: 10px; font-weight: bold; font-size: 1.2em;">${winner}</div>`;
- const endTime = performance.now();
- console.log(`Single simulation time: ${(endTime - startTime).toFixed(6)} ms`);
-
- return combatLog;
- }
-
- async function fetchModifiers() {
- try {
- const response = await fetch('https://elethor.com/game/neo-spire/modifiers');
- const data = await response.json();
- return data.modifiers || {};
- } catch (error) {
- console.error('Error fetching modifiers:', error);
- return null;
- }
- }
-
- function createModifiersList(modifiers) {
- selectedModifiers = {};
- selectedModifiers = loadFromLocalStorage('selectedModifiers') || {};
-
- const container = document.createElement('div');
-
- container.style.cssText = `
- display: flex;
- gap: 20px;
- padding: 10px;
- font-family: Arial, sans-serif;
- color: #ecf0f1;
- max-height: 80vh;
- overflow-y: auto;
- width: 600px;
- `;
-
- ['Defense', 'Offense'].forEach(tree => {
- const treeData = modifiers[tree];
- if (!treeData) return;
-
- const treeSection = document.createElement('div');
- treeSection.style.cssText = `flex: 1; min-width: 0;`;
- treeSection.innerHTML = `<h3 style="color: #3498db; margin: 10px 0;">${tree}</h3>`;
-
- Object.entries(treeData).forEach(([anchor, options]) => {
- const anchorSection = document.createElement('div');
- anchorSection.style.cssText = `
- margin-bottom: 15px;
- padding: 8px;
- background: rgba(44, 62, 80, 0.5);
- border-radius: 4px;
- `;
-
- const headerContainer = document.createElement('div');
- headerContainer.style.cssText = `
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 5px;
- `;
-
- const anchorName = document.createElement('div');
- anchorName.style.cssText = `color: #f1c40f;`;
- anchorName.textContent = anchor;
- headerContainer.appendChild(anchorName);
-
- const deselectButton = document.createElement('button');
- deselectButton.textContent = 'Deselect';
- deselectButton.style.cssText = `
- padding: 2px 6px;
- background: #e74c3c;
- border: none;
- border-radius: 3px;
- color: white;
- cursor: pointer;
- font-size: 0.8em;
- transition: background 0.2s;
- `;
- deselectButton.addEventListener('mouseenter', () => { deselectButton.style.background = '#c0392b'; });
- deselectButton.addEventListener('mouseleave', () => { deselectButton.style.background = '#e74c3c'; });
-
- deselectButton.addEventListener('click', () => {
-
- optionsContainer.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
- checkbox.checked = false;
- });
-
- const compositeKey = `${tree}:${anchor}`;
- if (selectedModifiers[compositeKey]) {
- delete selectedModifiers[compositeKey];
- console.log(`Deselected modifier at ${compositeKey}`);
- console.log('Remaining selected modifiers:', {...selectedModifiers});
-
- saveToLocalStorage('selectedModifiers', selectedModifiers);
- }
- });
- headerContainer.appendChild(deselectButton);
- anchorSection.appendChild(headerContainer);
-
- const optionsContainer = document.createElement('div');
- optionsContainer.style.cssText = `
- display: flex;
- flex-wrap: wrap;
- gap: 10px;
- margin-top: 5px;
- `;
-
- const radioGroupName = `${tree.toLowerCase()}-${anchor.replace(/\s+/g, '-').toLowerCase()}`;
-
- options.forEach(option => {
- const optionDiv = document.createElement('div');
- optionDiv.style.cssText = `
- padding: 5px;
- border-left: 2px solid #3498db;
- padding-left: 8px;
- flex: 1;
- min-width: 150px;
- max-width: calc(50% - 5px);
- cursor: pointer;
- transition: background-color 0.3s;
- display: flex;
- align-items: center;
- `;
-
- const checkbox = document.createElement('input');
- checkbox.type = 'checkbox';
- checkbox.style.marginRight = '8px';
- checkbox.dataset.tree = tree;
- checkbox.dataset.anchor = anchor;
- checkbox.dataset.optionIndex = options.indexOf(option);
-
- checkbox.addEventListener('change', (e) => {
-
- if (e.target.checked) {
- optionsContainer.querySelectorAll('input[type="checkbox"]').forEach(cb => {
- if (cb !== e.target) {
- cb.checked = false;
- }
- });
-
- const compositeKey = `${tree}:${anchor}`;
- selectedModifiers[compositeKey] = { ...option, tree: tree, anchor: anchor };
-
- optionsContainer.querySelectorAll('div').forEach(div => {
- div.style.backgroundColor = '';
- });
- optionDiv.style.backgroundColor = '#2980b9';
-
- console.log('Selected modifier:', compositeKey, selectedModifiers[compositeKey]);
- } else {
-
- const compositeKey = `${tree}:${anchor}`;
- if (selectedModifiers[compositeKey]) {
- delete selectedModifiers[compositeKey];
- optionDiv.style.backgroundColor = '';
- console.log(`Deselected modifier at ${compositeKey}`);
- }
- }
-
- saveToLocalStorage('selectedModifiers', selectedModifiers);
-
- console.log('All selected modifiers:', {...selectedModifiers});
- });
- optionDiv.appendChild(checkbox);
-
- const textContainer = document.createElement('div');
- textContainer.style.cssText = 'flex: 1;';
- textContainer.innerHTML = `
- <div style="font-weight: bold;">${option.name}</div>
- <div style="color: #95a5a6; font-size: 0.9em;">Cost: ${option.cost} | Value: ${option.value}</div>
- `;
-
- optionDiv.appendChild(textContainer);
-
- optionDiv.addEventListener('click', (e) => {
-
- if (e.target !== checkbox) {
- checkbox.checked = !checkbox.checked;
-
- const changeEvent = new Event('change', { bubbles: true });
- checkbox.dispatchEvent(changeEvent);
- }
- });
-
- optionsContainer.appendChild(optionDiv);
- });
-
- anchorSection.appendChild(optionsContainer);
- treeSection.appendChild(anchorSection);
- });
-
- container.appendChild(treeSection);
- });
-
- const savedModifiers = loadFromLocalStorage('selectedModifiers') || {};
- Object.entries(savedModifiers).forEach(([compositeKey, modData]) => {
- const [tree, anchor] = compositeKey.split(':');
-
- const options = modifiers[tree]?.[anchor] || [];
- const matchingOption = options.find(opt =>
- opt.name === modData.name &&
- opt.value === modData.value
- );
-
- if (matchingOption) {
- const optionIndex = options.indexOf(matchingOption);
-
- const checkbox = container.querySelector(
- `input[data-tree="${tree}"][data-anchor="${anchor}"][data-option-index="${optionIndex}"]`
- );
-
- if (checkbox) {
- checkbox.checked = true;
-
- const parentDiv = checkbox.closest('div');
- if (parentDiv) {
- parentDiv.style.backgroundColor = '#2980b9';
- }
-
- selectedModifiers[compositeKey] = {
- ...matchingOption,
- tree: tree,
- anchor: anchor
- };
- }
- }
- });
-
- return container;
- }
-
- async function fetchMonsterStats(floor) {
- try {
- const response = await fetch(`https://elethor.com/game/neo-spire/${floor}`);
- const data = await response.json();
- if (data && data.floor && data.floor.stats) {
- return data.floor.stats;
- } else {
- console.error('Invalid data format from API');
- return null;
- }
- } catch (error) {
- console.error('Error fetching monster stats:', error);
- return null;
- }
- }
-
-
- function updateMonsterStatInputs(stats) {
- if (!stats) return;
- document.querySelectorAll('input[data-stat]').forEach(input => {
- if (input.dataset.type === 'monster') {
- const statKey = input.dataset.stat.toLowerCase().replace(/ /g, '_');
- if (stats[statKey] !== undefined) {
- input.value = stats[statKey];
- const event = new Event('input', { bubbles: true });
- input.dispatchEvent(event);
- }
- }
- });
- }
- function updateStatInputs() {
- document.querySelectorAll('input[data-stat]').forEach(input => {
- const statKey = input.dataset.stat.toLowerCase().replace(/ /g, '_');
-
- if (input.dataset.type === 'monster') {
- if (monsterStats[statKey] !== undefined) {
- input.value = monsterStats[statKey];
- const event = new Event('input', { bubbles: true });
- input.dispatchEvent(event);
- }
- } else if (input.dataset.type === 'user') { // Update "You" stats
- if (userStats[statKey] !== undefined) {
- input.value = userStats[statKey];
- const event = new Event('input', { bubbles: true });
- input.dispatchEvent(event);
- }
- }
- });
- }
-
-
- function updateTotalStats() {
- const totalStats = calculateTotalStats();
- document.querySelectorAll('span[data-stat]').forEach(span => {
- const stat = span.dataset.stat.toLowerCase().replace(/ /g, '_');
- span.textContent = totalStats[stat] || '-';
- });
- }
-
-
- function createStatsTable() {
- const tableContainer = document.createElement('div');
- tableContainer.style.cssText = `
- padding: 10px;
- font-family: Arial, sans-serif;
- color: #ecf0f1;
- flex: 1;
- `;
- const table = document.createElement('table');
- table.style.cssText = `
- border-collapse: collapse;
- width: 100%;
- `;
-
- const autoStatsContainer = document.createElement('div');
- autoStatsContainer.style.cssText = `
- display: flex;
- align-items: center;
- margin-bottom: 10px;
- padding: 8px;
- background: rgba(44, 62, 80, 0.5);
- border-radius: 4px;
- `;
- const autoStatsCheckbox = document.createElement('input');
- autoStatsCheckbox.type = 'checkbox';
- autoStatsCheckbox.id = 'auto-monster-stats';
- autoStatsCheckbox.style.cssText = `margin-right: 8px;`;
- const autoStatsLabel = document.createElement('label');
- autoStatsLabel.htmlFor = 'auto-monster-stats';
- autoStatsLabel.textContent = 'Auto Monster Stats';
- autoStatsLabel.style.cssText = `margin-right: 15px; color: #3498db;`;
- const floorLabel = document.createElement('label');
- floorLabel.htmlFor = 'floor-input';
- floorLabel.textContent = 'Floor:';
- floorLabel.style.cssText = `margin-right: 8px; color: #3498db;`;
- const floorInput = document.createElement('input');
- floorInput.type = 'number';
- floorInput.id = 'floor-input';
- floorInput.min = '1';
- floorInput.value = '1';
- floorInput.style.cssText = `
- width: 60px;
- background: #2c3e50;
- border: 1px solid #34495e;
- color: #ecf0f1;
- padding: 4px;
- border-radius: 3px;
- `;
- const fetchButton = document.createElement('button');
- fetchButton.textContent = 'Fetch';
- fetchButton.style.cssText = `
- margin-left: 8px;
- padding: 4px 8px;
- background: #3498db;
- border: none;
- border-radius: 4px;
- color: white;
- cursor: pointer;
- font-weight: bold;
- transition: background 0.3s;
- `;
- fetchButton.addEventListener('mouseenter', () => { fetchButton.style.background = '#2980b9'; });
- fetchButton.addEventListener('mouseleave', () => { fetchButton.style.background = '#3498db'; });
- autoStatsCheckbox.addEventListener('change', async (e) => {
- autoMonsterStats = e.target.checked;
- document.querySelectorAll('input[data-type="monster"]').forEach(input => {
- input.disabled = autoMonsterStats;
- input.style.opacity = autoMonsterStats ? '0.5' : '1';
- });
- if (autoMonsterStats) {
- currentFloor = parseInt(floorInput.value) || 1;
- const stats = await fetchMonsterStats(currentFloor);
- if (stats) { updateMonsterStatInputs(stats); }
- }
- });
- floorInput.addEventListener('input', (e) => { currentFloor = parseInt(e.target.value) || 1; });
- fetchButton.addEventListener('click', async () => {
- if (autoMonsterStats) {
- currentFloor = parseInt(floorInput.value) || 1;
- const stats = await fetchMonsterStats(currentFloor);
- if (stats) { updateMonsterStatInputs(stats); }
- }
- });
- autoStatsContainer.appendChild(autoStatsCheckbox);
- autoStatsContainer.appendChild(autoStatsLabel);
- autoStatsContainer.appendChild(floorLabel);
- autoStatsContainer.appendChild(floorInput);
- autoStatsContainer.appendChild(fetchButton);
- tableContainer.appendChild(autoStatsContainer);
-
- const simulationControls = document.createElement('div');
- simulationControls.style.cssText = 'margin-bottom: 10px; display: flex; align-items: center; gap: 10px;';
- const numSimLabel = document.createElement('label');
- numSimLabel.htmlFor = 'num-simulations';
- numSimLabel.textContent = 'Number of simulations:';
- numSimLabel.style.cssText = 'color: #3498db;';
- simulationControls.appendChild(numSimLabel);
- const numSimInput = document.createElement('input');
- numSimInput.type = 'number';
- numSimInput.id = 'num-simulations';
- numSimInput.value = '10';
- numSimInput.style.cssText = `
- width: 60px;
- background: #2c3e50;
- border: 1px solid #34495e;
- color: #ecf0f1;
- padding: 4px;
- border-radius: 3px;
- `;
- simulationControls.appendChild(numSimInput);
- tableContainer.appendChild(simulationControls);
-
- const header = document.createElement('tr');
- ['STAT', 'MONSTER', 'YOU', 'TOTAL YOU'].forEach(text => {
- const th = document.createElement('th');
- th.textContent = text;
- th.style.cssText = `
- padding: 8px;
- text-align: left;
- border-bottom: 2px solid #34495e;
- color: #3498db;
- `;
- header.appendChild(th);
- });
- table.appendChild(header);
-
- stats.forEach(stat => {
- const row = document.createElement('tr');
- const nameCell = document.createElement('td');
- nameCell.textContent = stat;
- nameCell.style.cssText = `padding: 8px; border-bottom: 1px solid #34495e;`;
- const monsterCell = document.createElement('td');
- const monsterInput = document.createElement('input');
- monsterInput.type = 'text';
- monsterInput.dataset.stat = stat;
- monsterInput.dataset.type = 'monster';
- monsterInput.addEventListener('input', (e) => {
- const value = e.target.value;
- const statKey = stat.toLowerCase().replace(/ /g, '_');
- monsterStats[statKey] = value;
-
- saveToLocalStorage('monsterStats', monsterStats);
- console.log('Monster stats updated:', monsterStats);
- });
-
- monsterInput.style.cssText = `
- width: 80px;
- background: #2c3e50;
- border: 1px solid #34495e;
- color: #ecf0f1;
- padding: 4px;
- border-radius: 3px;
- `;
- monsterCell.appendChild(monsterInput);
- monsterCell.style.cssText = `padding: 8px; border-bottom: 1px solid #34495e;`;
- const yourCell = document.createElement('td');
- const yourInput = document.createElement('input');
- yourInput.type = 'text';
- yourInput.dataset.stat = stat;
- yourInput.addEventListener('input', (e) => {
- const value = e.target.value;
- const statKey = stat.toLowerCase().replace(/ /g, '_');
- userStats[statKey] = value;
-
- console.log('User stats updated:', userStats);
- });
- yourInput.style.cssText = `
- width: 80px;
- background: #2c3e50;
- border: 1px solid #34495e;
- color: #ecf0f1;
- padding: 4px;
- border-radius: 3px;
- `;
- yourCell.appendChild(yourInput);
- yourCell.style.cssText = `padding: 8px; border-bottom: 1px solid #34495e;`;
- row.appendChild(nameCell);
- row.appendChild(monsterCell);
- row.appendChild(yourCell);
- const totalCell = document.createElement('td');
- const totalSpan = document.createElement('span');
- totalSpan.dataset.stat = stat;
- totalSpan.textContent = '-';
- totalSpan.style.cssText = `color: #2ecc71; font-weight: bold;`;
- totalCell.appendChild(totalSpan);
- totalCell.style.cssText = `padding: 8px; border-bottom: 1px solid #34495e;`;
- row.appendChild(totalCell);
- row.addEventListener('mouseenter', () => { row.style.backgroundColor = 'rgba(52, 73, 94, 0.5)'; });
- row.addEventListener('mouseleave', () => { row.style.backgroundColor = ''; });
- table.appendChild(row);
- });
- tableContainer.appendChild(table);
-
- const simulateButton = document.createElement('button');
- simulateButton.textContent = 'Simulate Combat';
- simulateButton.style.cssText = `
- margin-top: 10px;
- padding: 8px 16px;
- background: #2ecc71;
- border: none;
- border-radius: 4px;
- color: white;
- cursor: pointer;
- width: 100%;
- font-weight: bold;
- transition: background 0.3s;
- `;
- simulateButton.addEventListener('mouseenter', () => { simulateButton.style.background = '#27ae60'; });
- simulateButton.addEventListener('mouseleave', () => { simulateButton.style.background = '#2ecc71'; });
- simulateButton.addEventListener('click', () => {
-
- simulationResultsContainer.innerHTML = '';
- simulationLogs = [];
- monsterWins = 0;
- userWins = 0;
- const totalUserStats = calculateTotalStats();
-
- const numSims = parseInt(document.getElementById('num-simulations').value) || 1;
- for (let i = 0; i < numSims; i++) {
- const combatLog = simulateCombat();
-
- combatLog.style.display = 'none';
- combatLog.id = `combat-log-${i}`;
- simulationLogs.push(combatLog);
-
- const finalLine = combatLog.querySelector('div[style*="color: #f1c40f"]');
- const resultText = finalLine ? finalLine.textContent : 'Unknown';
- if (resultText === 'You win!') {
- userWins++;
- } else if (resultText === 'Monster wins!') {
- monsterWins++;
- }
- const simResultDiv = document.createElement('div');
- simResultDiv.style.cssText = 'margin-bottom: 10px; padding: 5px; background: rgba(44, 62, 80, 0.5); border-radius: 4px;';
- simResultDiv.innerHTML = `<strong>Simulation ${i + 1}:</strong> ${resultText}`;
- const logsButton = document.createElement('button');
- logsButton.textContent = 'Logs';
- logsButton.style.cssText = 'margin-left: 10px; padding: 2px 6px;';
- logsButton.addEventListener('click', () => {
- simulationResultsContainer.style.display = 'none';
- simulationLogOverlay.innerHTML = '';
- simulationLogOverlay.appendChild(backButton);
- simulationLogOverlay.appendChild(simulationLogs[i]);
- simulationLogs[i].style.display = 'block';
- simulationLogOverlay.style.display = 'block';
- });
- simResultDiv.appendChild(logsButton);
- simulationResultsContainer.appendChild(simResultDiv);
- }
- const statsDiv = document.createElement('div');
- statsDiv.style.cssText = `
- margin-bottom: 15px;
- padding: 10px;
- background: rgba(44, 62, 80, 0.8);
- border-radius: 4px;
- font-weight: bold;
- display: flex;
- justify-content: space-between;
- `;
-
- const winrate = (userWins / numSims * 100).toFixed(2);
-
- statsDiv.innerHTML = `
- <div style="color: #e74c3c;">Monster Wins: ${monsterWins}</div>
- <div style="color: #2ecc71;">Your Wins: ${userWins}</div>
- <div style="color: #f1c40f;">Winrate: ${winrate}%</div>
- `;
-
-
- simulationResultsContainer.insertBefore(statsDiv, simulationResultsContainer.firstChild);
- });
- tableContainer.appendChild(simulateButton);
-
- const confirmButton = document.createElement('button');
- confirmButton.textContent = 'Confirm Modifiers';
- confirmButton.style.cssText = `
- margin-top: 10px;
- margin-bottom: 10px;
- padding: 8px 16px;
- background: #e67e22;
- border: none;
- border-radius: 4px;
- color: white;
- cursor: pointer;
- width: 100%;
- font-weight: bold;
- transition: background 0.3s;
- `;
- confirmButton.addEventListener('mouseenter', () => { confirmButton.style.background = '#d35400'; });
- confirmButton.addEventListener('mouseleave', () => { confirmButton.style.background = '#e67e22'; });
- confirmButton.addEventListener('click', () => {
- logSelectedModifiers();
- updateTotalStats();
- });
- tableContainer.appendChild(confirmButton);
-
- let isDragging = false, currentX, currentY, initialX, initialY, xOffset = 0, yOffset = 0;
- tableContainer.addEventListener('mousedown', dragStart);
- document.addEventListener('mousemove', drag);
- document.addEventListener('mouseup', dragEnd);
- function dragStart(e) {
- initialX = e.clientX - xOffset;
- initialY = e.clientY - yOffset;
- if (e.target === tableContainer) { isDragging = true; }
- }
- function drag(e) {
- if (isDragging) {
- e.preventDefault();
- currentX = e.clientX - initialX;
- currentY = e.clientY - initialY;
- xOffset = currentX;
- yOffset = currentY;
- setTranslate(currentX, currentY, tableContainer);
- }
- }
- function setTranslate(xPos, yPos, el) {
- el.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`;
- }
- function dragEnd() {
- initialX = currentX;
- initialY = currentY;
- isDragging = false;
- }
-
- document.body.appendChild(tableContainer);
- }
-
- window.addEventListener('load', async () => {
- const mainContainer = document.createElement('div');
- const closeButton = document.createElement('button');
- closeButton.textContent = 'X';
- closeButton.style.cssText = `
- position: absolute;
- top: 10px;
- right: 10px;
- padding: 5px 10px;
- background: #e74c3c;
- border: none;
- border-radius: 4px;
- color: white;
- cursor: pointer;
- font-weight: bold;
- z-index: 10001;
- `;
- closeButton.addEventListener('click', () => {
- mainContainer.style.display = 'none';
- showOverlayButton.style.display = 'block';
- });
-
- mainContainer.appendChild(closeButton);
- const showOverlayButton = document.createElement('button');
- showOverlayButton.textContent = 'Open Spire Simulator';
- showOverlayButton.style.cssText = `
- position: fixed;
- top: 10px;
- right: 10px;
- padding: 5px 10px;
- background: #2ecc71;
- border: none;
- border-radius: 4px;
- color: white;
- cursor: pointer;
- font-weight: bold;
- z-index: 9998;
- display: none;
- `;
- showOverlayButton.addEventListener('click', () => {
- mainContainer.style.display = 'block';
- showOverlayButton.style.display = 'none';
- });
- document.body.appendChild(showOverlayButton);
-
- const savedUserStats = {};
- const savedMonsterStats = loadFromLocalStorage('monsterStats') || {};
-
- userStats = savedUserStats;
- monsterStats = savedMonsterStats;
-
- mainContainer.style.cssText = `
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(22, 28, 36, 0.95);
- z-index: 9999;
- box-shadow: 0 0 10px rgba(0,0,0,0.5);
- overflow: auto;
- `;
-
- const contentContainer = document.createElement('div');
- contentContainer.style.cssText = `
- display: flex;
- flex-direction: row;
- gap: 20px;
- padding: 20px;
- `;
-
- simulationResultsContainer = document.createElement('div');
- simulationResultsContainer.id = 'simulation-results';
- simulationResultsContainer.style.cssText = `
- flex: 1;
- background: rgba(22, 28, 36, 0.95);
- color: #ecf0f1;
- padding: 10px;
- border: 1px solid #2c3e50;
- border-radius: 4px;
- max-height: 400px;
- overflow-y: auto;
- `;
- contentContainer.appendChild(simulationResultsContainer);
-
- createStatsTable();
- document.querySelectorAll('input[data-stat]').forEach(input => {
- const statKey = input.dataset.stat.toLowerCase().replace(/ /g, '_');
-
- if (input.dataset.type === 'monster' && monsterStats[statKey] !== undefined) {
- input.value = monsterStats[statKey];
- const event = new Event('input', { bubbles: true });
- input.dispatchEvent(event);
- }
-
- if (input.dataset.type === 'user' && userStats[statKey] !== undefined) {
- input.value = userStats[statKey];
- const event = new Event('input', { bubbles: true });
- input.dispatchEvent(event);
- }
- });
-
- const statsTable = document.querySelector('div[style*="font-family: Arial"]');
- if (statsTable) {
- contentContainer.appendChild(statsTable);
- }
-
- const modifiers = await fetchModifiers();
- if (modifiers) {
- modifiersData = modifiers;
- const modifiersList = createModifiersList(modifiers);
- contentContainer.appendChild(modifiersList);
- }
-
- mainContainer.appendChild(contentContainer);
-
- document.body.appendChild(simulationLogOverlay);
-
- document.body.appendChild(mainContainer);
-
- });
- })();