ToE Counter Picker & AutoToE for HWH Tweaker
Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị 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;
}