ToE Counter Picker & AutoToE for HWH Tweaker
Este script não deve ser instalado diretamente. Este script é uma biblioteca de outros scripts para incluir com o diretório meta // @require https://update.greasyfork.org/scripts/569616/1774147/HWHT%20ToE%20Module.js
// ==UserScript==
// @name HWHT ToE Module
// @namespace http://tampermonkey.net/
// @version 1.0.0
// @description ToE Counter Picker & AutoToE for HWH Tweaker
// @author AI Assistant
// @license MIT
// @match https://www.hero-wars.com/*
// @match https://apps-1701433570146040.apps.fbsbx.com/*
// @run-at document-idle
// @grant unsafeWindow
// ==/UserScript==
// debugLog stub - uses Tweaker's version when available, falls back to console
function debugLog(...args) {
if (window._tweakerDebugLog) return window._tweakerDebugLog(...args);
if (localStorage.getItem('hwh_debug_mode') === 'true') console.log('[ToE]', ...args);
}
// Wait for game dependencies
(function waitForDeps() {
const check = () => {
return typeof cheats !== 'undefined' && cheats.translate &&
typeof Caller !== 'undefined' && Caller.send &&
typeof getToeCounterData === 'function';
};
if (check()) { initToEModule(); return; }
let attempts = 0;
const timer = setInterval(() => {
attempts++;
if (check()) { clearInterval(timer); initToEModule(); }
else if (attempts > 60) { clearInterval(timer); console.warn('[ToE Module] Dependencies not found after 120s'); }
}, 2000);
})();
function initToEModule() {
//
// ToE Counter Picker v2 - Smart team selection with auto-retry
//
const ToeCounterPicker = (() => {
let counterDB = {};
let titanNameToId = {};
let titanIdToName = {};
let initialized = false;
let ownedTitanIds = [];
// Retry tracking
let triedCounters = {}; // { rivalId: Set of tried keys }
let currentRivalId = null; // Who we're currently fighting
let currentCounter = null; // What counter we used
let retryInProgress = false; // Prevent double-retry
// Build titan name lookups from game library
const buildTitanMappings = () => {
titanNameToId = {};
titanIdToName = {};
if (typeof cheats === 'undefined' || !cheats.translate) return false;
for (let id = 4000; id < 4050; id++) {
try {
const name = cheats.translate('LIB_HERO_NAME_' + id);
if (name && !name.startsWith('LIB_HERO_NAME_')) {
titanNameToId[name.toLowerCase()] = id;
titanIdToName[id] = name;
}
} catch (e) {}
}
debugLog(`[ToE] Built mappings for ${Object.keys(titanIdToName).length} titans`);
return true;
};
// Load counter database
const loadCounterData = () => {
const TOE_COUNTER_DATA = getToeCounterData();
if (!TOE_COUNTER_DATA) return false;
buildTitanMappings();
counterDB = {};
for (const [winRate, attackerStr, defenderStr] of TOE_COUNTER_DATA) {
const attackerNames = attackerStr.split(',');
const defenderNames = defenderStr.split(',');
const attackerIds = attackerNames.map(n => titanNameToId[n.toLowerCase()]).filter(Boolean);
const defenderIds = defenderNames.map(n => titanNameToId[n.toLowerCase()]).filter(Boolean);
if (attackerIds.length !== 5 || defenderIds.length !== 5) continue;
const defKey = defenderIds.slice().sort((a,b) => a-b).join('_');
if (!counterDB[defKey]) counterDB[defKey] = [];
const attackKey = attackerIds.slice().sort((a,b) => a-b).join('_');
if (!counterDB[defKey].some(c => c.key === attackKey)) {
counterDB[defKey].push({ attackerNames, attackerIds, winRate, key: attackKey });
}
}
for (const defKey in counterDB) {
counterDB[defKey].sort((a, b) => b.winRate - a.winRate);
}
initialized = true;
console.log(`[ToE] Loaded ${Object.keys(counterDB).length} defender teams`);
return true;
};
// Get owned titan IDs from multiple sources
const getOwnedTitanIds = () => {
if (ownedTitanIds.length > 0) return ownedTitanIds;
if (window._myTitans?.length > 0) { ownedTitanIds = window._myTitans; return ownedTitanIds; }
if (window.gameLoadData?.titans) {
const ids = Object.keys(window.gameLoadData.titans).map(id => parseInt(id));
if (ids.length > 0) { ownedTitanIds = ids; return ids; }
}
return [];
};
const loadOwnedTitans = async () => {
try {
const titans = await Caller.send('titanGetAll');
ownedTitanIds = Object.keys(titans).map(id => parseInt(id));
window._myTitans = ownedTitanIds;
window._myTitansFull = Object.values(titans);
debugLog(`[ToE] Loaded ${ownedTitanIds.length} owned titans`);
if (Object.keys(counterDB).length < 200) loadCounterData();
} catch (e) {}
};
// Element fallback
const ELEMENT_MAP = { 0: 'water', 1: 'fire', 2: 'earth', 3: 'dark', 4: 'light' };
const ELEMENT_ADVANTAGE = { water: 'fire', fire: 'earth', earth: 'water', dark: 'light', light: 'dark' };
const getTitanElement = (id) => ELEMENT_MAP[Math.floor((id % 100) / 10)] || 'neutral';
const getElementFallbackTeam = (enemyTitanIds) => {
let titans = window._myTitansFull?.length > 0 ? [...window._myTitansFull] :
window.gameLoadData?.titans ? Object.values(window.gameLoadData.titans) : null;
if (!titans || titans.length < 5) return null;
const enemyElements = {};
for (const id of enemyTitanIds) {
const elem = getTitanElement(id);
enemyElements[elem] = (enemyElements[elem] || 0) + 1;
}
const dominantEnemy = Object.entries(enemyElements).sort((a, b) => b[1] - a[1])[0]?.[0];
const counterElement = ELEMENT_ADVANTAGE[dominantEnemy];
titans.sort((a, b) => {
const elemA = getTitanElement(a.id), elemB = getTitanElement(b.id);
const scoreA = elemA === counterElement ? 2 : (elemA === 'dark' || elemA === 'light' ? 1 : 0);
const scoreB = elemB === counterElement ? 2 : (elemB === 'dark' || elemB === 'light' ? 1 : 0);
if (scoreA !== scoreB) return scoreB - scoreA;
return (b.power || 0) - (a.power || 0);
});
const team = titans.slice(0, 5).map(t => t.id);
const names = team.map(id => titanIdToName[id] || `#${id}`);
return { team, names, winRate: 0, matchType: `element (vs ${dominantEnemy})`, key: 'element_' + dominantEnemy };
};
// Find counter, skipping already-tried ones
const findCounter = (enemyTitanIds, rivalId = null) => {
if (!initialized) loadCounterData();
if (!enemyTitanIds || enemyTitanIds.length !== 5) return null;
const owned = new Set(getOwnedTitanIds());
const tried = triedCounters[rivalId] || new Set();
const enemySet = new Set(enemyTitanIds);
const defKey = enemyTitanIds.slice().sort((a,b) => a-b).join('_');
// 1. Exact matches
const exactCounters = counterDB[defKey] || [];
for (const counter of exactCounters) {
if (tried.has(counter.key)) {
debugLog(`[ToE] Skip tried: ${counter.attackerNames.join(', ')}`);
continue;
}
if (counter.attackerIds.every(id => owned.has(id))) {
return { team: counter.attackerIds, names: counter.attackerNames,
winRate: counter.winRate, matchType: 'exact', key: counter.key };
}
}
// 2. Fuzzy matches (4/5)
let bestFuzzy = null;
for (const [key, counters] of Object.entries(counterDB)) {
const dbTitans = key.split('_').map(Number);
const matchCount = dbTitans.filter(id => enemySet.has(id)).length;
if (matchCount >= 4) {
for (const counter of counters) {
if (tried.has(counter.key)) continue;
if (counter.attackerIds.every(id => owned.has(id))) {
const adjustedRate = Math.round(counter.winRate * (matchCount / 5));
if (!bestFuzzy || adjustedRate > bestFuzzy.winRate) {
bestFuzzy = { team: counter.attackerIds, names: counter.attackerNames,
winRate: adjustedRate, matchType: `fuzzy (${matchCount}/5)`, key: counter.key };
}
}
}
}
}
if (bestFuzzy && !tried.has(bestFuzzy.key)) return bestFuzzy;
// 3. Element fallback
const elementKey = 'element_fallback';
if (!tried.has(elementKey)) {
const elem = getElementFallbackTeam(enemyTitanIds);
if (elem) { elem.key = elementKey; return elem; }
}
return null;
};
// Get counter for rival object
const getCounterForRival = (rival, rivalId) => {
if (!rival?.titans) return null;
let titanList = Array.isArray(rival.titans) ? rival.titans : Object.values(rival.titans);
const enemyIds = titanList.map(t => parseInt(t.id || t));
return findCounter(enemyIds, rivalId);
};
// Retry management
const markTried = (rivalId, key) => {
if (!triedCounters[rivalId]) triedCounters[rivalId] = new Set();
triedCounters[rivalId].add(key);
console.log(`[ToE] Marked tried: ${key}`);
};
const clearTried = () => { triedCounters = {}; currentRivalId = null; currentCounter = null; };
const setCurrentBattle = (rivalId, counter) => { currentRivalId = rivalId; currentCounter = counter; };
const getCurrentBattle = () => ({ rivalId: currentRivalId, counter: currentCounter });
// Auto-retry on loss
const handleBattleEnd = async (win, cachedRivals) => {
if (retryInProgress) return;
const { rivalId, counter } = getCurrentBattle();
if (!rivalId || !counter) return;
if (win) {
console.log(`[ToE] ✓ Won with ${counter.names.join(', ')}`);
return;
}
// Lost - mark and retry
console.log(`[ToE] ✗ Lost with ${counter.names.join(', ')}`);
markTried(rivalId, counter.key);
const rival = cachedRivals?.[rivalId];
if (!rival) return;
const nextCounter = getCounterForRival(rival, rivalId);
if (!nextCounter) {
console.log('[ToE] No more counters to try');
return;
}
console.log(`[ToE] ↻ Retrying with ${nextCounter.names.join(', ')} (${nextCounter.winRate}% ${nextCounter.matchType})`);
retryInProgress = true;
try {
await Caller.send('titanArenaStartBattle', { visitorId: parseInt(rivalId), titans: nextCounter.team });
setCurrentBattle(rivalId, nextCounter);
} catch (e) {
console.warn('[ToE] Retry failed:', e);
}
retryInProgress = false;
};
const getTitanName = (id) => { if (!initialized) buildTitanMappings(); return titanIdToName[id] || `#${id}`; };
const getTitanId = (name) => { if (!initialized) buildTitanMappings(); return titanNameToId[name.toLowerCase()] || null; };
return {
loadCounterData, loadOwnedTitans, findCounter, getCounterForRival, getElementFallbackTeam,
getTitanName, getTitanId, getOwnedTitanIds, markTried, clearTried,
setCurrentBattle, getCurrentBattle, handleBattleEnd,
get initialized() { return initialized; },
get counterDB() { return counterDB; },
get retryInProgress() { return retryInProgress; }
};
})();
// Initialize when ready
const initToeCounter = () => {
try {
const test = cheats.translate('LIB_HERO_NAME_4001');
if (test && !test.startsWith('LIB_')) { ToeCounterPicker.loadCounterData(); return true; }
} catch (e) {}
return false;
};
if (!initToeCounter()) {
const initInterval = setInterval(() => { if (initToeCounter()) clearInterval(initInterval); }, 2000);
}
unsafeWindow.ToeCounterPicker = window.ToeCounterPicker = ToeCounterPicker;
//
// AutoToE Configuration & UI
//
const AutoToEConfig = {
_defaults: {
strategies: ['power','exact', 'meta', 'element'],
seedRetries: 10,
maxRetries: 10,
enabled: { exact: true, meta: true, element: true, power: true },
customTeam: [] // User's selected fallback team (5 titan IDs)
},
// Load from localStorage
load() {
try {
const saved = localStorage.getItem('AutoToEConfig');
if (saved) {
const parsed = JSON.parse(saved);
Object.assign(this, this._defaults, parsed);
} else {
Object.assign(this, this._defaults);
}
} catch (e) {
Object.assign(this, this._defaults);
}
return this;
},
// Save to localStorage
save() {
const toSave = {
strategies: this.strategies,
seedRetries: this.seedRetries,
maxRetries: this.maxRetries,
enabled: this.enabled,
customTeam: this.customTeam || []
};
localStorage.setItem('AutoToEConfig', JSON.stringify(toSave));
console.log('[AutoToE] Config saved');
},
// Reset to defaults
reset() {
Object.assign(this, this._defaults);
this.save();
}
}.load();
unsafeWindow.AutoToEConfig = window.AutoToEConfig = AutoToEConfig;
//
// Custom Auto ToE with smart counter-picking and retry on loss
//
const AutoToE = {
running: false,
get maxRetries() { return AutoToEConfig.maxRetries || 20; },
get seedRetries() { return AutoToEConfig.seedRetries || 10; },
async run() {
if (this.running) { console.log('[AutoToE] Already running'); return null; }
this.running = true;
console.log('[AutoToE] Starting...');
const results = {
wins: 0,
losses: 0,
totalAttempts: 0,
scoreGained: 0,
tiersCompleted: 0,
rivals: []
};
try {
// Get team setup
const teamData = await Caller.send('teamGetAll');
const titanTeam = teamData?.titan_arena || [];
if (titanTeam.length === 0) {
console.warn('[AutoToE] No titan_arena team set');
}
// Load counter data if needed
if (ToeCounterPicker.getOwnedTitanIds().length === 0) {
await ToeCounterPicker.loadOwnedTitans();
}
if (Object.keys(ToeCounterPicker.counterDB).length < 200) {
ToeCounterPicker.loadCounterData();
}
let startScore = 0;
let keepGoing = true;
while (keepGoing && this.running) {
const status = await Caller.send('titanArenaGetStatus');
if (!status) {
console.log('[AutoToE] Could not get status');
break;
}
console.log(`[AutoToE] Status: ${status.status}, Tier: ${status.tier}, canRaid: ${status.canRaid}`);
if (startScore === 0) startScore = status.dailyScore || 0;
// Check status
if (status.status === 'disabled') {
console.log('[AutoToE] ToE is disabled');
break;
}
if (status.status === 'peace_time') {
console.log('[AutoToE] Peace time - ToE not active');
break;
}
// Completed tier - advance to next
if (status.status === 'completed_tier') {
console.log(`[AutoToE] ═══ Completing Tier ${status.tier} ═══`);
await Caller.send({ name: 'titanArenaCompleteTier', args: {} });
results.tiersCompleted++;
await this.delay(500);
continue; // Loop back to get new status
}
// Can raid - use quick raid
if (status.canRaid) {
console.log(`[AutoToE] ═══ Raiding Tier ${status.tier} ═══`);
await this.doRaid(titanTeam, results);
results.tiersCompleted++;
await this.delay(500);
continue; // Loop back to check next tier
}
// Normal battle - fight unbeaten rivals
if (!status.rivals || Object.keys(status.rivals).length === 0) {
console.log('[AutoToE] No rivals available');
break;
}
const rivals = Object.entries(status.rivals).filter(([id, rival]) => {
return rival.attackScore < 250;
});
if (rivals.length === 0) {
console.log('[AutoToE] All rivals beaten on tier ' + status.tier);
// Check once more for completed_tier status
const recheck = await Caller.send('titanArenaGetStatus');
if (recheck?.status === 'completed_tier') {
console.log(`[AutoToE] ═══ Completing Tier ${recheck.tier} ═══`);
await Caller.send({ name: 'titanArenaCompleteTier', args: {} });
results.tiersCompleted++;
await this.delay(500);
continue;
}
// If still not completed_tier, we're done with this tier
console.log('[AutoToE] Tier not completable (not enough score or canRaid=false)');
break;
}
console.log(`[AutoToE] ═══ Tier ${status.tier}: ${rivals.length} rivals remaining ═══`);
for (const [rivalId, rival] of rivals) {
if (!this.running) break;
const result = await this.fightRival(rivalId, rival);
results.totalAttempts += result.attempts;
results.rivals.push({
id: rivalId,
name: rival.userData?.name || rivalId,
won: result.won,
attempts: result.attempts
});
if (result.won) results.wins++;
else results.losses++;
await this.delay(300);
}
// After fighting all rivals, loop back to check status
await this.delay(500);
}
// Get final score
const finalStatus = await Caller.send('titanArenaGetStatus');
results.scoreGained = (finalStatus?.dailyScore || 0) - startScore;
// Collect daily rewards
try {
const rewards = await Caller.send({ name: 'titanArenaFarmDailyReward', args: {} });
if (rewards && Object.keys(rewards).length > 0) {
console.log(`[AutoToE] 🎁 Collected daily rewards:`, Object.keys(rewards).length, 'tiers');
}
} catch (e) {
// May fail if already collected or not enough score
console.log('[AutoToE] No daily rewards to collect');
}
console.log(`[AutoToE] Done! Tiers: ${results.tiersCompleted}, Wins: ${results.wins}, Losses: ${results.losses}, Score: +${results.scoreGained}`);
} catch (e) {
console.error('[AutoToE] Error:', e);
}
this.running = false;
return results;
},
// Quick raid (when canRaid is true)
async doRaid(titanTeam, results) {
try {
const raidData = await Caller.send({
name: 'titanArenaStartRaid',
args: { titans: titanTeam }
});
if (!raidData?.rivals) {
console.log('[AutoToE] Raid returned no data');
return;
}
const { attackers, rivals } = raidData;
const endResults = {};
// Calculate all battles
for (const [rivalId, rival] of Object.entries(rivals)) {
const battleData = {
attackers: attackers,
defenders: [rival.team],
seed: rival.seed,
typeId: rivalId
};
const battleResult = await new Promise(resolve => {
BattleCalc(battleData, "get_titanClanPvp", resolve);
});
endResults[rivalId] = {
progress: battleResult.progress,
result: battleResult.result
};
if (battleResult.result?.win) results.wins++;
else results.losses++;
}
// Submit raid results
await Caller.send({
name: 'titanArenaEndRaid',
args: { results: endResults }
});
console.log(`[AutoToE] Raid complete: ${Object.keys(rivals).length} rivals`);
} catch (e) {
console.error('[AutoToE] Raid error:', e);
}
},
// ... rest of AutoToE methods (fightRival, findUntriedCounter, tryStrategy, doBattle, delay, stop)
async fightRival(rivalId, rival) {
const titanList = Array.isArray(rival.titans) ? rival.titans : Object.values(rival.titans);
const enemyIds = titanList.map(t => parseInt(t.id));
const enemyNames = enemyIds.map(id => ToeCounterPicker.getTitanName(id));
console.log(`[AutoToE] ═══ Rival ${rivalId}: ${enemyNames.join(', ')} ═══`);
const triedKeys = new Set();
let attempts = 0;
while (attempts < this.maxRetries) {
attempts++;
// Find a counter we haven't tried
const counter = this.findUntriedCounter(enemyIds, triedKeys);
if (!counter) {
console.log(`[AutoToE] No more counters to try`);
return { won: false, attempts };
}
console.log(`[AutoToE] Attempt ${attempts}: ${counter.names.join(', ')} (${counter.winRate}% ${counter.matchType})`);
triedKeys.add(counter.key);
// Start battle
const battleResult = await this.doBattle(rivalId, counter.team);
if (battleResult.won) {
console.log(`[AutoToE] ✓ Won on attempt ${attempts}`);
return { won: true, attempts };
}
console.log(`[AutoToE] ✗ Lost, trying next counter...`);
await this.delay(300);
}
console.log(`[AutoToE] Failed after ${attempts} attempts`);
return { won: false, attempts };
},
findUntriedCounter(enemyIds, triedKeys) {
const owned = new Set(ToeCounterPicker.getOwnedTitanIds());
const enemySet = new Set(enemyIds);
const defKey = enemyIds.slice().sort((a,b) => a-b).join('_');
for (const strategy of AutoToEConfig.strategies) {
if (AutoToEConfig.enabled[strategy] === false) continue;
const result = this.tryStrategy(strategy, { enemyIds, enemySet, defKey, owned, triedKeys });
if (result) return result;
}
return null;
},
tryStrategy(strategy, ctx) {
const { enemyIds, enemySet, defKey, owned, triedKeys } = ctx;
switch (strategy) {
case 'exact': {
const exactCounters = ToeCounterPicker.counterDB[defKey] || [];
for (const counter of exactCounters) {
if (triedKeys.has(counter.key)) continue;
if (counter.attackerIds.every(id => owned.has(id))) {
return { team: counter.attackerIds, names: counter.attackerNames,
winRate: counter.winRate, matchType: 'exact', key: counter.key };
}
}
break;
}
case 'meta': {
if (typeof TOE_META_TEAMS === 'undefined') break;
for (let i = 0; i < TOE_META_TEAMS.length; i++) {
const metaKey = 'meta_' + i;
if (triedKeys.has(metaKey)) continue;
const names = TOE_META_TEAMS[i];
const team = names.map(n => ToeCounterPicker.getTitanId(n)).filter(Boolean);
if (team.length === 5 && team.every(id => owned.has(id))) {
return { team, names, winRate: 0, matchType: `meta #${i+1}`, key: metaKey };
}
}
break;
}
case 'element': {
const elementKey = 'element_fallback';
if (triedKeys.has(elementKey)) break;
const elem = ToeCounterPicker.getElementFallbackTeam(enemyIds);
if (elem) { elem.key = elementKey; return elem; }
break;
}
case 'power': {
const powerKey = 'power_fallback';
if (triedKeys.has(powerKey)) break;
let team, names;
if (AutoToEConfig.customTeam?.length === 5) {
team = AutoToEConfig.customTeam;
names = team.map(id => ToeCounterPicker.getTitanName(id));
} else if (window._myTitansFull?.length >= 5) {
const sorted = [...window._myTitansFull].sort((a,b) => (b.power||0) - (a.power||0));
team = sorted.slice(0,5).map(t => t.id);
names = team.map(id => ToeCounterPicker.getTitanName(id));
} else {
break;
}
return { team, names, winRate: 0, matchType: 'custom', key: powerKey };
}
}
return null;
},
async doBattle(rivalId, titanTeam) {
try {
// Start battle
const startResult = await Caller.send({
name: 'titanArenaStartBattle',
args: {
rivalId: parseInt(rivalId),
titans: titanTeam
}
});
if (!startResult?.battle) {
console.error('[AutoToE] No battle data returned');
return { won: false, error: 'No battle data' };
}
// Calculate battle result using game's BattleCalc
let battleResult = await new Promise(resolve => {
BattleCalc(startResult.battle, "get_titanClanPvp", e => resolve(e));
});
// Try different seeds if first calc loses
if (!battleResult.result?.win) {
for (let i = 0; i < AutoToEConfig.seedRetries; i++) {
const battle = structuredClone(startResult.battle);
battle.seed = Math.floor(Date.now() / 1000) + Math.floor(Math.random() * 1000);
const retry = await new Promise(resolve => {
BattleCalc(battle, "get_titanClanPvp", e => resolve(e));
});
if (retry.result?.win) {
console.log(`[AutoToE] Found winning seed on try ${i+1}`);
battleResult = retry;
break;
}
}
}
console.log('[AutoToE] Battle calc:', battleResult.result?.win ? 'WIN' : 'LOSE');
// End battle with calculated result
const endResult = await Caller.send({
name: 'titanArenaEndBattle',
args: {
rivalId: parseInt(rivalId),
progress: battleResult.progress,
result: battleResult.result
}
});
const won = battleResult.result?.win === true;
return { won, result: endResult, battleResult };
} catch (e) {
console.error('[AutoToE] Battle error:', e);
return { won: false, error: e };
}
},
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
},
stop() {
this.running = false;
console.log('[AutoToE] Stopped');
}
};
// Expose globally
unsafeWindow.AutoToE = window.AutoToE = AutoToE;
console.log('[AutoToE] Ready - run with: AutoToE.run()');
//
// AutoToE UI - Summary & Config Popup
//
const AutoToEUI = {
lastResults: null,
// Strategy display names
strategyNames: {
exact: '📚 Counter Library',
meta: '⭐ Meta Teams',
element: '🔥 Element Counter',
power: '💪 Selected Team'
},
// Show config/summary popup
showPopup(results = null) {
this.lastResults = results || this.lastResults;
// Remove existing popup
document.querySelector('.autotoe-popup')?.remove();
const popup = document.createElement('div');
popup.className = 'autotoe-popup';
popup.innerHTML = `
<style>
.autotoe-popup {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: linear-gradient(180deg, #3d2817 0%, #2a1810 100%);
border: 3px solid #8b6914;
border-radius: 10px;
padding: 14px;
z-index: 999999;
min-width: 360px;
max-width: 440px;
color: #e8d5b0;
font-family: "Twemoji Country Flags", Arial, sans-serif;
box-shadow: 0 8px 30px rgba(0,0,0,0.5);
font-size: 12px;
}
.autotoe-popup h2 {
margin: 0 0 10px 0;
color: #ffd700;
font-size: 16px;
display: flex;
align-items: center;
gap: 8px;
}
.autotoe-popup .close-btn {
position: absolute;
top: 6px;
right: 10px;
background: none;
border: none;
color: #8b7355;
font-size: 20px;
cursor: pointer;
}
.autotoe-popup .close-btn:hover { color: #ffd700; }
.autotoe-popup .tabs {
display: flex;
gap: 4px;
margin-bottom: 10px;
}
.autotoe-popup .tab {
padding: 5px 12px;
background: rgba(139,105,20,0.3);
border: 1px solid #5a4a2a;
border-radius: 5px;
color: #c9a84c;
cursor: pointer;
font-size: 11px;
transition: all 0.15s;
}
.autotoe-popup .tab:hover { background: rgba(139,105,20,0.5); }
.autotoe-popup .tab.active { background: #8b6914; color: #fff; border-color: #8b6914; }
.autotoe-popup .tab-content { display: none; }
.autotoe-popup .tab-content.active { display: block; }
.autotoe-popup .config-section {
background: rgba(0,0,0,0.2);
border: 1px solid #5a4a2a;
border-radius: 6px;
padding: 8px 10px;
margin-bottom: 8px;
}
.autotoe-popup .config-section h3 {
margin: 0 0 6px 0;
font-size: 12px;
color: #ffd700;
}
.autotoe-popup .strategy-list {
list-style: none;
padding: 0;
margin: 0;
}
.autotoe-popup .strategy-item {
display: flex;
align-items: center;
gap: 8px;
padding: 5px 6px;
background: rgba(0,0,0,0.15);
border: 1px solid transparent;
border-radius: 4px;
margin-bottom: 3px;
cursor: grab;
font-size: 11px;
}
.autotoe-popup .strategy-item:hover { border-color: #5a4a2a; }
.autotoe-popup .strategy-item:active { cursor: grabbing; }
.autotoe-popup .strategy-item.dragging { opacity: 0.5; }
.autotoe-popup .strategy-item .drag-handle { color: #5a4a2a; font-size: 14px; }
.autotoe-popup .strategy-item label { flex: 1; cursor: pointer; color: #e8d5b0; }
.autotoe-popup .strategy-item input[type="checkbox"] {
width: 15px;
height: 15px;
cursor: pointer;
accent-color: #4ae29a;
}
.autotoe-popup .input-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 6px;
}
.autotoe-popup .input-row label { color: #c9a84c; font-size: 11px; }
.autotoe-popup .input-row input[type="number"] {
width: 50px;
padding: 3px 6px;
border: 1px solid #5a4a2a;
border-radius: 4px;
background: rgba(0,0,0,0.3);
color: #ffd700;
font-size: 12px;
text-align: center;
}
.autotoe-popup .btn-row {
display: flex;
gap: 6px;
margin-top: 10px;
}
.autotoe-popup .btn {
flex: 1;
padding: 7px 12px;
border: none;
border-radius: 5px;
font-size: 12px;
cursor: pointer;
transition: all 0.15s;
}
.autotoe-popup .btn-primary {
background: #8b6914;
color: #fff;
border: 1px solid #a67c00;
}
.autotoe-popup .btn-primary:hover { background: #a67c00; }
.autotoe-popup .btn-secondary {
background: rgba(0,0,0,0.3);
color: #c9a84c;
border: 1px solid #5a4a2a;
}
.autotoe-popup .btn-secondary:hover { background: rgba(139,105,20,0.3); }
.autotoe-popup .btn-success {
background: linear-gradient(180deg, #4a8b3a 0%, #2d6620 100%);
color: #fff;
border: 1px solid #5a9e45;
}
.autotoe-popup .btn-success:hover { background: linear-gradient(180deg, #5a9e45 0%, #3a7a2a 100%); }
.autotoe-popup .summary-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 6px;
}
.autotoe-popup .summary-card {
background: rgba(0,0,0,0.2);
border: 1px solid #5a4a2a;
border-radius: 6px;
padding: 8px;
text-align: center;
}
.autotoe-popup .summary-card .value {
font-size: 20px;
font-weight: bold;
color: #ffd700;
}
.autotoe-popup .summary-card .label {
font-size: 10px;
color: #8b7355;
margin-top: 2px;
}
.autotoe-popup .rival-list {
max-height: 160px;
overflow-y: auto;
margin-top: 8px;
}
.autotoe-popup .rival-item {
display: flex;
justify-content: space-between;
padding: 4px 8px;
background: rgba(0,0,0,0.15);
border-radius: 3px;
margin-bottom: 2px;
font-size: 11px;
}
.autotoe-popup .rival-item.win { border-left: 3px solid #4ae29a; }
.autotoe-popup .rival-item.lose { border-left: 3px solid #c94040; }
</style>
<button class="close-btn">×</button>
<h2>⚔️ AutoToE</h2>
<div class="tabs">
<button class="tab active" data-tab="config">⚙️ Config</button>
<button class="tab" data-tab="summary">📊 Summary</button>
</div>
<div class="tab-content active" data-tab="config">
<div class="config-section">
<h3>📋 Strategy Order (drag to reorder)</h3>
<ul class="strategy-list" id="autotoe-strategies">
${this.renderStrategyList()}
</ul>
</div>
<div class="config-section">
<h3>⚡ Battle Settings</h3>
<div class="input-row">
<label>Max Attempts per Rival:</label>
<input type="number" id="autotoe-maxRetries" value="${AutoToEConfig.maxRetries}" min="1" max="50">
</div>
<div class="input-row">
<label>Seed Retries per Battle:</label>
<input type="number" id="autotoe-seedRetries" value="${AutoToEConfig.seedRetries}" min="0" max="50">
</div>
</div>
<div class="config-section">
<h3>💪 Selected Team</h3>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="color: #ccc; font-size: 12px;">
${AutoToEConfig.customTeam?.length === 5
? AutoToEConfig.customTeam.map(id => ToeCounterPicker.getTitanName(id)).join(', ')
: 'Not set (using top 5 by power)'}
</span>
<button class="btn btn-secondary" id="autotoe-select-team" style="flex: 0; padding: 6px 12px;">Select</button>
</div>
</div>
<div class="btn-row">
<button class="btn btn-secondary" id="autotoe-reset">Reset Defaults</button>
<button class="btn btn-primary" id="autotoe-save">💾 Save</button>
</div>
</div>
<div class="tab-content" data-tab="summary">
${this.renderSummary()}
</div>
<div class="btn-row">
<button class="btn btn-success" id="autotoe-run" style="font-size: 16px;">
▶️ Start AutoToE Raid
</button>
</div>
`;
document.body.appendChild(popup);
this.attachEvents(popup);
},
renderStrategyList() {
const allStrategies = ['exact', 'meta', 'element', 'power'];
const ordered = [...AutoToEConfig.strategies];
// Add any missing strategies at the end
for (const s of allStrategies) {
if (!ordered.includes(s)) ordered.push(s);
}
return ordered.map(s => `
<li class="strategy-item" data-strategy="${s}" draggable="true">
<span class="drag-handle">☰</span>
<input type="checkbox" id="strat-${s}" ${AutoToEConfig.enabled[s] !== false ? 'checked' : ''}>
<label for="strat-${s}">${this.strategyNames[s] || s}</label>
</li>
`).join('');
},
renderSummary() {
if (!this.lastResults) {
return `
<div style="text-align: center; color: #888; padding: 30px;">
No raid results yet.<br>Run AutoToE to see summary.
</div>
`;
}
const r = this.lastResults;
return `
<div class="summary-grid">
<div class="summary-card">
<div class="value" style="color: #00d26a;">${r.wins}</div>
<div class="label">Wins</div>
</div>
<div class="summary-card">
<div class="value" style="color: #e94560;">${r.losses}</div>
<div class="label">Losses</div>
</div>
<div class="summary-card">
<div class="value">${r.totalAttempts || 0}</div>
<div class="label">Total Attempts</div>
</div>
<div class="summary-card">
<div class="value">${r.scoreGained || 0}</div>
<div class="label">Score Gained</div>
</div>
</div>
<div class="rival-list">
${(r.rivals || []).map(rival => `
<div class="rival-item ${rival.won ? 'win' : 'lose'}">
<span>${rival.name || rival.id}</span>
<span>${rival.won ? '✓ Won' : '✗ Lost'} (${rival.attempts} tries)</span>
</div>
`).join('')}
</div>
`;
},
attachEvents(popup) {
// Close button
popup.querySelector('.close-btn').onclick = () => popup.remove();
// Tab switching
popup.querySelectorAll('.tab').forEach(tab => {
tab.onclick = () => {
popup.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
popup.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
tab.classList.add('active');
popup.querySelector(`.tab-content[data-tab="${tab.dataset.tab}"]`).classList.add('active');
};
});
// Drag and drop for strategy reordering
const strategyList = popup.querySelector('#autotoe-strategies');
let draggedItem = null;
strategyList.querySelectorAll('.strategy-item').forEach(item => {
item.addEventListener('dragstart', (e) => {
draggedItem = item;
item.classList.add('dragging');
});
item.addEventListener('dragend', () => {
item.classList.remove('dragging');
draggedItem = null;
});
item.addEventListener('dragover', (e) => {
e.preventDefault();
if (draggedItem && draggedItem !== item) {
const rect = item.getBoundingClientRect();
const midY = rect.top + rect.height / 2;
if (e.clientY < midY) {
strategyList.insertBefore(draggedItem, item);
} else {
strategyList.insertBefore(draggedItem, item.nextSibling);
}
}
});
});
// Save button
popup.querySelector('#autotoe-save').onclick = () => {
// Get strategy order from DOM
const strategies = [];
const enabled = {};
popup.querySelectorAll('.strategy-item').forEach(item => {
const s = item.dataset.strategy;
strategies.push(s);
enabled[s] = item.querySelector('input[type="checkbox"]').checked;
});
AutoToEConfig.strategies = strategies;
AutoToEConfig.enabled = enabled;
AutoToEConfig.maxRetries = parseInt(popup.querySelector('#autotoe-maxRetries').value) || 10;
AutoToEConfig.seedRetries = parseInt(popup.querySelector('#autotoe-seedRetries').value) || 10;
AutoToEConfig.save();
// Visual feedback
const btn = popup.querySelector('#autotoe-save');
btn.textContent = '✓ Saved!';
setTimeout(() => btn.textContent = '💾 Save', 1500);
};
// Reset button
popup.querySelector('#autotoe-reset').onclick = () => {
AutoToEConfig.reset();
this.showPopup(); // Refresh popup
};
// Select team button
popup.querySelector('#autotoe-select-team').onclick = () => {
this.showTeamSelector();
};
// Run button
popup.querySelector('#autotoe-run').onclick = async () => {
const btn = popup.querySelector('#autotoe-run');
btn.disabled = true;
btn.textContent = '⏳ Running...';
try {
const results = await AutoToE.run();
this.lastResults = results;
// Switch to summary tab
popup.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
popup.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
popup.querySelector('.tab[data-tab="summary"]').classList.add('active');
popup.querySelector('.tab-content[data-tab="summary"]').classList.add('active');
popup.querySelector('.tab-content[data-tab="summary"]').innerHTML = this.renderSummary();
} finally {
btn.disabled = false;
btn.textContent = '▶️ Start AutoToE Raid';
}
};
},
async showTeamSelector() {
document.querySelector('.toe-team-popup')?.remove();
// Load titan data if not available
let owned = window._myTitansFull || Object.values(window.gameLoadData?.titans || {});
if (!owned || owned.length < 5) {
try {
const titans = await Caller.send('titanGetAll');
window._myTitans = Object.keys(titans).map(id => parseInt(id));
window._myTitansFull = Object.values(titans);
owned = window._myTitansFull;
console.log('[AutoToE] Loaded', owned.length, 'titans');
} catch (e) {
console.error('[AutoToE] Failed to load titans:', e);
return;
}
}
if (!owned || owned.length < 5) {
console.error('[AutoToE] No titan data available');
return;
}
// Sort by power
const titans = [...owned].sort((a, b) => (b.power || 0) - (a.power || 0));
const currentTeam = AutoToEConfig.customTeam || [];
const popup = document.createElement('div');
popup.className = 'toe-team-popup';
popup.innerHTML = `
<style>
.toe-team-popup {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: linear-gradient(180deg, #3d2817 0%, #2a1810 100%);
border: 3px solid #8b6914;
border-radius: 10px;
padding: 14px;
z-index: 1000000;
min-width: 380px;
max-width: 480px;
color: #e8d5b0;
font-family: "Twemoji Country Flags", Arial, sans-serif;
box-shadow: 0 8px 30px rgba(0,0,0,0.5);
font-size: 12px;
}
.toe-team-popup h3 {
margin: 0 0 6px 0;
color: #ffd700;
font-size: 14px;
}
.toe-team-popup .close-btn {
position: absolute;
top: 6px;
right: 10px;
background: none;
border: none;
color: #8b7355;
font-size: 20px;
cursor: pointer;
}
.toe-team-popup .close-btn:hover { color: #ffd700; }
.toe-team-popup .selected-team {
display: flex;
gap: 6px;
padding: 8px;
background: rgba(0,0,0,0.3);
border: 1px solid #5a4a2a;
border-radius: 6px;
margin-bottom: 10px;
min-height: 44px;
flex-wrap: wrap;
align-items: center;
}
.toe-team-popup .selected-titan {
display: flex;
flex-direction: column;
align-items: center;
padding: 3px 8px;
background: rgba(74,226,154,0.15);
border: 1px solid rgba(74,226,154,0.4);
border-radius: 4px;
font-size: 10px;
cursor: pointer;
}
.toe-team-popup .selected-titan:hover {
background: rgba(201,64,64,0.2);
border-color: #c94040;
}
.toe-team-popup .selected-titan .power {
font-size: 10px;
color: #8b7355;
}
.toe-team-popup .empty-slot {
width: 55px;
height: 36px;
border: 1px dashed #5a4a2a;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
color: #5a4a2a;
font-size: 16px;
}
.toe-team-popup .titan-grid {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 4px;
max-height: 240px;
overflow-y: auto;
padding: 4px;
}
.toe-team-popup .titan-card {
display: flex;
flex-direction: column;
align-items: center;
padding: 5px 3px;
background: rgba(0,0,0,0.2);
border: 1px solid transparent;
border-radius: 4px;
cursor: pointer;
transition: all 0.1s;
font-size: 10px;
text-align: center;
}
.toe-team-popup .titan-card:hover {
background: rgba(139,105,20,0.2);
border-color: #5a4a2a;
}
.toe-team-popup .titan-card.selected {
background: rgba(74,226,154,0.15);
border-color: rgba(74,226,154,0.5);
}
.toe-team-popup .titan-card.disabled {
opacity: 0.3;
cursor: not-allowed;
}
.toe-team-popup .titan-card .name {
font-weight: bold;
color: #e8d5b0;
margin-bottom: 1px;
}
.toe-team-popup .titan-card .power {
font-size: 10px;
color: #8b7355;
}
.toe-team-popup .titan-card .element {
font-size: 10px;
padding: 0px 3px;
border-radius: 2px;
margin-top: 1px;
}
.toe-team-popup .elem-fire { background: rgba(255,80,20,0.3); color: #ffa07a; }
.toe-team-popup .elem-water { background: rgba(30,90,180,0.3); color: #87ceeb; }
.toe-team-popup .elem-earth { background: rgba(45,100,30,0.3); color: #90ee90; }
.toe-team-popup .elem-light { background: rgba(180,180,30,0.3); color: #ffff99; }
.toe-team-popup .elem-dark { background: rgba(100,30,140,0.3); color: #dda0dd; }
.toe-team-popup .btn-row {
display: flex;
gap: 6px;
margin-top: 10px;
}
.toe-team-popup .btn {
flex: 1;
padding: 7px 12px;
border: none;
border-radius: 5px;
font-size: 12px;
cursor: pointer;
}
.toe-team-popup .btn-primary {
background: #8b6914;
color: #fff;
border: 1px solid #a67c00;
}
.toe-team-popup .btn-primary:hover { background: #a67c00; }
.toe-team-popup .btn-secondary {
background: rgba(0,0,0,0.3);
color: #c9a84c;
border: 1px solid #5a4a2a;
}
.toe-team-popup .hint {
font-size: 10px;
color: #8b7355;
margin-bottom: 8px;
}
</style>
<button class="close-btn">×</button>
<h3>💪 Selected Team</h3>
<p class="hint">Click titans to add/remove. This team is used when counter library, meta teams, and element counter all fail.</p>
<div class="selected-team" id="toe-selected-team">
${this.renderSelectedTeam(currentTeam)}
</div>
<div class="titan-grid" id="toe-titan-grid">
${titans.map(t => this.renderTitanCard(t, currentTeam.includes(t.id))).join('')}
</div>
<div class="btn-row">
<button class="btn btn-secondary" id="toe-team-clear">Clear</button>
<button class="btn btn-primary" id="toe-team-save">💾 Save Team</button>
</div>
`;
document.body.appendChild(popup);
let selected = [...currentTeam];
const updateDisplay = () => {
popup.querySelector('#toe-selected-team').innerHTML = this.renderSelectedTeam(selected);
popup.querySelectorAll('.titan-card').forEach(card => {
const id = parseInt(card.dataset.id);
card.classList.toggle('selected', selected.includes(id));
card.classList.toggle('disabled', selected.length >= 5 && !selected.includes(id));
});
// Click to remove from selected
popup.querySelectorAll('.selected-titan').forEach(el => {
el.onclick = () => {
const id = parseInt(el.dataset.id);
selected = selected.filter(x => x !== id);
updateDisplay();
};
});
};
// Close
popup.querySelector('.close-btn').onclick = () => popup.remove();
// Titan selection
popup.querySelectorAll('.titan-card').forEach(card => {
card.onclick = () => {
const id = parseInt(card.dataset.id);
if (selected.includes(id)) {
selected = selected.filter(x => x !== id);
} else if (selected.length < 5) {
selected.push(id);
}
updateDisplay();
};
});
// Clear
popup.querySelector('#toe-team-clear').onclick = () => {
selected = [];
updateDisplay();
};
// Save
popup.querySelector('#toe-team-save').onclick = () => {
if (selected.length !== 5) {
alert('Please select exactly 5 titans');
return;
}
AutoToEConfig.customTeam = selected;
AutoToEConfig.save();
popup.remove();
console.log('[AutoToE] Custom team saved:', selected.map(id => ToeCounterPicker.getTitanName(id)));
};
updateDisplay();
},
renderSelectedTeam(teamIds) {
const slots = [];
for (let i = 0; i < 5; i++) {
if (teamIds[i]) {
const id = teamIds[i];
const titan = window._myTitansFull?.find(t => t.id === id) ||
Object.values(window.gameLoadData?.titans || {}).find(t => t.id === id);
const name = ToeCounterPicker.getTitanName(id);
const power = titan?.power || '?';
slots.push(`
<div class="selected-titan" data-id="${id}">
<span class="name">${name}</span>
<span class="power">${power}</span>
</div>
`);
} else {
slots.push('<div class="empty-slot">+</div>');
}
}
return slots.join('');
},
renderTitanCard(titan, isSelected) {
const name = ToeCounterPicker.getTitanName(titan.id);
const element = this.getTitanElement(titan.id);
return `
<div class="titan-card ${isSelected ? 'selected' : ''}" data-id="${titan.id}">
<span class="name">${name}</span>
<span class="power">${titan.power || '?'}</span>
<span class="element elem-${element}">${element}</span>
</div>
`;
},
getTitanElement(id) {
const map = { 0: 'water', 1: 'fire', 2: 'earth', 3: 'dark', 4: 'light' };
return map[Math.floor((id % 100) / 10)] || 'neutral';
}
};
unsafeWindow.AutoToEUI = window.AutoToEUI = AutoToEUI;
}