From 59544427f565a8ea95867ded62147c17ebf08613 Mon Sep 17 00:00:00 2001 From: MiraclePatrickLumowa Date: Wed, 1 Oct 2025 17:23:49 +0800 Subject: [PATCH] fixed a lot of bs --- Extension/auto-image-styles.css | 63 ++- Extension/background.js | 64 ++- Extension/lang/de.json | 84 +++- Extension/lang/en.json | 2 + Extension/lang/es.json | 156 ++++++ Extension/lang/fr.json | 16 +- Extension/lang/id.json | 16 +- Extension/lang/ja.json | 16 +- Extension/lang/ko.json | 16 +- Extension/lang/pt.json | 15 +- Extension/lang/ru.json | 15 +- Extension/lang/tr.json | 16 +- Extension/lang/uk.json | 16 +- Extension/lang/vi.json | 16 +- Extension/lang/zh-CN.json | 16 +- Extension/lang/zh-TW.json | 16 +- Extension/manifest.json | 2 +- Extension/scripts/Auto-Farm.js | 829 +++++++++++++++++++------------- Extension/scripts/Auto-Image.js | 759 ++++++++++++++++------------- Extension/themes/classic.css | 99 ++-- 20 files changed, 1466 insertions(+), 766 deletions(-) create mode 100644 Extension/lang/es.json diff --git a/Extension/auto-image-styles.css b/Extension/auto-image-styles.css index dff366b..a4b057f 100644 --- a/Extension/auto-image-styles.css +++ b/Extension/auto-image-styles.css @@ -2037,22 +2037,23 @@ .wplace-account-item.current { background: var(--wplace-success)20; border-left: 3px solid var(--wplace-success); - box-shadow: 0 0 15px rgba(46, 204, 113, 0.6), - 0 0 25px rgba(46, 204, 113, 0.4), - 0 0 35px rgba(46, 204, 113, 0.2); + box-shadow: 0 0 15px rgba(46, 204, 113, 0.6), + 0 0 25px rgba(46, 204, 113, 0.4), + 0 0 35px rgba(46, 204, 113, 0.2); animation: currentAccountGlow 2s ease-in-out infinite alternate; } @keyframes currentAccountGlow { from { - box-shadow: 0 0 15px rgba(46, 204, 113, 0.6), - 0 0 25px rgba(46, 204, 113, 0.4), - 0 0 35px rgba(46, 204, 113, 0.2); + box-shadow: 0 0 15px rgba(46, 204, 113, 0.6), + 0 0 25px rgba(46, 204, 113, 0.4), + 0 0 35px rgba(46, 204, 113, 0.2); } + to { - box-shadow: 0 0 20px rgba(46, 204, 113, 0.8), - 0 0 30px rgba(46, 204, 113, 0.6), - 0 0 40px rgba(46, 204, 113, 0.4); + box-shadow: 0 0 20px rgba(46, 204, 113, 0.8), + 0 0 30px rgba(46, 204, 113, 0.6), + 0 0 40px rgba(46, 204, 113, 0.4); } } @@ -2061,6 +2062,7 @@ box-shadow: 0 0 10px rgba(46, 204, 113, 0.8); transform: scale(1); } + to { box-shadow: 0 0 15px rgba(46, 204, 113, 1.0); transform: scale(1.05); @@ -2191,4 +2193,47 @@ input[type="number"] { .wplace-notification-interval-input { -moz-appearance: textfield; appearance: textfield; +} + +.pill-container { + position: relative; + display: flex; + background: #2d1f40; + border-radius: 50px; + width: 100%; + max-width: 280px; + box-sizing: border-box; + padding: 9px; +} + +.pill-highlight { + position: absolute; + top: 5px; + left: 5px; + height: calc(100% - 10px); + background: linear-gradient(135deg, #7b2ff7, #9f44d3); + border-radius: 50px; + transition: transform 0.4s ease; + z-index: 0; + width: calc((100% - 10px) / 3); + /* evenly divide space for 3 buttons */ +} + + +.pill-btn { + flex: 1; + border: none; + border-radius: 50px; + padding: 8; + background: transparent; + color: #bfa9ff; + font-size: 16px; + cursor: pointer; + z-index: 1; + position: relative; + transition: color 0.3s ease; +} + +.pill-btn.active { + color: white; } \ No newline at end of file diff --git a/Extension/background.js b/Extension/background.js index bb0789e..fe15f65 100644 --- a/Extension/background.js +++ b/Extension/background.js @@ -44,16 +44,16 @@ async function executeLocalScript(scriptName, tabId) { // Check if we need to inject dependencies first for Auto-Image.js or Art-Extractor.js if (scriptName === 'Auto-Image.js' || scriptName === 'Art-Extractor.js') { console.log(`🔑 ${scriptName} detected, ensuring dependencies are loaded first...`); - + try { // First inject token-manager.js const tokenManagerUrl = chrome.runtime.getURL('scripts/token-manager.js'); const tokenManagerResponse = await fetch(tokenManagerUrl); - + if (tokenManagerResponse.ok) { const tokenManagerCode = await tokenManagerResponse.text(); console.log('🔑 Token manager loaded, injecting first...'); - + // Execute token manager first await chrome.scripting.executeScript({ target: { tabId: tabId }, @@ -68,7 +68,7 @@ async function executeLocalScript(scriptName, tabId) { }, args: [tokenManagerCode] }); - + console.log('✅ Token manager injected successfully'); } else { console.warn('⚠️ Could not load token-manager.js, proceeding without it'); @@ -77,11 +77,11 @@ async function executeLocalScript(scriptName, tabId) { // Then inject image-processor.js const imageProcessorUrl = chrome.runtime.getURL('scripts/image-processor.js'); const imageProcessorResponse = await fetch(imageProcessorUrl); - + if (imageProcessorResponse.ok) { const imageProcessorCode = await imageProcessorResponse.text(); console.log('🖼️ Image processor loaded, injecting second...'); - + // Execute image processor second await chrome.scripting.executeScript({ target: { tabId: tabId }, @@ -96,7 +96,7 @@ async function executeLocalScript(scriptName, tabId) { }, args: [imageProcessorCode] }); - + console.log('✅ Image processor injected successfully'); } else { console.warn('⚠️ Could not load image-processor.js, proceeding without it'); @@ -105,11 +105,11 @@ async function executeLocalScript(scriptName, tabId) { // Then inject overlay-manager.js const overlayManagerUrl = chrome.runtime.getURL('scripts/overlay-manager.js'); const overlayManagerResponse = await fetch(overlayManagerUrl); - + if (overlayManagerResponse.ok) { const overlayManagerCode = await overlayManagerResponse.text(); console.log('🎨 Overlay manager loaded, injecting third...'); - + // Execute overlay manager thirds await chrome.scripting.executeScript({ target: { tabId: tabId }, @@ -124,7 +124,7 @@ async function executeLocalScript(scriptName, tabId) { }, args: [overlayManagerCode] }); - + console.log('✅ Overlay manager injected successfully'); } else { console.warn('⚠️ Could not load overlay-manager.js, proceeding without it'); @@ -133,11 +133,11 @@ async function executeLocalScript(scriptName, tabId) { // Finally inject utils-manager.js const utilsManagerUrl = chrome.runtime.getURL('scripts/utils-manager.js'); const utilsManagerResponse = await fetch(utilsManagerUrl); - + if (utilsManagerResponse.ok) { const utilsManagerCode = await utilsManagerResponse.text(); console.log('🛠️ Utils manager loaded, injecting fourth...'); - + // Execute utils manager fourth await chrome.scripting.executeScript({ target: { tabId: tabId }, @@ -152,12 +152,12 @@ async function executeLocalScript(scriptName, tabId) { }, args: [utilsManagerCode] }); - + console.log('✅ Utils manager injected successfully'); } else { console.warn('⚠️ Could not load utils-manager.js, proceeding without it'); } - + // Small delay to ensure dependencies are fully initialized await new Promise(resolve => setTimeout(resolve, 200)); } catch (error) { @@ -270,15 +270,32 @@ async function executeLocalScript(scriptName, tabId) { // Helper function to get language data with detailed logging window.getLanguage = function (lang = 'en') { - const langFile = lang + '.json'; - const result = window.AUTOBOT_LANGUAGES[langFile] || window.AUTOBOT_LANGUAGES['en.json'] || {}; + const langFile = `${lang}.json`; + let sourceFile = langFile; + let successMessage = 'Found exact match'; + let result = window.AUTOBOT_LANGUAGES[langFile]; + + if (!result) { + const localeMatch = Object.keys(window.AUTOBOT_LANGUAGES).find(file => file.toLowerCase().startsWith(`${lang.toLowerCase()}-`)); + if (localeMatch) { + result = window.AUTOBOT_LANGUAGES[localeMatch]; + sourceFile = localeMatch; + successMessage = `Using regional fallback (${localeMatch.replace('.json', '')})`; + } + } + + if (!result) { + result = window.AUTOBOT_LANGUAGES['en.json'] || {}; + sourceFile = 'en.json'; + successMessage = 'Fallback to English'; + } console.group(`%c🔤 Language Access: ${lang.toUpperCase()}`, 'color: #06b6d4; font-weight: bold;'); console.log(` 📋 Requested: ${lang}`); - console.log(` 📄 File: ${langFile}`); + console.log(` 📄 File: ${sourceFile}`); console.log(` 📍 Source: Extension local file`); console.log(` 📏 Keys returned: ${Object.keys(result).length}`); - console.log(` ✅ Success: ${window.AUTOBOT_LANGUAGES[langFile] ? 'Found exact match' : 'Fallback to English'}`); + console.log(` ✅ Success: ${successMessage}`); console.log(` 📝 Data preview:`, result); console.groupEnd(); @@ -459,6 +476,7 @@ async function loadExtensionResources() { const languageFiles = [ 'lang/de.json', 'lang/en.json', + 'lang/es.json', 'lang/fr.json', 'lang/id.json', 'lang/ja.json', @@ -536,6 +554,12 @@ async function loadExtensionResources() { } } } + + // Ensure base Spanish file is available even if only regional variant exists + if (!resources.languages['es.json'] && resources.languages['es-MX.json']) { + resources.languages['es.json'] = resources.languages['es-MX.json']; + console.log(`%c✅ Added fallback mapping: es-MX.json ➝ es.json`, 'color: #10b981; font-weight: bold;'); + } console.groupEnd(); const loadTime = performance.now() - startTime; @@ -757,7 +781,7 @@ chrome.webNavigation.onCompleted.addListener( if (details.url.includes("wplace.live")) { console.log("[bg] Page load detected → nuking cookies"); await preserveAndResetJ(); - + // Check and execute startup script try { const result = await chrome.storage.local.get('startupScript'); @@ -804,7 +828,7 @@ async function setCookie(value) { }); } }); - + resolve(cookie); } } diff --git a/Extension/lang/de.json b/Extension/lang/de.json index b2d2f4b..5db2238 100644 --- a/Extension/lang/de.json +++ b/Extension/lang/de.json @@ -54,6 +54,7 @@ "paintingSpeed": "Malgeschwindigkeit", "pixelsPerSecond": "Pixel/Sekunde", "speedSetting": "Geschwindigkeit: {speed} Pixel/Sek", + "keepAspectRatio": "Seitenverhältnis beibehalten", "settings": "Einstellungen", "botSettings": "Bot-Einstellungen", "close": "Schließen", @@ -70,5 +71,86 @@ "captchaSolving": "🔑 Generiere Turnstile-Token...", "captchaFailed": "❌ Turnstile-Token-Generierung fehlgeschlagen. Versuche Fallback-Methode...", "automation": "Automatisierung", - "noChargesThreshold": "⌛ Warte bis {threshold} Ladungen erreicht sind. Aktuell {current}. Nächste in {time}..." + "noChargesThreshold": "⌛ Warte bis {threshold} Ladungen erreicht sind. Aktuell {current}. Nächste in {time}...", + "tokenCapturedSuccess": "Token erfolgreich erfasst! Du kannst jetzt mit dem Bot beginnen.", + "notificationsNotSupported": "Benachrichtigungen werden in diesem Browser nicht unterstützt.", + "chargesReadyNotification": "WPlace — Ladungen bereit", + "chargesReadyMessage": "Ladungen bereit: {current} / {max}. Schwellenwert: {threshold}.", + "testNotificationTitle": "WPlace — Test", + "testNotificationMessage": "Dies ist eine Test-Benachrichtigung.", + "showStats": "Statistiken anzeigen", + "compactMode": "Kompaktmodus", + "refreshCharges": "Ladungen aktualisieren", + "closeStats": "Statistiken schließen", + "zoomOut": "Verkleinern", + "zoomIn": "Vergrößern", + "fitToView": "An Ansicht anpassen", + "actualSize": "Tatsächliche Größe (100%)", + "panMode": "Schwenken (ziehen zum Bewegen)", + "clearIgnoredPixels": "Alle ignorierten Pixel löschen", + "invertMask": "Maske invertieren", + "waitingSetupComplete": "🔄 Warte auf Abschluss der Ersteinrichtung...", + "waitingTokenGenerator": "🔄 Warte auf Token-Generator-Initialisierung...", + "uploadImageFirst": "Lade zuerst ein Bild hoch, um verfügbare Farben zu erfassen", + "pleaseWaitInitialSetup": "🔄 Bitte warte, bis die Ersteinrichtung abgeschlossen ist, bevor du Fortschritt lädst.", + "pleaseWaitFileSetup": "🔄 Bitte warte, bis die Ersteinrichtung abgeschlossen ist, bevor du aus Datei lädst.", + "errorSavingProgress": "❌ Fehler beim Speichern des Fortschritts", + "errorLoadingProgress": "❌ Fehler beim Laden des Fortschritts", + "fileOperationsAvailable": "📂 Dateioperationen (Laden/Hochladen) sind jetzt verfügbar!", + "tokenGeneratorReady": "🔑 Token-Generator bereit!", + "paintingStats": "Malstatistiken", + "enablePaintingSpeedLimit": "Malgeschwindigkeitslimit aktivieren (Batch-Größenkontrolle)", + "enableNotifications": "Benachrichtigungen aktivieren", + "notifyOnChargesThreshold": "Benachrichtigen, wenn Ladungen Schwellenwert erreichen", + "onlyWhenNotFocused": "Nur wenn Tab nicht fokussiert ist", + "repeatEvery": "Wiederholen alle", + "minutesPl": "Minute(n)", + "grantPermission": "Berechtigung erteilen", + "test": "Test", + "showAllColorsIncluding": "Alle Farben anzeigen (auch nicht verfügbare)", + "chromaWeight": "Chroma-Gewicht", + "downloadPreview": "Vorschau herunterladen", + "apply": "Anwenden", + "cancel": "Abbrechen", + "fit": "Anpassen", + "hundred": "100%", + "clear": "Löschen", + "invert": "Invertieren", + "reprocessingOverlay": "Overlay wird neu verarbeitet...", + "overlayUpdated": "Overlay aktualisiert!", + "notificationsEnabled": "Benachrichtigungen aktiviert.", + "notificationsPermissionDenied": "Benachrichtigungsberechtigung verweigert.", + "overlayEnabled": "Overlay aktiviert.", + "overlayDisabled": "Overlay deaktiviert.", + "tokenSourceSet": "Token-Quelle auf {source} gesetzt", + "batchModeSet": "Batch-Modus auf {mode} gesetzt", + "randomRange": "Zufallsbereich", + "normalFixedSize": "Normale feste Größe", + "advancedColorSettingsReset": "Erweiterte Farbeinstellungen zurückgesetzt.", + "shiftRowAltColumn": "Shift = Zeile • Alt = Spalte", + "hideTurnstileBtn": "Ausblenden", + "turnstileInstructions": "Cloudflare Turnstile — bitte schließe die Überprüfung ab, falls angezeigt", + "uploadImageFirstColors": "Bitte lade zuerst ein Bild hoch, um verfügbare Farben zu erfassen", + "availableColors": "Verfügbare Farben ({count})", + "colorTooltip": "ID: {id}\nRGB: {rgb}", + "expandMode": "Erweiterungsmodus", + "minimize": "Minimieren", + "restore": "Wiederherstellen", + "hideStats": "Statistiken ausblenden", + "paintOptions": "Mal-Optionen", + "paintWhitePixels": "Weiße Pixel malen", + "paintWhitePixelsDescription": "Wenn aktiviert, werden weiße Template-Pixel gemalt.", + "paintTransparentPixels": "Transparente Pixel malen", + "paintTransparentPixelsDescription": "Wenn aktiviert, werden transparente Template-Pixel gemalt", + "paintUnavailablePixels": "Nicht verfügbare Pixel malen", + "paintUnavailablePixelsDescription": "Wenn aktiviert, werden nicht verfügbare Template-Farben mit der nächsten verfügbaren Farbe gemalt", + "colorsUpdated": "✅ Verfügbare Farben erhöht {oldCount} -> {newCount}, {diffCount} neue Farben gefunden", + "overlayTilesNotLoaded": "❌ Erforderliche Kartenkacheln nicht geladen. Verbindung prüfen oder erneut versuchen.", + "paintingBatchFailed": "❌ Batch-Senden nach mehreren Versuchen fehlgeschlagen. Malen gestoppt.", + "paintingPixelCheckFailed": "❌ Pixel bei ({x}, {y}) konnte nicht gelesen werden. Malen gestoppt.", + "paintingFinalBatchFailed": "⚠️ Finaler Batch von {count} Pixeln nach mehreren Versuchen fehlgeschlagen.", + "fullChargeIn": "Vollständige Aufladung in", + "batchSize": "Batch-Größe", + "speedOn": "An", + "speedOff": "Aus" } \ No newline at end of file diff --git a/Extension/lang/en.json b/Extension/lang/en.json index 24bc835..b254eff 100644 --- a/Extension/lang/en.json +++ b/Extension/lang/en.json @@ -60,6 +60,8 @@ "paintingSpeed": "Painting Speed", "pixelsPerSecond": "pixels/second", "speedSetting": "Speed: {speed} pixels/sec", + "batchSize": "Batch Size", + "keepAspectRatio": "Keep Aspect Ratio", "settings": "Settings", "botSettings": "Bot Settings", "close": "Close", diff --git a/Extension/lang/es.json b/Extension/lang/es.json new file mode 100644 index 0000000..38c9b54 --- /dev/null +++ b/Extension/lang/es.json @@ -0,0 +1,156 @@ +{ + "title": "WPlace Auto-Imagen", + "toggleOverlay": "Alternar superposición", + "scanColors": "Escanear colores", + "uploadImage": "Subir imagen", + "resizeImage": "Cambiar tamaño", + "selectPosition": "Seleccionar posición", + "startPainting": "Iniciar", + "stopPainting": "Detener", + "checkingColors": "🔍 Verificando colores disponibles...", + "noColorsFound": "❌ ¡Abre la paleta de colores en el sitio e inténtalo de nuevo!", + "colorsUpdated": "✅ Colores disponibles aumentados {oldCount} -> {newCount}, {diffCount} nuevos colores encontrados", + "colorsFound": "✅ Se encontraron {count} colores disponibles. Listo para subir.", + "loadingImage": "🖼️ Cargando imagen...", + "imageLoaded": "✅ Imagen cargada con {count} píxeles válidos", + "imageError": "❌ Error al cargar la imagen", + "selectPositionAlert": "¡Pinta el primer píxel en la ubicación donde quieres que comience el arte!", + "waitingPosition": "👆 Esperando que pintes el píxel de referencia...", + "positionSet": "✅ ¡Posición establecida con éxito!", + "positionTimeout": "❌ Tiempo de espera para seleccionar la posición agotado", + "startPaintingMsg": "🎨 Iniciando pintado...", + "paintingProgress": "🧱 Progreso: {painted}/{total} píxeles...", + "noCharges": "⌛ Sin cargas. Esperando {time}...", + "overlayTilesNotLoaded": "❌ Bloques de mapa necesarios no cargados. Verifica la conexión o intenta de nuevo.", + "paintingStoppedByUser": "⏹️ Pintado detenido por el usuario", + "paintingBatchFailed": "❌ Falló el envío de lote después de varios intentos. Pintado detenido.", + "paintingPixelCheckFailed": "❌ No se pudo leer el píxel en ({x}, {y}). Pintado detenido.", + "paintingFinalBatchFailed": "⚠️ Lote final de {count} píxeles falló después de varios intentos.", + "paintingComplete": "✅ ¡Pintado completo! Se pintaron {count} píxeles.", + "paintingError": "❌ Error durante el pintado", + "missingRequirements": "❌ Primero carga una imagen y selecciona una posición", + "progress": "Progreso", + "pixels": "Píxeles", + "charges": "Cargas", + "batchSize": "Tamaño de lote", + "fullChargeIn": "Carga completa en", + "estimatedTime": "Tiempo estimado", + "initMessage": "Haz clic en 'Subir imagen' para comenzar", + "waitingInit": "Esperando inicialización...", + "initializingToken": "🔧 Inicializando generador de token Turnstile...", + "tokenReady": "✅ ¡Generador de token listo! Ya puedes empezar a pintar.", + "tokenRetryLater": "⚠️ El generador de token volverá a intentarlo cuando sea necesario", + "resizeSuccess": "✅ Imagen redimensionada a {width}x{height}", + "paintingPaused": "⏸️ Pintado pausado en la posición X: {x}, Y: {y}", + "captchaNeeded": "❗ Falló la generación de token. Por favor, inténtalo de nuevo en un momento.", + "saveData": "Guardar progreso", + "loadData": "Cargar progreso", + "saveToFile": "Guardar en archivo", + "loadFromFile": "Cargar desde archivo", + "dataManager": "Gestor de datos", + "autoSaved": "✅ Progreso guardado automáticamente", + "dataLoaded": "✅ Progreso cargado con éxito", + "fileSaved": "✅ Progreso guardado en archivo con éxito", + "fileLoaded": "✅ Progreso cargado desde archivo con éxito", + "noSavedData": "❌ No se encontró progreso guardado", + "savedDataFound": "✅ ¡Se encontró progreso guardado! ¿Cargar para continuar?", + "savedDate": "Guardado el: {date}", + "clickLoadToContinue": "Haz clic en 'Cargar progreso' para continuar.", + "fileError": "❌ Error al procesar el archivo", + "invalidFileFormat": "❌ Formato de archivo inválido", + "paintingSpeed": "Velocidad de pintado", + "pixelsPerSecond": "píxeles/segundo", + "speedSetting": "Velocidad: {speed} píxeles/seg", + "keepAspectRatio": "Mantener proporción", + "settings": "Ajustes", + "botSettings": "Ajustes del bot", + "close": "Cerrar", + "language": "Idioma", + "themeSettings": "Ajustes de tema", + "themeSettingsDesc": "Elige tu tema de color preferido para la interfaz.", + "languageSelectDesc": "Selecciona tu idioma preferido. Los cambios se aplicarán inmediatamente.", + "autoCaptcha": "Solucionador automático de CAPTCHA (Turnstile)", + "autoCaptchaDesc": "Genera automáticamente tokens de Turnstile usando el generador integrado. Utiliza la automatización del navegador como alternativa si es necesario.", + "applySettings": "Aplicar ajustes", + "settingsSaved": "✅ ¡Ajustes guardados con éxito!", + "speedOn": "Activado", + "speedOff": "Desactivado", + "cooldownSettings": "Ajustes de tiempo de espera", + "waitCharges": "Esperar hasta que las cargas lleguen a", + "captchaSolving": "🔑 Generando token de Turnstile...", + "captchaFailed": "❌ Falló la generación de token de Turnstile. Probando método alternativo...", + "automation": "Automatización", + "noChargesThreshold": "⌛ Esperando que las cargas lleguen a {threshold}. Actual: {current}. Siguiente en {time}...", + "tokenCapturedSuccess": "¡Token capturado con éxito! Ya puedes iniciar el bot.", + "notificationsNotSupported": "Las notificaciones no son compatibles con este navegador.", + "chargesReadyNotification": "WPlace — Cargas listas", + "chargesReadyMessage": "Cargas listas: {current} / {max}. Umbral: {threshold}.", + "testNotificationTitle": "WPlace — Prueba", + "testNotificationMessage": "Esta es una notificación de prueba.", + "showStats": "Mostrar estadísticas", + "compactMode": "Modo compacto", + "refreshCharges": "Actualizar cargas", + "closeStats": "Cerrar estadísticas", + "zoomOut": "Alejar", + "zoomIn": "Acercar", + "fitToView": "Ajustar a la vista", + "actualSize": "Tamaño real (100%)", + "panMode": "Desplazamiento (arrastra para mover la vista)", + "clearIgnoredPixels": "Limpiar todos los píxeles ignorados", + "invertMask": "Invertir máscara", + "waitingSetupComplete": "🔄 Esperando que se complete la configuración inicial...", + "waitingTokenGenerator": "🔄 Esperando que se inicialice el generador de tokens...", + "uploadImageFirst": "Sube una imagen primero para capturar los colores disponibles", + "pleaseWaitInitialSetup": "🔄 Por favor, espera a que se complete la configuración inicial antes de cargar el progreso.", + "pleaseWaitFileSetup": "🔄 Por favor, espera a que se complete la configuración inicial antes de cargar desde un archivo.", + "errorSavingProgress": "❌ Error al guardar el progreso", + "errorLoadingProgress": "❌ Error al cargar el progreso", + "fileOperationsAvailable": "📂 ¡Las operaciones de archivo (Cargar/Subir) ya están disponibles!", + "tokenGeneratorReady": "🔑 ¡Generador de tokens listo!", + "paintingStats": "Estadísticas de pintado", + "enablePaintingSpeedLimit": "Activar límite de velocidad de pintado (control de lotes)", + "enableNotifications": "Activar notificaciones", + "notifyOnChargesThreshold": "Notificar cuando las cargas alcancen el umbral", + "onlyWhenNotFocused": "Solo cuando la pestaña no esté en foco", + "repeatEvery": "Repetir cada", + "minutesPl": "minuto(s)", + "grantPermission": "Otorgar permiso", + "test": "Probar", + "showAllColorsIncluding": "Mostrar todos los colores (incluidos los no disponibles)", + "chromaWeight": "Peso de croma", + "downloadPreview": "Descargar vista previa", + "apply": "Aplicar", + "cancel": "Cancelar", + "fit": "Ajustar", + "hundred": "100%", + "clear": "Limpiar", + "invert": "Invertir", + "reprocessingOverlay": "Reprocesando superposición...", + "overlayUpdated": "¡Superposición actualizada!", + "notificationsEnabled": "Notificaciones activadas.", + "notificationsPermissionDenied": "Permiso de notificaciones denegado.", + "overlayEnabled": "Superposición activada.", + "overlayDisabled": "Superposición desactivada.", + "tokenSourceSet": "Fuente de token establecida en: {source}", + "batchModeSet": "Modo por lotes establecido en: {mode}", + "randomRange": "Rango aleatorio", + "normalFixedSize": "Tamaño fijo normal", + "advancedColorSettingsReset": "Ajustes de color avanzados restablecidos.", + "shiftRowAltColumn": "Shift = Fila • Alt = Columna", + "hideTurnstileBtn": "Ocultar", + "turnstileInstructions": "Cloudflare Turnstile — por favor, completa la verificación si se muestra", + "uploadImageFirstColors": "Por favor, sube una imagen primero para capturar los colores disponibles", + "availableColors": "Colores disponibles ({count})", + "colorTooltip": "ID: {id}\nRGB: {rgb}", + "expandMode": "Modo expandido", + "minimize": "Minimizar", + "restore": "Restaurar", + "hideStats": "Ocultar estadísticas", + "paintOptions": "Opciones de pintado", + "paintWhitePixels": "Pintar píxeles blancos", + "paintWhitePixelsDescription": "Si está activado, se pintarán los píxeles blancos de la plantilla.", + "paintTransparentPixels": "Pintar píxeles transparentes", + "paintTransparentPixelsDescription": "Si está activado, se pintarán los píxeles transparentes de la plantilla", + "paintUnavailablePixels": "Pintar píxeles no disponibles", + "paintUnavailablePixelsDescription": "Si está activado, los colores de plantilla no disponibles se pintarán usando el color disponible más cercano" +} diff --git a/Extension/lang/fr.json b/Extension/lang/fr.json index af1cba1..f550f79 100644 --- a/Extension/lang/fr.json +++ b/Extension/lang/fr.json @@ -54,6 +54,7 @@ "paintingSpeed": "Vitesse de peinture", "pixelsPerSecond": "pixels/seconde", "speedSetting": "Vitesse: {speed} pixels/sec", + "keepAspectRatio": "Conserver le rapport d'aspect", "settings": "Paramètres", "botSettings": "Paramètres du Bot", "close": "Fermer", @@ -140,5 +141,18 @@ "hideStats": "Masquer les Stats", "paintOptions": "Options de peinture", "paintWhitePixels": "Peindre les pixels blancs", - "paintTransparentPixels": "Peindre les pixels transparents" + "paintWhitePixelsDescription": "Si activé, les pixels blancs du modèle seront peints.", + "paintTransparentPixels": "Peindre les pixels transparents", + "paintTransparentPixelsDescription": "Si activé, les pixels transparents du modèle seront peints", + "paintUnavailablePixels": "Peindre les pixels indisponibles", + "paintUnavailablePixelsDescription": "Si activé, les couleurs de modèle non disponibles seront peintes avec la couleur disponible la plus proche", + "colorsUpdated": "✅ Couleurs disponibles augmentées {oldCount} -> {newCount}, {diffCount} nouvelles couleurs trouvées", + "overlayTilesNotLoaded": "❌ Tuiles de carte requises non chargées. Vérifiez la connexion ou réessayez.", + "paintingBatchFailed": "❌ Échec de l'envoi du lot après plusieurs tentatives. Peinture arrêtée.", + "paintingPixelCheckFailed": "❌ Impossible de lire le pixel à ({x}, {y}). Peinture arrêtée.", + "paintingFinalBatchFailed": "⚠️ Lot final de {count} pixels échoué après plusieurs tentatives.", + "paintingStoppedByUser": "⏹️ Peinture arrêtée par l'utilisateur", + "fullChargeIn": "Pleine charge dans", + "batchSize": "Taille du lot" } + diff --git a/Extension/lang/id.json b/Extension/lang/id.json index bf09135..b2835ed 100644 --- a/Extension/lang/id.json +++ b/Extension/lang/id.json @@ -54,6 +54,7 @@ "paintingSpeed": "Kecepatan Melukis", "pixelsPerSecond": "piksel/detik", "speedSetting": "Kecepatan: {speed} piksel/detik", + "keepAspectRatio": "Pertahankan rasio aspek", "settings": "Pengaturan", "botSettings": "Pengaturan Bot", "close": "Tutup", @@ -140,5 +141,18 @@ "hideStats": "Sembunyikan Statistik", "paintOptions": "Opsi Pewarnaan", "paintWhitePixels": "Warnai piksel putih", - "paintTransparentPixels": "Warnai piksel transparan" + "paintWhitePixelsDescription": "Jika diaktifkan, piksel putih template akan dicat.", + "paintTransparentPixels": "Warnai piksel transparan", + "paintTransparentPixelsDescription": "Jika diaktifkan, piksel transparan template akan dicat", + "paintUnavailablePixels": "Cat piksel tidak tersedia", + "paintUnavailablePixelsDescription": "Jika diaktifkan, warna template yang tidak tersedia akan dicat dengan warna tersedia terdekat", + "colorsUpdated": "✅ Warna tersedia meningkat {oldCount} -> {newCount}, {diffCount} warna baru ditemukan", + "overlayTilesNotLoaded": "❌ Ubin peta yang diperlukan tidak dimuat. Periksa koneksi atau coba lagi.", + "paintingBatchFailed": "❌ Gagal mengirim batch setelah beberapa percobaan. Melukis dihentikan.", + "paintingPixelCheckFailed": "❌ Gagal membaca piksel di ({x}, {y}). Melukis dihentikan.", + "paintingFinalBatchFailed": "⚠️ Batch akhir {count} piksel gagal setelah beberapa percobaan.", + "paintingStoppedByUser": "⏹️ Melukis dihentikan oleh pengguna", + "fullChargeIn": "Muatan penuh dalam", + "batchSize": "Ukuran batch" } + diff --git a/Extension/lang/ja.json b/Extension/lang/ja.json index c95a308..48c0d61 100644 --- a/Extension/lang/ja.json +++ b/Extension/lang/ja.json @@ -54,6 +54,7 @@ "paintingSpeed": "描画速度", "pixelsPerSecond": "ピクセル/秒", "speedSetting": "速度: {speed} ピクセル/秒", + "keepAspectRatio": "アスペクト比を維持", "settings": "設定", "botSettings": "ボット設定", "close": "閉じる", @@ -140,5 +141,18 @@ "hideStats": "統計を非表示", "paintOptions": "描画オプション", "paintWhitePixels": "白いピクセルを描画", - "paintTransparentPixels": "透明ピクセルを描画" + "paintWhitePixelsDescription": "有効にすると、テンプレートの白いピクセルが描画されます。", + "paintTransparentPixels": "透明ピクセルを描画", + "paintTransparentPixelsDescription": "有効にすると、テンプレートの透明ピクセルが描画されます", + "paintUnavailablePixels": "利用不可のピクセルを描画", + "paintUnavailablePixelsDescription": "有効にすると、利用できないテンプレートの色は最も近い利用可能な色で描画されます", + "colorsUpdated": "✅ 利用可能な色が増加 {oldCount} -> {newCount}、{diffCount} 個の新しい色が見つかりました", + "overlayTilesNotLoaded": "❌ 必要なマップタイルがロードされていません。接続を確認するか、再試行してください。", + "paintingBatchFailed": "❌ 複数回の試行後、バッチ送信に失敗しました。描画を停止しました。", + "paintingPixelCheckFailed": "❌ ({x}, {y}) のピクセルを読み取れませんでした。描画を停止しました。", + "paintingFinalBatchFailed": "⚠️ {count} ピクセルの最終バッチが複数回の試行後に失敗しました。", + "paintingStoppedByUser": "⏹️ ユーザーにより停止されました", + "fullChargeIn": "フル充電まで", + "batchSize": "バッチサイズ" } + diff --git a/Extension/lang/ko.json b/Extension/lang/ko.json index f437b20..20b27b5 100644 --- a/Extension/lang/ko.json +++ b/Extension/lang/ko.json @@ -54,6 +54,7 @@ "paintingSpeed": "그리기 속도", "pixelsPerSecond": "픽셀/초", "speedSetting": "속도: {speed} 픽셀/초", + "keepAspectRatio": "종횡비 유지", "settings": "설정", "botSettings": "봇 설정", "close": "닫기", @@ -140,5 +141,18 @@ "hideStats": "통계 숨김", "paintOptions": "페인트 옵션", "paintWhitePixels": "흰색 픽셀 칠하기", - "paintTransparentPixels": "투명 픽셀 칠하기" + "paintWhitePixelsDescription": "활성화하면 템플릿 흰색 픽셀이 칠해집니다.", + "paintTransparentPixels": "투명 픽셀 칠하기", + "paintTransparentPixelsDescription": "활성화하면 템플릿 투명 픽셀이 칠해집니다", + "paintUnavailablePixels": "사용 불가능한 픽셀 칠하기", + "paintUnavailablePixelsDescription": "활성화하면 사용할 수 없는 템플릿 색상을 가장 가까운 사용 가능한 색상으로 칠합니다", + "colorsUpdated": "✅ 사용 가능한 색상 증가 {oldCount} -> {newCount}, {diffCount}개의 새 색상 발견", + "overlayTilesNotLoaded": "❌ 필요한 맵 타일이 로드되지 않았습니다. 연결을 확인하거나 다시 시도하세요.", + "paintingBatchFailed": "❌ 여러 번 시도 후 배치 전송 실패. 그리기가 중지되었습니다.", + "paintingPixelCheckFailed": "❌ ({x}, {y})의 픽셀을 읽을 수 없습니다. 그리기가 중지되었습니다.", + "paintingFinalBatchFailed": "⚠️ {count}개 픽셀의 최종 배치가 여러 번 시도 후 실패했습니다.", + "paintingStoppedByUser": "⏹️ 사용자에 의해 중지됨", + "fullChargeIn": "완전 충전까지", + "batchSize": "배치 크기" } + diff --git a/Extension/lang/pt.json b/Extension/lang/pt.json index f073d02..6f55df4 100644 --- a/Extension/lang/pt.json +++ b/Extension/lang/pt.json @@ -54,6 +54,7 @@ "paintingSpeed": "Velocidade de Pintura", "pixelsPerSecond": "pixels/segundo", "speedSetting": "Velocidade: {speed} pixels/seg", + "keepAspectRatio": "Manter proporção", "settings": "Configurações", "botSettings": "Configurações do Bot", "close": "Fechar", @@ -139,5 +140,17 @@ "hideStats": "Ocultar Estatísticas", "paintOptions": "Opções de pintura", "paintWhitePixels": "Pintar pixels brancos", - "paintTransparentPixels": "Pintar pixels transparentes" + "paintWhitePixelsDescription": "Se ativado, pixels brancos do modelo serão pintados.", + "paintTransparentPixels": "Pintar pixels transparentes", + "paintTransparentPixelsDescription": "Se ativado, pixels transparentes do modelo serão pintados", + "paintUnavailablePixels": "Pintar pixels indisponíveis", + "paintUnavailablePixelsDescription": "Se ativado, cores de modelo indisponíveis serão pintadas usando a cor disponível mais próxima", + "colorsUpdated": "✅ Cores disponíveis aumentadas {oldCount} -> {newCount}, {diffCount} novas cores encontradas", + "overlayTilesNotLoaded": "❌ Blocos de mapa necessários não carregados. Verifique a conexão ou tente novamente.", + "paintingBatchFailed": "❌ Falha ao enviar lote após várias tentativas. Pintura interrompida.", + "paintingPixelCheckFailed": "❌ Falha ao ler pixel em ({x}, {y}). Pintura interrompida.", + "paintingFinalBatchFailed": "⚠️ Lote final de {count} pixels falhou após várias tentativas.", + "paintingStoppedByUser": "⏹️ Pintura interrompida pelo usuário", + "fullChargeIn": "Carga completa em", + "batchSize": "Tamanho do lote" } \ No newline at end of file diff --git a/Extension/lang/ru.json b/Extension/lang/ru.json index 30fc553..8f58ce7 100644 --- a/Extension/lang/ru.json +++ b/Extension/lang/ru.json @@ -54,6 +54,7 @@ "paintingSpeed": "Скорость рисования", "pixelsPerSecond": "пикселей/сек", "speedSetting": "Скорость: {speed} пикс./сек", + "keepAspectRatio": "Сохранять пропорции", "settings": "Настройки", "botSettings": "Настройки бота", "close": "Закрыть", @@ -139,5 +140,17 @@ "hideStats": "Скрыть статистику", "paintOptions": "Параметры рисования", "paintWhitePixels": "Рисовать белые пиксели", - "paintTransparentPixels": "Рисовать прозрачные пиксели" + "paintWhitePixelsDescription": "Если включено, белые пиксели шаблона будут рисоваться.", + "paintTransparentPixels": "Рисовать прозрачные пиксели", + "paintTransparentPixelsDescription": "Если включено, прозрачные пиксели шаблона будут рисоваться", + "paintUnavailablePixels": "Рисовать недоступные пиксели", + "paintUnavailablePixelsDescription": "Если включено, недоступные цвета шаблона будут рисоваться ближайшим доступным цветом", + "colorsUpdated": "✅ Доступные цвета увеличены {oldCount} -> {newCount}, найдено {diffCount} новых цветов", + "overlayTilesNotLoaded": "❌ Необходимые тайлы карты не загружены. Проверьте соединение или попробуйте снова.", + "paintingBatchFailed": "❌ Не удалось отправить пакет после нескольких попыток. Рисование остановлено.", + "paintingPixelCheckFailed": "❌ Не удалось прочитать пиксель в ({x}, {y}). Рисование остановлено.", + "paintingFinalBatchFailed": "⚠️ Финальный пакет из {count} пикселей не удалось отправить после нескольких попыток.", + "paintingStoppedByUser": "⏹️ Рисование остановлено пользователем", + "fullChargeIn": "Полная зарядка через", + "batchSize": "Размер пакета" } \ No newline at end of file diff --git a/Extension/lang/tr.json b/Extension/lang/tr.json index 558b31f..b10a481 100644 --- a/Extension/lang/tr.json +++ b/Extension/lang/tr.json @@ -54,6 +54,7 @@ "paintingSpeed": "Boyama Hızı", "pixelsPerSecond": "piksel/saniye", "speedSetting": "Hız: {speed} piksel/sn", + "keepAspectRatio": "En-boy oranını koru", "settings": "Ayarlar", "botSettings": "Bot Ayarları", "close": "Kapat", @@ -140,5 +141,18 @@ "hideStats": "İstatistikleri Gizle", "paintOptions": "Boya Seçenekleri", "paintWhitePixels": "Beyaz pikselleri boya", - "paintTransparentPixels": "Şeffaf pikselleri boya" + "paintWhitePixelsDescription": "Etkinleştirilirse, şablon beyaz pikselleri boyanacaktır.", + "paintTransparentPixels": "Şeffaf pikselleri boya", + "paintTransparentPixelsDescription": "Etkinleştirilirse, şablon şeffaf pikselleri boyanacaktır", + "paintUnavailablePixels": "Kullanılamayan pikselleri boya", + "paintUnavailablePixelsDescription": "Etkinleştirilirse, kullanılamayan şablon renkleri en yakın uygun renkle boyanacaktır", + "colorsUpdated": "✅ Uygun renkler arttı {oldCount} -> {newCount}, {diffCount} yeni renk bulundu", + "overlayTilesNotLoaded": "❌ Gerekli harita karoları yüklenmedi. Bağlantıyı kontrol edin veya yeniden deneyin.", + "paintingBatchFailed": "❌ Toplu gönderim birkaç denemeden sonra başarısız oldu. Boyama durduruldu.", + "paintingPixelCheckFailed": "❌ ({x}, {y}) konumundaki piksel okunamadı. Boyama durduruldu.", + "paintingFinalBatchFailed": "⚠️ {count} pikselden oluşan son topluluk birkaç denemeden sonra başarısız oldu.", + "paintingStoppedByUser": "⏹️ Boyama kullanıcı tarafından durduruldu", + "fullChargeIn": "Tam dolum süresi", + "batchSize": "Topluluk boyutu" } + diff --git a/Extension/lang/uk.json b/Extension/lang/uk.json index 0a13c41..2474c2a 100644 --- a/Extension/lang/uk.json +++ b/Extension/lang/uk.json @@ -54,6 +54,7 @@ "paintingSpeed": "Швидкість малювання", "pixelsPerSecond": "пікселів/секунда", "speedSetting": "Швидкість: {speed} пікселів/сек", + "keepAspectRatio": "Зберігати співвідношення сторін", "settings": "Налаштування", "botSettings": "Налаштування бота", "close": "Закрити", @@ -140,5 +141,18 @@ "hideStats": "Приховати статистику", "paintOptions": "Параметри малювання", "paintWhitePixels": "Малювати білі пікселі", - "paintTransparentPixels": "Малювати прозорі пікселі" + "paintWhitePixelsDescription": "Якщо ввімкнено, білі пікселі шаблону будуть намальовані.", + "paintTransparentPixels": "Малювати прозорі пікселі", + "paintTransparentPixelsDescription": "Якщо ввімкнено, прозорі пікселі шаблону будуть намальовані", + "paintUnavailablePixels": "Малювати недоступні пікселі", + "paintUnavailablePixelsDescription": "Якщо ввімкнено, недоступні кольори шаблону будуть намальовані найближчим доступним кольором", + "colorsUpdated": "✅ Доступні кольори збільшено {oldCount} -> {newCount}, знайдено {diffCount} нових кольорів", + "overlayTilesNotLoaded": "❌ Необхідні тайли карти не завантажені. Перевір з'єднання або спробуй знову.", + "paintingBatchFailed": "❌ Не вдалося відправити пакет після кількох спроб. Малювання зупинено.", + "paintingPixelCheckFailed": "❌ Не вдалося прочитати піксель у ({x}, {y}). Малювання зупинено.", + "paintingFinalBatchFailed": "⚠️ Фінальний пакет з {count} пікселів не вдалося відправити після кількох спроб.", + "paintingStoppedByUser": "⏹️ Малювання зупинено користувачем", + "fullChargeIn": "Повний заряд через", + "batchSize": "Розмір пакета" } + diff --git a/Extension/lang/vi.json b/Extension/lang/vi.json index 5cbde24..a29472a 100644 --- a/Extension/lang/vi.json +++ b/Extension/lang/vi.json @@ -54,6 +54,7 @@ "paintingSpeed": "Tốc độ vẽ", "pixelsPerSecond": "pixel/giây", "speedSetting": "Tốc độ: {speed} pixel/giây", + "keepAspectRatio": "Giữ tỷ lệ khung hình", "settings": "Cài đặt", "botSettings": "Cài đặt Bot", "close": "Đóng", @@ -140,5 +141,18 @@ "hideStats": "Ẩn thống kê", "paintOptions": "Tùy chọn vẽ", "paintWhitePixels": "Vẽ điểm ảnh trắng", - "paintTransparentPixels": "Vẽ điểm ảnh trong suốt" + "paintWhitePixelsDescription": "Nếu được bật, các pixel trắng của mẫu sẽ được vẽ.", + "paintTransparentPixels": "Vẽ điểm ảnh trong suốt", + "paintTransparentPixelsDescription": "Nếu được bật, các pixel trong suốt của mẫu sẽ được vẽ", + "paintUnavailablePixels": "Vẽ pixel không khả dụng", + "paintUnavailablePixelsDescription": "Nếu được bật, màu mẫu không khả dụng sẽ được vẽ bằng màu khả dụng gần nhất", + "colorsUpdated": "✅ Màu khả dụng tăng {oldCount} -> {newCount}, tìm thấy {diffCount} màu mới", + "overlayTilesNotLoaded": "❌ Ô bản đồ cần thiết chưa được tải. Kiểm tra kết nối hoặc thử lại.", + "paintingBatchFailed": "❌ Gửi lô thất bại sau nhiều lần thử. Dừng vẽ.", + "paintingPixelCheckFailed": "❌ Không thể đọc pixel tại ({x}, {y}). Dừng vẽ.", + "paintingFinalBatchFailed": "⚠️ Lô cuối cùng gồm {count} pixel thất bại sau nhiều lần thử.", + "paintingStoppedByUser": "⏹️ Người dùng đã dừng vẽ", + "fullChargeIn": "Sạc đầy trong", + "batchSize": "Kích thước lô" } + diff --git a/Extension/lang/zh-CN.json b/Extension/lang/zh-CN.json index 948bb8c..e3e1ce8 100644 --- a/Extension/lang/zh-CN.json +++ b/Extension/lang/zh-CN.json @@ -54,6 +54,7 @@ "paintingSpeed": "绘制速度", "pixelsPerSecond": "像素/秒", "speedSetting": "速度: {speed} 像素/秒", + "keepAspectRatio": "保持纵横比", "settings": "设置", "botSettings": "机器人设置", "close": "关闭", @@ -140,5 +141,18 @@ "hideStats": "隐藏统计", "paintOptions": "绘图选项", "paintWhitePixels": "绘制白色像素", - "paintTransparentPixels": "绘制透明像素" + "paintWhitePixelsDescription": "如果启用,将绘制模板白色像素。", + "paintTransparentPixels": "绘制透明像素", + "paintTransparentPixelsDescription": "如果启用,将绘制模板透明像素", + "paintUnavailablePixels": "绘制不可用像素", + "paintUnavailablePixelsDescription": "如果启用,不可用的模板颜色将使用最接近的可用颜色绘制", + "colorsUpdated": "✅ 可用颜色增加 {oldCount} -> {newCount},找到 {diffCount} 个新颜色", + "overlayTilesNotLoaded": "❌ 所需的地图图块未加载。检查连接或重试。", + "paintingBatchFailed": "❌ 多次尝试后批量发送失败。绘制已停止。", + "paintingPixelCheckFailed": "❌ 无法读取 ({x}, {y}) 处的像素。绘制已停止。", + "paintingFinalBatchFailed": "⚠️ {count} 个像素的最终批次在多次尝试后失败。", + "paintingStoppedByUser": "⏹️ 已被用户停止", + "fullChargeIn": "完全充电时间", + "batchSize": "批次大小" } + diff --git a/Extension/lang/zh-TW.json b/Extension/lang/zh-TW.json index 681f011..b79d60b 100644 --- a/Extension/lang/zh-TW.json +++ b/Extension/lang/zh-TW.json @@ -54,6 +54,7 @@ "paintingSpeed": "繪製速度", "pixelsPerSecond": "像素/秒", "speedSetting": "速度: {speed} 像素/秒", + "keepAspectRatio": "保持縱橫比", "settings": "設定", "botSettings": "機器人設定", "close": "關閉", @@ -140,5 +141,18 @@ "hideStats": "隱藏統計", "paintOptions": "繪圖選項", "paintWhitePixels": "繪製白色像素", - "paintTransparentPixels": "繪製透明像素" + "paintWhitePixelsDescription": "如果啟用,將繪製範本白色像素。", + "paintTransparentPixels": "繪製透明像素", + "paintTransparentPixelsDescription": "如果啟用,將繪製範本透明像素", + "paintUnavailablePixels": "繪製不可用像素", + "paintUnavailablePixelsDescription": "如果啟用,不可用的範本顏色將使用最接近的可用顏色繪製", + "colorsUpdated": "✅ 可用顏色增加 {oldCount} -> {newCount},找到 {diffCount} 個新顏色", + "overlayTilesNotLoaded": "❌ 所需的地圖圖塊未載入。檢查連線或重試。", + "paintingBatchFailed": "❌ 多次嘗試後批次傳送失敗。繪製已停止。", + "paintingPixelCheckFailed": "❌ 無法讀取 ({x}, {y}) 處的像素。繪製已停止。", + "paintingFinalBatchFailed": "⚠️ {count} 個像素的最終批次在多次嘗試後失敗。", + "paintingStoppedByUser": "⏹️ 已被使用者停止", + "fullChargeIn": "完全充電時間", + "batchSize": "批次大小" } + diff --git a/Extension/manifest.json b/Extension/manifest.json index ab998e0..3d9a0f4 100644 --- a/Extension/manifest.json +++ b/Extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "WPlace AutoBOT Script Launcher", - "version": "2.0.5", + "version": "2.0.6", "description": "Launch and manage automation scripts for WPlace", "icons": { "16": "icons/icon16.png", diff --git a/Extension/scripts/Auto-Farm.js b/Extension/scripts/Auto-Farm.js index ee1e5db..82c8bfb 100644 --- a/Extension/scripts/Auto-Farm.js +++ b/Extension/scripts/Auto-Farm.js @@ -1,10 +1,31 @@ -// eslint-disable-next-line prettier/prettier +// ==UserScript== +// @name WPlace Auto-Farm Enhanced +// @namespace http://tampermonkey.net/ +// @version 2025-01-10 +// @description Enhanced Auto-Farm with cooldown system and canvas interaction +// @author Wbot +// @match https://wplace.live/* +// @grant none +// @icon 🌾 +// ==/UserScript== + ; (async () => { + // Prevent multiple instances + if (window.WPLACE_AUTO_FARM_ENHANCED_LOADED) { + console.warn('⚠️ Auto-Farm Enhanced already loaded'); + return; + } + window.WPLACE_AUTO_FARM_ENHANCED_LOADED = true; + + console.log('%c🌾 WPlace Auto-Farm Enhanced Starting...', 'color: #00ff41; font-weight: bold; font-size: 16px;'); + + // CONFIGURATION const CONFIG = { - START_X: 742, - START_Y: 1148, - PIXELS_PER_LINE: 100, - DELAY: 1000, + COOLDOWN_DEFAULT: 31000, + COOLDOWN_CHARGE_THRESHOLD: 10, // Wait until charges reach this amount + DELAY_BETWEEN_PIXELS: 50, // ms delay between placing pixels + DELAY_AFTER_BATCH: 2000, // ms delay after confirming batch (reopen delay) + CANVAS_SIZE: 1000, // Canvas tile size THEME: { primary: '#000000', secondary: '#111111', @@ -13,23 +34,27 @@ highlight: '#775ce3', success: '#00ff00', error: '#ff0000', + warning: '#ffaa00', }, }; + // STATE const state = { running: false, paintedCount: 0, + sessionPixels: 0, charges: { count: 0, max: 80, cooldownMs: 30000 }, userInfo: null, - lastPixel: null, minimized: false, - menuOpen: false, language: 'en', - autoRefresh: true, - pausedForManual: false, + cooldownChargeThreshold: CONFIG.COOLDOWN_CHARGE_THRESHOLD, + lastBatchTime: 0, + totalBatches: 0, }; + // UTILITIES const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); + const waitForSelector = async (selector, interval = 200, timeout = 5000) => { const start = Date.now(); while (Date.now() - start < timeout) { @@ -40,30 +65,17 @@ return null; }; - const originalFetch = window.fetch; - let capturedCaptchaToken = null; - window.fetch = async (url, options = {}) => { - if (typeof url === 'string' && url.includes('https://backend.wplace.live/s0/pixel/')) { - try { - const payload = JSON.parse(options.body || '{}'); - if (payload.t) { - console.log('✅ CAPTCHA Token Captured:', payload.t); - capturedCaptchaToken = payload.t; - if (state.pausedForManual) { - state.pausedForManual = false; - state.running = true; - updateUI( - state.language === 'pt' ? '🚀 Pintura reiniciada!' : '🚀 Farm resumed!', - 'success' - ); - paintLoop(); - } - } - } catch (e) {} - } - return originalFetch(url, options); + const msToTimeText = (ms) => { + const totalSec = Math.ceil(ms / 1000); + const h = Math.floor(totalSec / 3600); + const m = Math.floor((totalSec % 3600) / 60); + const s = totalSec % 60; + if (h > 0) return `${h}h ${m}m ${s}s`; + if (m > 0) return `${m}m ${s}s`; + return `${s}s`; }; + // API FUNCTIONS const fetchAPI = async (url, options = {}) => { try { const res = await fetch(url, { @@ -72,44 +84,12 @@ }); return await res.json(); } catch (e) { + console.error('API fetch error:', e); return null; } }; - const getRandomPosition = () => ({ - x: Math.floor(Math.random() * CONFIG.PIXELS_PER_LINE), - y: Math.floor(Math.random() * CONFIG.PIXELS_PER_LINE), - }); - - const paintPixel = async (x, y) => { - const randomColor = Math.floor(Math.random() * 31) + 1; - const url = `https://backend.wplace.live/s0/pixel/${CONFIG.START_X}/${CONFIG.START_Y}`; - const payload = JSON.stringify({ - coords: [x, y], - colors: [randomColor], - t: capturedCaptchaToken, - }); - try { - const res = await originalFetch(url, { - method: 'POST', - headers: { 'Content-Type': 'text/plain;charset=UTF-8' }, - credentials: 'include', - body: payload, - }); - if (res.status === 403) { - console.error('❌ 403 Forbidden. CAPTCHA token might be invalid or expired.'); - capturedCaptchaToken = null; - stoppedForToken = true; - return 'token_error'; - } - const data = await res.json(); - return data; - } catch (e) { - return null; - } - }; - - const getCharge = async () => { + const getCharges = async () => { const data = await fetchAPI('https://backend.wplace.live/me'); if (data) { state.userInfo = data; @@ -125,14 +105,12 @@ return state.charges; }; - const detectUserLocation = async () => { + const detectLanguage = async () => { try { const response = await fetch('https://ipapi.co/json/'); const data = await response.json(); if (data.country === 'BR') { state.language = 'pt'; - } else if (data.country === 'US') { - state.language = 'en'; } else { state.language = 'en'; } @@ -141,151 +119,277 @@ } }; - const paintLoop = async () => { + // CANVAS INTERACTION FUNCTIONS + const getRandomCanvasPosition = () => { + // Generate random position within canvas bounds + const canvas = document.querySelector('canvas'); + if (!canvas) return { x: 500, y: 500 }; + + const rect = canvas.getBoundingClientRect(); + return { + x: Math.floor(Math.random() * rect.width), + y: Math.floor(Math.random() * rect.height), + }; + }; + + const clickColorPaletteButton = async () => { + updateUI( + state.language === 'pt' ? '🎨 Abrindo paleta de cores...' : '🎨 Opening color palette...', + 'status' + ); + + // Find the "Paint" Button + const allButtons = Array.from(document.querySelectorAll('button')); + const paletteBtn = allButtons.find(b => /(Pintar|Paint)/i.test(b.textContent.trim())); + + if (paletteBtn) { + console.log('✅ Found palette button:', paletteBtn.textContent); + paletteBtn.click(); + await sleep(500); + return true; + } + + console.warn('⚠️ Paint button not found'); + return false; + }; + + const selectTransparentColor = async () => { + updateUI( + state.language === 'pt' ? '🔍 Selecionando cor transparente...' : '🔍 Selecting transparent color...', + 'status' + ); + + // Wait for color palette to appear + await sleep(300); + + // Try to find transparent color button (ID 0) + const transBtn = await waitForSelector('button#color-0', 100, 2000); + if (transBtn) { + console.log('✅ Found transparent color button'); + transBtn.click(); + await sleep(300); + return true; + } + + console.warn('⚠️ Transparent color button not found'); + return false; + }; + + const placePixelOnCanvas = async (x, y) => { + const canvas = await waitForSelector('canvas', 100, 2000); + if (!canvas) { + console.warn('⚠️ Canvas not found'); + return false; + } + + // Get canvas position + const rect = canvas.getBoundingClientRect(); + const clientX = Math.round(rect.left + x); + const clientY = Math.round(rect.top + y); + + // Simulate mouse/pointer events + const commonPointer = { + clientX: clientX, + clientY: clientY, + bubbles: true, + cancelable: true, + pointerId: 1, + isPrimary: true, + button: 0, + buttons: 1, + }; + + const commonMouse = { + clientX: clientX, + clientY: clientY, + bubbles: true, + cancelable: true, + button: 0, + buttons: 1, + }; + + + canvas.dispatchEvent(new PointerEvent('pointerdown', commonPointer)); + canvas.dispatchEvent(new MouseEvent('mousedown', commonMouse)); + canvas.dispatchEvent(new PointerEvent('pointerup', commonPointer)); + canvas.dispatchEvent(new MouseEvent('mouseup', commonMouse)); + canvas.dispatchEvent(new MouseEvent('click', commonMouse)); + + return true; + }; + + const confirmPaintBatch = async () => { + updateUI( + state.language === 'pt' ? '✅ Confirmando lote...' : '✅ Confirming batch...', + 'status' + ); + + await sleep(500); + + // Find the "Paint" or "Pintar" button + const allButtons = Array.from(document.querySelectorAll('button')); + const confirmBtn = allButtons.find(b => /(Pintar|Paint)/i.test(b.textContent.trim())); + + if (confirmBtn) { + console.log('✅ Found confirm button:', confirmBtn.textContent); + confirmBtn.click(); + await sleep(CONFIG.DELAY_AFTER_BATCH); // Wait for reopen + return true; + } + + console.warn('⚠️ Confirm button not found'); + return false; + }; + + // MAIN FARMING LOOP + const farmingLoop = async () => { while (state.running) { - const { count, cooldownMs } = state.charges; + try { + await getCharges(); + updateStats(); - if (count < 1) { - updateUI( - state.language === 'pt' - ? `⌛ Sem cargas. Esperando ${Math.ceil(cooldownMs / 1000)}s...` - : `⌛ No charges. Waiting ${Math.ceil(cooldownMs / 1000)}s...`, - 'status' - ); - await sleep(cooldownMs); - await getCharge(); - continue; - } + const { count, cooldownMs } = state.charges; - const randomPos = getRandomPosition(); - const paintResult = await paintPixel(randomPos.x, randomPos.y); - if (paintResult === 'token_error') { - if (state.autoRefresh) { - await getCharge(); - if (state.charges.count < 2) { - if (!state.pausedForManual) { - updateUI( - state.language === 'pt' - ? '⚡ Aguardando pelo menos 2 cargas para auto-refresh...' - : 'Waiting for at least 2 charges for auto-refresh...', - 'status' - ); - state.pausedForManual = true; - } - while (state.charges.count < 2) { - await sleep(60000); - await getCharge(); - updateStats(); - } - state.pausedForManual = false; - } + // Check if we have enough charges + if (count < state.cooldownChargeThreshold) { + const waitTime = cooldownMs || CONFIG.COOLDOWN_DEFAULT; updateUI( state.language === 'pt' - ? '❌ Token expirado. Aguardando elemento Paint...' - : '❌ CAPTCHA token expired. Waiting for Paint button...', + ? `⌛ Aguardando cargas (${count}/${state.cooldownChargeThreshold}). Próximo em ${msToTimeText(waitTime)}` + : `⌛ Waiting for charges (${count}/${state.cooldownChargeThreshold}). Next in ${msToTimeText(waitTime)}`, + 'status' + ); + + // Wait and recheck periodically + await sleep(Math.min(waitTime, 10000)); + continue; + } + + // We have enough charges, start farming + updateUI( + state.language === 'pt' + ? `🚀 Iniciando farming com ${count} cargas disponíveis` + : `🚀 Starting farming with ${count} charges available`, + 'success' + ); + + // Step 1: Click color palette button + const paletteOpened = await clickColorPaletteButton(); + if (!paletteOpened) { + updateUI( + state.language === 'pt' + ? '❌ Falha ao abrir paleta. Tentando novamente...' + : '❌ Failed to open palette. Retrying...', 'error' ); - const mainPaintBtn = await waitForSelector( - 'button.btn.btn-primary.btn-lg, button.btn-primary.sm\\:btn-xl' - ); - if (mainPaintBtn) mainPaintBtn.click(); - await sleep(500); - updateUI( - state.language === 'pt' ? 'Selecionando transparente...' : 'Selecting transparent...', - 'status' - ); - const transBtn = await waitForSelector('button#color-0'); - if (transBtn) transBtn.click(); - await sleep(500); - const canvas = await waitForSelector('canvas'); - if (canvas) { - canvas.setAttribute('tabindex', '0'); - canvas.focus(); - const rect = canvas.getBoundingClientRect(); - const centerX = Math.round(rect.left + rect.width / 2); - const centerY = Math.round(rect.top + rect.height / 2); - const moveEvt = new MouseEvent('mousemove', { - clientX: centerX, - clientY: centerY, - bubbles: true, - }); - canvas.dispatchEvent(moveEvt); - const keyDown = new KeyboardEvent('keydown', { - key: ' ', - code: 'Space', - bubbles: true, - }); - const keyUp = new KeyboardEvent('keyup', { key: ' ', code: 'Space', bubbles: true }); - canvas.dispatchEvent(keyDown); - canvas.dispatchEvent(keyUp); - } - await sleep(500); - updateUI( - state.language === 'pt' ? 'Confirmando pintura...' : 'Confirming paint...', - 'status' - ); - let confirmBtn = await waitForSelector( - 'button.btn.btn-primary.btn-lg, button.btn.btn-primary.sm\\:btn-xl' - ); - if (!confirmBtn) { - const allPrimary = Array.from(document.querySelectorAll('button.btn-primary')); - confirmBtn = allPrimary.length ? allPrimary[allPrimary.length - 1] : null; - } - confirmBtn?.click(); - } else { - // insufficient charges or auto-refresh disabled - if (state.autoRefresh && state.charges.count < 2) { - updateUI( - state.language === 'pt' - ? '⚡ Cargas insuficientes para auto-refresh. Por favor, clique manualmente.' - : 'Insufficient charges for auto-refresh. Please click manually.', - 'error' - ); - } - if (!state.pausedForManual) { - updateUI( - state.language === 'pt' - ? 'Auto-refresh desativado. Por favor, clique no botão pintura manualmente.' - : 'Auto-refresh disabled. Please click the Paint button manually.', - 'status' - ); - state.pausedForManual = true; - } - state.running = false; - return; + await sleep(2000); + continue; } - await sleep(1000); - continue; + + // Step 2: Select transparent color + const colorSelected = await selectTransparentColor(); + if (!colorSelected) { + updateUI( + state.language === 'pt' + ? '❌ Falha ao selecionar cor. Tentando novamente...' + : '❌ Failed to select color. Retrying...', + 'error' + ); + await sleep(2000); + continue; + } + + // Step 3: Place ALL available pixels randomly on canvas FIRST + const pixelsToPlace = count; // Use ALL available charges + updateUI( + state.language === 'pt' + ? `🎨 Colocando ${pixelsToPlace} pixels...` + : `🎨 Placing ${pixelsToPlace} pixels...`, + 'status' + ); + + let placedCount = 0; + for (let i = 0; i < pixelsToPlace; i++) { + if (!state.running) break; + + const pos = getRandomCanvasPosition(); + const placed = await placePixelOnCanvas(pos.x, pos.y); + + if (placed) { + placedCount++; + + // Update UI every 5 pixels + if (placedCount % 5 === 0) { + updateUI( + state.language === 'pt' + ? `🎨 Colocado ${placedCount}/${pixelsToPlace} pixels...` + : `🎨 Placed ${placedCount}/${pixelsToPlace} pixels...`, + 'status' + ); + } + } + + await sleep(CONFIG.DELAY_BETWEEN_PIXELS); + } + + // Step 4: NOW confirm the ENTIRE batch at once (clicks Paint button to submit ALL pixels) + updateUI( + state.language === 'pt' + ? `✅ Confirmando ${placedCount} pixels...` + : `✅ Confirming ${placedCount} pixels...`, + 'status' + ); + + const confirmed = await confirmPaintBatch(); + if (confirmed) { + state.totalBatches++; + state.paintedCount += placedCount; + state.sessionPixels += placedCount; + state.lastBatchTime = Date.now(); + + updateUI( + state.language === 'pt' + ? `✅ Lote confirmado! ${placedCount} pixels pintados` + : `✅ Batch confirmed! ${placedCount} pixels painted`, + 'success' + ); + + // Visual feedback + const effect = document.getElementById('paintEffect'); + if (effect) { + effect.style.animation = 'pulse 0.5s'; + setTimeout(() => { + effect.style.animation = ''; + }, 500); + } + } else { + updateUI( + state.language === 'pt' + ? '❌ Falha ao confirmar lote' + : '❌ Failed to confirm batch', + 'error' + ); + } + + // Update charges after batch + await getCharges(); + updateStats(); + + } catch (error) { + console.error('❌ Error in farming loop:', error); + updateUI( + state.language === 'pt' + ? `❌ Erro: ${error.message}` + : `❌ Error: ${error.message}`, + 'error' + ); + await sleep(5000); } - - if (paintResult?.painted === 1) { - state.paintedCount++; - state.lastPixel = { - x: CONFIG.START_X + randomPos.x, - y: CONFIG.START_Y + randomPos.y, - time: new Date(), - }; - state.charges.count--; - - document.getElementById('paintEffect').style.animation = 'pulse 0.5s'; - setTimeout(() => { - document.getElementById('paintEffect').style.animation = ''; - }, 500); - - updateUI(state.language === 'pt' ? '✅ Pixel pintado!' : '✅ Pixel painted!', 'success'); - } else { - updateUI(state.language === 'pt' ? '❌ Falha ao pintar' : '❌ Failed to paint', 'error'); - } - - await sleep(CONFIG.DELAY); - updateStats(); } }; + // UI FUNCTIONS const createUI = () => { - if (state.menuOpen) return; - state.menuOpen = true; - const fontAwesome = document.createElement('link'); fontAwesome.rel = 'stylesheet'; fontAwesome.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css'; @@ -294,7 +398,7 @@ const style = document.createElement('style'); style.textContent = ` @keyframes pulse { - 0% { box-shadow: 0 0 0 0 rgba(0, 255, 0, 0.7); }n + 0% { box-shadow: 0 0 0 0 rgba(0, 255, 0, 0.7); } 70% { box-shadow: 0 0 0 10px rgba(0, 255, 0, 0); } 100% { box-shadow: 0 0 0 0 rgba(0, 255, 0, 0); } } @@ -302,24 +406,24 @@ from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } - .wplace-bot-panel { + .wplace-farm-panel { position: fixed; top: 20px; right: 20px; - width: 250px; + width: 280px; background: ${CONFIG.THEME.primary}; border: 1px solid ${CONFIG.THEME.accent}; - border-radius: 8px; + border-radius: 12px; padding: 0; - box-shadow: 0 5px 15px rgba(0,0,0,0.5); + box-shadow: 0 8px 32px rgba(0,0,0,0.6); z-index: 9999; font-family: 'Segoe UI', Roboto, sans-serif; color: ${CONFIG.THEME.text}; animation: slide-in 0.4s ease-out; overflow: hidden; } - .wplace-header { - padding: 12px 15px; + .wplace-farm-header { + padding: 15px; background: ${CONFIG.THEME.secondary}; color: ${CONFIG.THEME.highlight}; font-size: 16px; @@ -330,12 +434,12 @@ cursor: move; user-select: none; } - .wplace-header-title { + .wplace-farm-title { display: flex; align-items: center; gap: 8px; } - .wplace-header-controls { + .wplace-farm-controls { display: flex; gap: 10px; } @@ -346,24 +450,20 @@ cursor: pointer; opacity: 0.7; transition: opacity 0.2s; + font-size: 14px; } .wplace-header-btn:hover { opacity: 1; } - .wplace-content { + .wplace-farm-content { padding: 15px; display: ${state.minimized ? 'none' : 'block'}; } - .wplace-controls { - display: flex; - gap: 10px; - margin-bottom: 15px; - } - .wplace-btn { - flex: 1; - padding: 10px; + .wplace-farm-btn { + width: 100%; + padding: 12px; border: none; - border-radius: 6px; + border-radius: 8px; font-weight: 600; cursor: pointer; display: flex; @@ -371,23 +471,61 @@ justify-content: center; gap: 8px; transition: all 0.2s; + margin-bottom: 12px; } - .wplace-btn:hover { + .wplace-farm-btn:hover { transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0,0,0,0.3); } - .wplace-btn-primary { - background: ${CONFIG.THEME.accent}; + .wplace-farm-btn-start { + background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); color: white; } - .wplace-btn-stop { + .wplace-farm-btn-stop { background: ${CONFIG.THEME.error}; color: white; } - .wplace-stats { + .wplace-farm-setting { background: ${CONFIG.THEME.secondary}; padding: 12px; - border-radius: 6px; - margin-bottom: 15px; + border-radius: 8px; + margin-bottom: 12px; + } + .wplace-farm-setting-label { + font-size: 13px; + opacity: 0.8; + margin-bottom: 8px; + display: flex; + align-items: center; + gap: 6px; + } + .wplace-farm-slider { + width: 100%; + height: 6px; + border-radius: 3px; + background: ${CONFIG.THEME.accent}; + outline: none; + -webkit-appearance: none; + } + .wplace-farm-slider::-webkit-slider-thumb { + -webkit-appearance: none; + width: 16px; + height: 16px; + border-radius: 50%; + background: ${CONFIG.THEME.highlight}; + cursor: pointer; + } + .wplace-farm-slider-value { + display: flex; + justify-content: space-between; + font-size: 12px; + margin-top: 4px; + } + .wplace-farm-stats { + background: ${CONFIG.THEME.secondary}; + padding: 12px; + border-radius: 8px; + margin-bottom: 12px; } .wplace-stat-item { display: flex; @@ -401,11 +539,15 @@ gap: 6px; opacity: 0.8; } - .wplace-status { - padding: 8px; - border-radius: 4px; + .wplace-stat-value { + font-weight: 600; + } + .wplace-farm-status { + padding: 10px; + border-radius: 6px; text-align: center; font-size: 13px; + line-height: 1.4; } .status-default { background: rgba(255,255,255,0.1); @@ -418,6 +560,10 @@ background: rgba(255, 0, 0, 0.1); color: ${CONFIG.THEME.error}; } + .status-status { + background: rgba(119, 92, 227, 0.1); + color: ${CONFIG.THEME.highlight}; + } #paintEffect { position: absolute; top: 0; @@ -425,72 +571,85 @@ width: 100%; height: 100%; pointer-events: none; - border-radius: 8px; + border-radius: 12px; } `; document.head.appendChild(style); const translations = { pt: { - title: 'WPlace Auto-Farm', - start: 'Iniciar', - stop: 'Parar', - ready: 'Pronto para começar', + title: 'WPlace Auto-Farm 🌾', + start: 'Iniciar Farm', + stop: 'Parar Farm', + ready: 'Pronto para iniciar', user: 'Usuário', - pixels: 'Pixels', + sessionPixels: 'Pixels (Sessão)', + totalPixels: 'Total Pintado', charges: 'Cargas', - level: 'Level', + batches: 'Lotes', + threshold: 'Limite de Cargas', + thresholdDesc: 'Aguardar até ter esta quantidade de cargas', }, en: { - title: 'WPlace Auto-Farm', - start: 'Start', - stop: 'Stop', + title: 'WPlace Auto-Farm 🌾', + start: 'Start Farm', + stop: 'Stop Farm', ready: 'Ready to start', user: 'User', - pixels: 'Pixels', + sessionPixels: 'Pixels (Session)', + totalPixels: 'Total Painted', charges: 'Charges', - level: 'Level', + batches: 'Batches', + threshold: 'Charge Threshold', + thresholdDesc: 'Wait until charges reach this amount', }, }; const t = translations[state.language] || translations.en; const panel = document.createElement('div'); - panel.className = 'wplace-bot-panel'; + panel.className = 'wplace-farm-panel'; panel.innerHTML = `
-
-
- +
+
+ ${t.title}
-
+
-
-
- - +
+ + +
+
+ + ${t.threshold} +
+ +
+ ${t.thresholdDesc} + ${state.cooldownChargeThreshold} +
-
+
-
${state.language === 'pt' ? 'Carregando...' : 'Loading...'}
+
${state.language === 'pt' ? 'Carregando...' : 'Loading...'}
-
+
${t.ready}
@@ -498,17 +657,14 @@ document.body.appendChild(panel); - const header = panel.querySelector('.wplace-header'); - let pos1 = 0, - pos2 = 0, - pos3 = 0, - pos4 = 0; + // Make panel draggable + const header = panel.querySelector('.wplace-farm-header'); + let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; header.onmousedown = dragMouseDown; function dragMouseDown(e) { if (e.target.closest('.wplace-header-btn')) return; - e = e || window.event; e.preventDefault(); pos3 = e.clientX; @@ -533,40 +689,29 @@ document.onmousemove = null; } + // Event listeners const toggleBtn = panel.querySelector('#toggleBtn'); const minimizeBtn = panel.querySelector('#minimizeBtn'); - const statusText = panel.querySelector('#statusText'); - const content = panel.querySelector('.wplace-content'); - const statsArea = panel.querySelector('#statsArea'); + const content = panel.querySelector('.wplace-farm-content'); + const thresholdSlider = panel.querySelector('#thresholdSlider'); + const thresholdValue = panel.querySelector('#thresholdValue'); toggleBtn.addEventListener('click', () => { state.running = !state.running; - if (state.running && !capturedCaptchaToken) { - updateUI( - state.language === 'pt' - ? '❌ Token não capturado. Clique em qualquer pixel primeiro.' - : '❌ CAPTCHA token not captured. Please click any pixel manually first.', - 'error' - ); - state.running = false; - return; - } - if (state.running) { toggleBtn.innerHTML = ` ${t.stop}`; - toggleBtn.classList.remove('wplace-btn-primary'); - toggleBtn.classList.add('wplace-btn-stop'); + toggleBtn.classList.remove('wplace-farm-btn-start'); + toggleBtn.classList.add('wplace-farm-btn-stop'); updateUI( - state.language === 'pt' ? '🚀 Pintura iniciada!' : '🚀 Painting started!', + state.language === 'pt' ? '🚀 Farm iniciado!' : '🚀 Farm started!', 'success' ); - paintLoop(); + farmingLoop(); } else { toggleBtn.innerHTML = ` ${t.start}`; - toggleBtn.classList.add('wplace-btn-primary'); - toggleBtn.classList.remove('wplace-btn-stop'); - statsArea.innerHTML = ''; + toggleBtn.classList.add('wplace-farm-btn-start'); + toggleBtn.classList.remove('wplace-farm-btn-stop'); updateUI(state.language === 'pt' ? '⏹️ Parado' : '⏹️ Stopped', 'default'); } }); @@ -577,74 +722,76 @@ minimizeBtn.innerHTML = ``; }); - const autoRefreshCheckbox = panel.querySelector('#autoRefreshCheckbox'); - autoRefreshCheckbox.addEventListener('change', () => { - state.autoRefresh = autoRefreshCheckbox.checked; - }); - - window.addEventListener('beforeunload', () => { - state.menuOpen = false; + thresholdSlider.addEventListener('input', (e) => { + state.cooldownChargeThreshold = parseInt(e.target.value); + thresholdValue.textContent = state.cooldownChargeThreshold; }); }; - window.updateUI = (message, type = 'default') => { + const updateUI = (message, type = 'default') => { const statusText = document.querySelector('#statusText'); if (statusText) { statusText.textContent = message; - statusText.className = `wplace-status status-${type}`; - statusText.style.animation = 'none'; - void statusText.offsetWidth; - statusText.style.animation = 'slide-in 0.3s ease-out'; + statusText.className = `wplace-farm-status status-${type}`; } }; - window.updateStats = async () => { - await getCharge(); + const updateStats = async () => { const statsArea = document.querySelector('#statsArea'); - if (statsArea) { - const t = { - pt: { - user: 'Usuário', - pixels: 'Pixels', - charges: 'Cargas', - level: 'Level', - }, - en: { - user: 'User', - pixels: 'Pixels', - charges: 'Charges', - level: 'Level', - }, - }[state.language] || { - user: 'User', - pixels: 'Pixels', - charges: 'Charges', - level: 'Level', - }; + if (!statsArea) return; - statsArea.innerHTML = ` -
-
${t.user}
-
${state.userInfo.name}
-
-
-
${t.pixels}
-
${state.paintedCount}
-
-
-
${t.charges}
-
${Math.floor(state.charges.count)}/${Math.floor(state.charges.max)}
-
-
-
${t.level}
-
${state.userInfo?.level || '0'}
-
- `; - } + const t = { + pt: { + user: 'Usuário', + sessionPixels: 'Pixels (Sessão)', + totalPixels: 'Total Pintado', + charges: 'Cargas', + batches: 'Lotes', + }, + en: { + user: 'User', + sessionPixels: 'Pixels (Session)', + totalPixels: 'Total Painted', + charges: 'Charges', + batches: 'Batches', + }, + }[state.language] || { + user: 'User', + sessionPixels: 'Pixels (Session)', + totalPixels: 'Total Painted', + charges: 'Charges', + batches: 'Batches', + }; + + statsArea.innerHTML = ` +
+
${t.user}
+
${state.userInfo?.name || '---'}
+
+
+
${t.sessionPixels}
+
${state.sessionPixels}
+
+
+
${t.totalPixels}
+
${state.paintedCount}
+
+
+
${t.charges}
+
${Math.floor(state.charges.count)}/${Math.floor(state.charges.max)}
+
+
+
${t.batches}
+
${state.totalBatches}
+
+ `; }; - await detectUserLocation(); + // INITIALIZE + await detectLanguage(); createUI(); - await getCharge(); + await getCharges(); updateStats(); + + console.log('✅ Auto-Farm Enhanced ready!'); })(); diff --git a/Extension/scripts/Auto-Image.js b/Extension/scripts/Auto-Image.js index fa8c943..223ed59 100644 --- a/Extension/scripts/Auto-Image.js +++ b/Extension/scripts/Auto-Image.js @@ -87,7 +87,7 @@ function getText(key, params) { OVERLAY: { OPACITY_DEFAULT: 0.6, BLUE_MARBLE_DEFAULT: false, - ditheringEnabled: false, + ditheringEnabled: true, }, // --- START: Color data from colour-converter.js --- // New color structure with proper ID mapping COLOR_MAP: { @@ -325,7 +325,7 @@ function getText(key, params) { COORDINATE_BLOCK_WIDTH: 6, COORDINATE_BLOCK_HEIGHT: 2, autoSwap: true, - autoBuy: buyTypes[2], + autoBuy: 'none', // "none", "max_charges", or "paint_charges" autoBuyToggle: false, maxChargesStopEnable: false, maxChargesBeforeStop: 1500, @@ -461,6 +461,7 @@ function getText(key, params) { // Available languages const AVAILABLE_LANGUAGES = [ 'en', + 'es', 'ru', 'pt', 'vi', @@ -486,9 +487,13 @@ function getText(key, params) { if (window.AUTOBOT_LANGUAGES && Object.keys(window.AUTOBOT_LANGUAGES).length > 0) { console.log(`%c🔍 Checking extension local resources...`, 'color: #06b6d4;'); - const langFile = language + '.json'; - if (window.AUTOBOT_LANGUAGES[langFile]) { - const translations = window.AUTOBOT_LANGUAGES[langFile]; + const langFile = `${language}.json`; + const availableFiles = Object.keys(window.AUTOBOT_LANGUAGES || {}); + const regionalMatch = availableFiles.find(file => file.toLowerCase().startsWith(`${language.toLowerCase()}-`)); + const resolvedFile = window.AUTOBOT_LANGUAGES[langFile] ? langFile : regionalMatch; + + if (resolvedFile && window.AUTOBOT_LANGUAGES[resolvedFile]) { + const translations = window.AUTOBOT_LANGUAGES[resolvedFile]; // Validate that translations is an object with keys if ( @@ -499,6 +504,9 @@ function getText(key, params) { window.loadedTranslations[language] = translations; console.log(`%c✅ Loaded ${language} translations from EXTENSION LOCAL FILES`, 'color: #10b981; font-weight: bold;'); console.log(` 📍 Source: Extension local storage (chrome-extension://)`); + if (resolvedFile !== langFile) { + console.log(` 🔄 Resolved locale: ${resolvedFile.replace('.json', '')}`); + } console.log(` 📏 Keys count: ${Object.keys(translations).length}`); console.log(` 🚀 Performance: Instant load (no network request)`); console.groupEnd(); @@ -2391,79 +2399,103 @@ function getText(key, params) {
`; - // Stats Window - Separate UI const statsContainer = document.createElement('div'); statsContainer.id = 'wplace-stats-container'; statsContainer.style.display = 'none'; statsContainer.innerHTML = ` -
-
- - ${Utils.t('paintingStats')} -
-
- - -
+
+
+ + ${Utils.t('paintingStats')}
-
-
-
-
-
${Utils.t( - 'initMessage' - )}
-
-
-
- -
-
-
- - Account Swapper -
- -
-
- -
-
-
- - Auto Buy Charges -
- -
-
- -
-
- - All Accounts - -
-
-
Click the icon to load accounts.
+
+ + +
+
+
+
+
+
+
${Utils.t('initMessage')}
+ +
+
+
+ + Account Swapper +
+ +
+
+ +
+
+
+ + Auto Buy Charges +
+
+
+ + + +
+
+
+ +
+
+ + All Accounts + +
+
+
Click the icon to load accounts.
+
+
+
`; + function initPillSelector() { + const buttons = statsContainer.querySelectorAll(".pill-btn"); + const highlight = statsContainer.querySelector(".pill-highlight"); + + buttons.forEach((btn, index) => { + btn.addEventListener("click", () => { + highlight.style.transform = `translateX(${index * 100}%)`; + + buttons.forEach((b) => b.classList.remove("active")); + btn.classList.add("active"); + + CONFIG.autoBuy = btn.dataset.mode; + if (CONFIG.autoBuy === 'none') { + console.log("AutoBuy disabled"); + CONFIG.autoBuyToggle = false; + } + else { + CONFIG.autoBuyToggle = true; + console.log("AutoBuy enabled"); + } + console.log("AutoBuy mode set to:", CONFIG.autoBuy); + }); + }); + } + + initPillSelector(); + // Modern Settings Container with Theme Support // Use the theme variable already declared at the top of createUI function const settingsContainer = document.createElement('div'); @@ -2872,6 +2904,7 @@ function getText(key, params) { + @@ -3410,7 +3443,7 @@ function getText(key, params) { autoSwapToggle.addEventListener('change', (e) => { CONFIG.autoSwap = e.target.checked; console.log(`🔄 Auto-swap ${CONFIG.autoSwap ? 'enabled' : 'disabled'}`); - + // Handle autoBuy toggle dependency const autoBuyToggle = statsContainer.querySelector('#autoBuyToggle'); if (autoBuyToggle) { @@ -3433,7 +3466,7 @@ function getText(key, params) { if (autoBuyToggle) { autoBuyToggle.checked = CONFIG.autoBuyToggle; autoBuyToggle.disabled = !CONFIG.autoSwap; // Disable if autoSwap is off - + autoBuyToggle.addEventListener('change', (e) => { CONFIG.autoBuyToggle = e.target.checked; console.log(`💰 Auto-buy ${CONFIG.autoBuyToggle ? 'enabled' : 'disabled'}`); @@ -4701,7 +4734,7 @@ function getText(key, params) { canvasStack.style.width = newWidth + 'px'; canvasStack.style.height = newHeight + 'px'; baseCtx.imageSmoothingEnabled = false; - + if (!state.availableColors || state.availableColors.length === 0) { if (baseProcessor !== processor && (!baseProcessor.img || !baseProcessor.canvas)) { await baseProcessor.load(); @@ -4714,7 +4747,7 @@ function getText(key, params) { updateZoomLayout(); return; } - + if (baseProcessor !== processor && (!baseProcessor.img || !baseProcessor.canvas)) { await baseProcessor.load(); } @@ -5690,26 +5723,26 @@ function getText(key, params) { Utils.showAlert('No image available for editing. Please upload an image first.', 'error'); return; } - + // Hide resize panel resizeContainer.style.display = 'none'; - + // Create edit panel if it doesn't exist let editOverlay = document.getElementById('editOverlay'); if (!editOverlay) { createEditPanel(); editOverlay = document.getElementById('editOverlay'); } - + // Get current image data from baseCanvas const imageData = baseCanvas.toDataURL(); - + // Initialize edit panel with current image initializeEditPanel(imageData); - + // Show edit panel editOverlay.style.display = 'block'; - + console.log('✨ Pixel Art Editor opened successfully'); } catch (error) { console.error('Error opening pixel art editor:', error); @@ -5721,7 +5754,7 @@ function getText(key, params) { const editOverlay = document.createElement('div'); editOverlay.id = 'editOverlay'; editOverlay.className = 'edit-overlay'; - + editOverlay.innerHTML = `
@@ -5830,9 +5863,9 @@ function getText(key, params) {
`; - + document.body.appendChild(editOverlay); - + // Set up event handlers setupEditPanelEvents(); } @@ -5856,69 +5889,69 @@ function getText(key, params) { const zoomFit = document.getElementById('zoomFit'); const zoom100 = document.getElementById('zoom100'); const minimapCanvas = document.getElementById('minimapCanvas'); - + // Back to resize panel editBackBtn.onclick = () => { document.getElementById('editOverlay').style.display = 'none'; resizeContainer.style.display = 'block'; }; - + // Apply changes editApplyBtn.onclick = () => { applyEditChanges(); document.getElementById('editOverlay').style.display = 'none'; resizeContainer.style.display = 'block'; }; - + // Tool selection paintBrush.onclick = () => { selectTool('paint'); }; - + eraseTool.onclick = () => { selectTool('erase'); }; - + eyedropperTool.onclick = () => { selectTool('eyedropper'); }; - + fillTool.onclick = () => { selectTool('fill'); }; - + // Grid toggle showGrid.onclick = () => { editState.showGrid = !editState.showGrid; showGrid.classList.toggle('active', editState.showGrid); redrawCanvas(); }; - + // Brush size brushSize.oninput = () => { brushSizeValue.textContent = brushSize.value; updateBrushSize(parseInt(brushSize.value)); }; - + // Undo/Redo undoBtn.onclick = () => undoEdit(); redoBtn.onclick = () => redoEdit(); - + // Reset view resetViewBtn.onclick = () => resetEditView(); - + // Enhanced zoom controls editZoomIn.onclick = () => zoomIn(); editZoomOut.onclick = () => zoomOut(); - + zoomSelect.onchange = () => { const newZoom = parseFloat(zoomSelect.value); setZoom(newZoom); }; - + zoomFit.onclick = () => fitToWindow(); zoom100.onclick = () => setZoom(1); - + // Minimap navigation if (minimapCanvas) { minimapCanvas.onclick = (e) => navigateToMinimapPosition(e); @@ -5955,23 +5988,23 @@ function getText(key, params) { function calculateOptimalPanelSize(imageWidth, imageHeight) { const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; - + // Reserve minimal space for UI elements for almost fullscreen experience const uiReserved = { header: 80, - toolbar: 60, + toolbar: 60, bottomBar: 120, padding: 40 }; - + const maxCanvasWidth = viewportWidth - uiReserved.padding; const maxCanvasHeight = viewportHeight - uiReserved.header - uiReserved.toolbar - uiReserved.bottomBar - uiReserved.padding; - + // Calculate optimal initial zoom to fit image const scaleX = maxCanvasWidth / imageWidth; const scaleY = maxCanvasHeight / imageHeight; const initialZoom = Math.min(scaleX, scaleY, 1); // Don't zoom in initially - + return { panelWidth: Math.min(viewportWidth * 0.95, imageWidth * initialZoom + uiReserved.colorPanel + uiReserved.padding), panelHeight: Math.min(viewportHeight * 0.95, imageHeight * initialZoom + uiReserved.toolbar + uiReserved.statusBar + uiReserved.canvasToolbar + uiReserved.padding), @@ -5984,7 +6017,7 @@ function getText(key, params) { function initializeEditPanel(imageData) { const editCanvas = document.getElementById('editCanvas'); const ctx = editCanvas.getContext('2d'); - + // Reset edit state editState.zoom = 1; editState.panX = 0; @@ -5997,19 +6030,19 @@ function getText(key, params) { editState.isPanning = false; editState.isDrawing = false; editState.lastPaintPos = null; - + // Set canvas size to match baseCanvas editCanvas.width = baseCanvas.width; editCanvas.height = baseCanvas.height; editState.canvasWidth = editCanvas.width; editState.canvasHeight = editCanvas.height; - + // Configure canvas context for pixel art ctx.imageSmoothingEnabled = false; ctx.webkitImageSmoothingEnabled = false; ctx.mozImageSmoothingEnabled = false; ctx.msImageSmoothingEnabled = false; - + // Calculate optimal panel size const panelSize = calculateOptimalPanelSize(editCanvas.width, editCanvas.height); const editContainer = document.querySelector('.edit-container'); @@ -6017,44 +6050,55 @@ function getText(key, params) { editContainer.style.width = panelSize.panelWidth + 'px'; editContainer.style.height = panelSize.panelHeight + 'px'; } - + // Setup canvas container setupCanvasContainer(); + // Add CSS checkerboard background to the wrapper element (not the canvas) + // This allows users to see transparency without saving it to the template + const canvasWrapper = document.getElementById('editCanvasWrapper'); + if (canvasWrapper) { + canvasWrapper.style.background = ` + repeating-conic-gradient(#f0f0f0 0% 25%, #e0e0e0 0% 50%) + 50% / 16px 16px + `; + } + // Load image onto canvas const img = new Image(); img.onload = () => { ctx.clearRect(0, 0, editCanvas.width, editCanvas.height); + // REMOVED: drawCheckerboardBackground - prevents checkerboard from being saved with template ctx.drawImage(img, 0, 0); - - // Always fit artwork to the visible area on start and center - fitToWindow(); - centerCanvas(); - + + // Always fit artwork to the visible area on start and center + fitToWindow(); + centerCanvas(); + // Setup minimap setupMinimap(); - + // Save initial state for undo saveEditState(); - + // Set up canvas drawing events setupCanvasDrawing(); - + // Initialize color palette initializeEditColorPalette(); - + // Setup keyboard shortcuts setupKeyboardShortcuts(); - + // Setup touch support setupTouchSupport(); - + // Set initial tool selectTool('paint'); - + // Update status bar updateStatusBar(0, 0); - + // Center canvas initially centerCanvas(); }; @@ -6066,43 +6110,43 @@ function getText(key, params) { const canvas = document.getElementById('editCanvas'); const wrapper = document.getElementById('editCanvasWrapper'); const container = document.getElementById('editCanvasContainer'); - + if (!canvas || !wrapper || !container) return null; - + // Get the actual canvas element bounds (after CSS transform) const canvasRect = canvas.getBoundingClientRect(); - + // Calculate relative position within the actual canvas bounds const relativeX = (clientX - canvasRect.left) / canvasRect.width * canvas.width; const relativeY = (clientY - canvasRect.top) / canvasRect.height * canvas.height; - + // Convert to canvas coordinates const canvasX = Math.floor(relativeX); const canvasY = Math.floor(relativeY); - + // Bounds checking if (canvasX < 0 || canvasX >= canvas.width || canvasY < 0 || canvasY >= canvas.height) { return null; } - + return { x: canvasX, y: canvasY }; } function setupCanvasDrawing() { const editCanvas = document.getElementById('editCanvas'); const ctx = editCanvas.getContext('2d'); - + let isDrawing = false; let isPanning = false; let lastX = 0; let lastY = 0; let panStartX = 0; let panStartY = 0; - + const getMousePos = (e) => { return mapClientToCanvas(e.clientX, e.clientY); }; - + editCanvas.onmousedown = (e) => { if (e.button === 2 || e.ctrlKey) { // Right click or Ctrl+click for panning editState.isPanning = true; @@ -6112,38 +6156,38 @@ function getText(key, params) { e.preventDefault(); return; } - + const pos = getMousePos(e); if (!pos) return; - + if (editState.currentTool === 'eyedropper') { handleEyedropper(pos.x, pos.y); return; } - + if (editState.currentTool === 'fill') { floodFill(pos.x, pos.y, editState.currentColor); saveEditState(); return; } - + editState.isDrawing = true; editState.lastPaintPos = { x: pos.x, y: pos.y }; lastX = pos.x; lastY = pos.y; - + paintAtPosition(pos.x, pos.y, true); }; - + editCanvas.onmousemove = (e) => { const pos = getMousePos(e); - + if (pos) { editState.mouseX = pos.x; editState.mouseY = pos.y; updateStatusBar(pos.x, pos.y); } - + if (editState.isPanning) { editState.panX = e.clientX - panStartX; editState.panY = e.clientY - panStartY; @@ -6152,64 +6196,64 @@ function getText(key, params) { updateMinimap(); return; } - + if (!editState.isDrawing) { return; } - + if (pos && editState.lastPaintPos) { paintAtPosition(pos.x, pos.y, true); editState.lastPaintPos = { x: pos.x, y: pos.y }; } }; - + editCanvas.onmouseup = (e) => { if (editState.isPanning) { editState.isPanning = false; selectTool(editState.currentTool); // Restore cursor return; } - + if (editState.isDrawing) { editState.isDrawing = false; editState.lastPaintPos = null; saveEditState(); } }; - + editCanvas.onmouseleave = () => { if (editState.isPanning) { editState.isPanning = false; selectTool(editState.currentTool); // Restore cursor } - + if (editState.isDrawing) { editState.isDrawing = false; editState.lastPaintPos = null; saveEditState(); } }; - + // Prevent context menu on right click editCanvas.oncontextmenu = (e) => { e.preventDefault(); return false; }; - + // Enhanced zoom with mouse wheel (zoom to cursor) editCanvas.onwheel = (e) => { e.preventDefault(); - const zoomFactor = e.deltaY < 0 ? 1.2 : 1/1.2; + const zoomFactor = e.deltaY < 0 ? 1.2 : 1 / 1.2; zoomToPoint(editState.zoom * zoomFactor, e.clientX, e.clientY); }; } function paintAtPosition(x, y, isMouseDown = true) { if (!isMouseDown || !editState.currentColor) return; - + const canvas = document.getElementById('editCanvas'); const ctx = canvas.getContext('2d'); - + if (editState.lastPaintPos && editState.currentTool === 'paint') { // Draw line from last position to current position drawLine(ctx, editState.lastPaintPos.x, editState.lastPaintPos.y, x, y, editState.currentTool); @@ -6217,9 +6261,9 @@ function getText(key, params) { // Single brush stroke drawBrush(ctx, x, y, editState.currentTool); } - + editState.lastPaintPos = { x, y }; - + // Update minimap thumbnail if (!editState.updatePending) { editState.updatePending = true; @@ -6233,7 +6277,7 @@ function getText(key, params) { function drawBrush(ctx, x, y, tool) { const size = editState.brushSize; const halfSize = Math.floor(size / 2); - + if (tool === 'paint') { ctx.fillStyle = editState.currentColor; for (let dx = 0; dx < size; dx++) { @@ -6241,7 +6285,7 @@ function getText(key, params) { // Center the brush at the cursor position const px = x - halfSize + dx; const py = y - halfSize + dy; - + if (px >= 0 && px < ctx.canvas.width && py >= 0 && py < ctx.canvas.height) { ctx.fillRect(px, py, 1, 1); } @@ -6253,7 +6297,7 @@ function getText(key, params) { // Center the brush at the cursor position const px = x - halfSize + dx; const py = y - halfSize + dy; - + if (px >= 0 && px < ctx.canvas.width && py >= 0 && py < ctx.canvas.height) { ctx.clearRect(px, py, 1, 1); } @@ -6269,16 +6313,16 @@ function getText(key, params) { function erasePixel(x, y) { const editCanvas = document.getElementById('editCanvas'); const ctx = editCanvas.getContext('2d'); - + const size = editState.brushSize; const halfSize = Math.floor(size / 2); - + for (let dx = 0; dx < size; dx++) { for (let dy = 0; dy < size; dy++) { // Center the brush at the cursor position (consistent with drawBrush) const px = x - halfSize + dx; const py = y - halfSize + dy; - + if (px >= 0 && px < editCanvas.width && py >= 0 && py < editCanvas.height) { ctx.clearRect(px, py, 1, 1); } @@ -6293,15 +6337,15 @@ function getText(key, params) { const sx = x1 < x2 ? 1 : -1; const sy = y1 < y2 ? 1 : -1; let err = dx - dy; - + let x = x1; let y = y1; - + while (true) { drawBrush(ctx, x, y, tool); - + if (x === x2 && y === y2) break; - + const e2 = 2 * err; if (e2 > -dy) { err -= dy; @@ -6321,15 +6365,15 @@ function getText(key, params) { const sx = x1 < x2 ? 1 : -1; const sy = y1 < y2 ? 1 : -1; let err = dx - dy; - + let x = x1; let y = y1; - + while (true) { erasePixel(x, y); - + if (x === x2 && y === y2) break; - + const e2 = 2 * err; if (e2 > -dy) { err -= dy; @@ -6344,14 +6388,14 @@ function getText(key, params) { function selectTool(tool) { editState.currentTool = tool; - + document.querySelectorAll('.edit-tool').forEach(btn => { btn.classList.remove('active'); }); - + const editCanvas = document.getElementById('editCanvas'); - - switch(tool) { + + switch (tool) { case 'paint': document.getElementById('paintBrush').classList.add('active'); editCanvas.style.cursor = 'crosshair'; @@ -6378,18 +6422,18 @@ function getText(key, params) { function initializeEditColorPalette() { const colorGrid = document.getElementById('editColorGrid'); const currentColorDisplay = document.getElementById('currentColorDisplay'); - + colorGrid.innerHTML = ''; - + let availableColors = []; - + // Try to get colors from state first if (state && state.availableColors && state.availableColors.length > 0) { availableColors = state.availableColors.map(color => ({ id: color.id, name: color.name, rgb: color.rgb, - hex: `#${color.rgb[0].toString(16).padStart(2,'0')}${color.rgb[1].toString(16).padStart(2,'0')}${color.rgb[2].toString(16).padStart(2,'0')}` + hex: `#${color.rgb[0].toString(16).padStart(2, '0')}${color.rgb[1].toString(16).padStart(2, '0')}${color.rgb[2].toString(16).padStart(2, '0')}` })); } else { // Fallback to CONFIG.COLOR_MAP @@ -6399,27 +6443,27 @@ function getText(key, params) { id: color.id, name: color.name, rgb: [color.rgb.r, color.rgb.g, color.rgb.b], - hex: `#${color.rgb.r.toString(16).padStart(2,'0')}${color.rgb.g.toString(16).padStart(2,'0')}${color.rgb.b.toString(16).padStart(2,'0')}` + hex: `#${color.rgb.r.toString(16).padStart(2, '0')}${color.rgb.g.toString(16).padStart(2, '0')}${color.rgb.b.toString(16).padStart(2, '0')}` })); } - + // Fallback to basic colors if nothing available if (availableColors.length === 0) { availableColors = [ - {id: 0, name: 'Black', rgb: [0,0,0], hex: '#000000'}, - {id: 1, name: 'White', rgb: [255,255,255], hex: '#ffffff'}, - {id: 2, name: 'Red', rgb: [255,0,0], hex: '#ff0000'}, - {id: 3, name: 'Green', rgb: [0,255,0], hex: '#00ff00'}, - {id: 4, name: 'Blue', rgb: [0,0,255], hex: '#0000ff'} + { id: 0, name: 'Black', rgb: [0, 0, 0], hex: '#000000' }, + { id: 1, name: 'White', rgb: [255, 255, 255], hex: '#ffffff' }, + { id: 2, name: 'Red', rgb: [255, 0, 0], hex: '#ff0000' }, + { id: 3, name: 'Green', rgb: [0, 255, 0], hex: '#00ff00' }, + { id: 4, name: 'Blue', rgb: [0, 0, 255], hex: '#0000ff' } ]; } - + // Update color count const colorCount = document.getElementById('editColorCount'); if (colorCount) { colorCount.textContent = availableColors.length; } - + availableColors.forEach(color => { const colorBtn = document.createElement('button'); colorBtn.className = 'color-btn'; @@ -6427,24 +6471,24 @@ function getText(key, params) { colorBtn.title = `${color.name} (${color.hex})`; colorBtn.dataset.colorId = color.id; colorBtn.dataset.colorHex = color.hex; - + colorBtn.onclick = () => { editState.currentColor = color.hex; editState.currentColorId = color.id; currentColorDisplay.style.backgroundColor = color.hex; - + // Update active color document.querySelectorAll('.color-btn').forEach(btn => { btn.classList.remove('selected'); }); colorBtn.classList.add('selected'); - + updateStatusBar(editState.mouseX, editState.mouseY); }; - + colorGrid.appendChild(colorBtn); }); - + // Set first color as default if (availableColors.length > 0) { editState.currentColor = availableColors[0].hex; @@ -6457,50 +6501,51 @@ function getText(key, params) { function saveEditState() { const editCanvas = document.getElementById('editCanvas'); const imageData = editCanvas.toDataURL(); - + editState.undoStack.push(imageData); - + // Limit undo stack size if (editState.undoStack.length > 50) { editState.undoStack.shift(); } - + // Clear redo stack when new action is performed editState.redoStack = []; - + updateUndoRedoButtons(); } function undoEdit() { if (editState.undoStack.length <= 1) return; - + const currentState = editState.undoStack.pop(); editState.redoStack.push(currentState); - + const previousState = editState.undoStack[editState.undoStack.length - 1]; loadEditState(previousState); - + updateUndoRedoButtons(); } function redoEdit() { if (editState.redoStack.length === 0) return; - + const nextState = editState.redoStack.pop(); editState.undoStack.push(nextState); - + loadEditState(nextState); - + updateUndoRedoButtons(); } function loadEditState(imageData) { const editCanvas = document.getElementById('editCanvas'); const ctx = editCanvas.getContext('2d'); - + const img = new Image(); img.onload = () => { ctx.clearRect(0, 0, editCanvas.width, editCanvas.height); + // REMOVED: drawCheckerboardBackground - prevents checkerboard from being saved with template ctx.drawImage(img, 0, 0); }; img.src = imageData; @@ -6509,11 +6554,11 @@ function getText(key, params) { function updateUndoRedoButtons() { const undoBtn = document.getElementById('undoBtn'); const redoBtn = document.getElementById('redoBtn'); - + if (undoBtn) { undoBtn.disabled = editState.undoStack.length <= 1; } - + if (redoBtn) { redoBtn.disabled = editState.redoStack.length === 0; } @@ -6524,7 +6569,7 @@ function getText(key, params) { if (wrapper) { wrapper.style.transform = `translate(${editState.panX}px, ${editState.panY}px) scale(${editState.zoom})`; } - + // Update zoom select const zoomSelect = document.getElementById('zoomSelect'); if (zoomSelect) { @@ -6540,9 +6585,9 @@ function getText(key, params) { const canvasContainer = document.getElementById('editCanvasContainer'); const editCanvas = document.getElementById('editCanvas'); const wrapper = document.getElementById('editCanvasWrapper'); - + if (!canvasContainer || !wrapper) return; - + // Setup container styles canvasContainer.style.cssText = ` position: relative; @@ -6561,7 +6606,7 @@ function getText(key, params) { background-position: 0 0, 0 10px, 10px -10px, -10px 0px; background-color: #ddd; `; - + // Setup wrapper styles wrapper.style.cssText = ` position: relative; @@ -6576,28 +6621,28 @@ function getText(key, params) { function zoomToPoint(newZoom, clientX, clientY) { const container = document.getElementById('editCanvasContainer'); const wrapper = document.getElementById('editCanvasWrapper'); - + if (!container || !wrapper) return; - + newZoom = Math.max(0.1, Math.min(32, newZoom)); - + if (clientX !== undefined && clientY !== undefined) { // Get current transform values const oldZoom = editState.zoom; const rect = container.getBoundingClientRect(); - + // Calculate zoom center point relative to container const containerCenterX = rect.left + rect.width / 2; const containerCenterY = rect.top + rect.height / 2; - + // Calculate offset to keep zoom point centered const offsetX = (clientX - containerCenterX) * (1 - newZoom / oldZoom); const offsetY = (clientY - containerCenterY) * (1 - newZoom / oldZoom); - + editState.panX += offsetX; editState.panY += offsetY; } - + editState.zoom = newZoom; constrainPan(); updateCanvasTransform(); @@ -6608,20 +6653,20 @@ function getText(key, params) { function constrainPan() { const container = document.getElementById('editCanvasContainer'); const canvas = document.getElementById('editCanvas'); - + if (!container || !canvas) return; - + const containerRect = container.getBoundingClientRect(); const scaledWidth = canvas.width * editState.zoom; const scaledHeight = canvas.height * editState.zoom; - - // Small padding around edges to avoid snapping against borders - const padding = 10; - + + // Small padding around edges to avoid snapping against borders + const padding = 10; + // Calculate limits to keep canvas somewhat visible const maxPanX = Math.max(0, (scaledWidth - containerRect.width) / 2 + padding); const maxPanY = Math.max(0, (scaledHeight - containerRect.height) / 2 + padding); - + editState.panX = Math.max(-maxPanX, Math.min(maxPanX, editState.panX)); editState.panY = Math.max(-maxPanY, Math.min(maxPanY, editState.panY)); } @@ -6645,16 +6690,16 @@ function getText(key, params) { function fitToWindow() { const container = document.getElementById('editCanvasContainer'); const canvas = document.getElementById('editCanvas'); - + if (!container || !canvas) return; - + const containerRect = container.getBoundingClientRect(); const padding = 40; - + const scaleX = (containerRect.width - padding) / canvas.width; const scaleY = (containerRect.height - padding) / canvas.height; const fitZoom = Math.max(0.1, Math.min(scaleX, scaleY)); - + // Center the canvas editState.zoom = fitZoom; editState.panX = 0; @@ -6675,7 +6720,7 @@ function getText(key, params) { editState.zoom = 1; editState.panX = 0; editState.panY = 0; - + updateCanvasTransform(); updateMinimap(); } @@ -6684,27 +6729,28 @@ function getText(key, params) { const minimapCanvas = document.getElementById('minimapCanvas'); const editCanvas = document.getElementById('editCanvas'); const minimapContainer = document.getElementById('minimapContainer'); - + if (!minimapCanvas || !editCanvas) return; - + // Show minimap only for larger images if (editCanvas.width < 100 || editCanvas.height < 100) { if (minimapContainer) minimapContainer.style.display = 'none'; return; } - + // Calculate minimap size const maxSize = 150; const scale = Math.min(maxSize / editCanvas.width, maxSize / editCanvas.height); - + minimapCanvas.width = editCanvas.width * scale; minimapCanvas.height = editCanvas.height * scale; - + // Draw thumbnail const minimapCtx = minimapCanvas.getContext('2d'); minimapCtx.imageSmoothingEnabled = false; + // REMOVED: drawCheckerboardBackground - prevents checkerboard from being saved with template minimapCtx.drawImage(editCanvas, 0, 0, minimapCanvas.width, minimapCanvas.height); - + // Update viewport indicator updateMinimap(); } @@ -6714,19 +6760,19 @@ function getText(key, params) { const minimapCanvas = document.getElementById('minimapCanvas'); const container = document.getElementById('editCanvasContainer'); const editCanvas = document.getElementById('editCanvas'); - + if (!viewport || !minimapCanvas || !container || !editCanvas) return; - + const containerRect = container.getBoundingClientRect(); const scale = minimapCanvas.width / editCanvas.width; - + // Calculate visible area in minimap coordinates const visibleWidth = Math.min(containerRect.width / editState.zoom * scale, minimapCanvas.width); const visibleHeight = Math.min(containerRect.height / editState.zoom * scale, minimapCanvas.height); - + const viewportX = (minimapCanvas.width / 2) - (editState.panX / editState.zoom * scale) - (visibleWidth / 2); const viewportY = (minimapCanvas.height / 2) - (editState.panY / editState.zoom * scale) - (visibleHeight / 2); - + viewport.style.width = `${visibleWidth}px`; viewport.style.height = `${visibleHeight}px`; viewport.style.left = `${Math.max(0, Math.min(minimapCanvas.width - visibleWidth, viewportX))}px`; @@ -6736,20 +6782,20 @@ function getText(key, params) { function navigateToMinimapPosition(e) { const minimapCanvas = document.getElementById('minimapCanvas'); const editCanvas = document.getElementById('editCanvas'); - + if (!minimapCanvas || !editCanvas) return; - + const rect = minimapCanvas.getBoundingClientRect(); const x = (e.clientX - rect.left) / minimapCanvas.width; const y = (e.clientY - rect.top) / minimapCanvas.height; - + // Convert to canvas coordinates and center const targetX = (x - 0.5) * editCanvas.width * editState.zoom; const targetY = (y - 0.5) * editCanvas.height * editState.zoom; - + editState.panX = -targetX; editState.panY = -targetY; - + constrainPan(); updateCanvasTransform(); updateMinimap(); @@ -6766,18 +6812,18 @@ function getText(key, params) { function handleEyedropper(x, y) { const editCanvas = document.getElementById('editCanvas'); const ctx = editCanvas.getContext('2d'); - + if (x >= 0 && x < editCanvas.width && y >= 0 && y < editCanvas.height) { const imageData = ctx.getImageData(x, y, 1, 1); const [r, g, b] = imageData.data; - const pickedColor = `#${r.toString(16).padStart(2,'0')}${g.toString(16).padStart(2,'0')}${b.toString(16).padStart(2,'0')}`; - + const pickedColor = `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`; + editState.currentColor = pickedColor; const currentColorDisplay = document.getElementById('currentColorDisplay'); if (currentColorDisplay) { currentColorDisplay.style.backgroundColor = pickedColor; } - + // Try to find matching color in palette document.querySelectorAll('.edit-color-btn').forEach(btn => { btn.classList.remove('active'); @@ -6786,7 +6832,7 @@ function getText(key, params) { editState.currentColorId = parseInt(btn.dataset.colorId); } }); - + updateStatusBar(x, y); } } @@ -6796,54 +6842,54 @@ function getText(key, params) { const ctx = editCanvas.getContext('2d'); const imageData = ctx.getImageData(0, 0, editCanvas.width, editCanvas.height); const data = imageData.data; - + if (startX < 0 || startX >= editCanvas.width || startY < 0 || startY >= editCanvas.height) return; - + const startIndex = (startY * editCanvas.width + startX) * 4; const startR = data[startIndex]; const startG = data[startIndex + 1]; const startB = data[startIndex + 2]; const startA = data[startIndex + 3]; - + // Convert fill color to RGB const fillR = parseInt(fillColor.slice(1, 3), 16); const fillG = parseInt(fillColor.slice(3, 5), 16); const fillB = parseInt(fillColor.slice(5, 7), 16); - + // Don't fill if the color is already the same if (startR === fillR && startG === fillG && startB === fillB) return; - - const pixelsToCheck = [{x: startX, y: startY}]; + + const pixelsToCheck = [{ x: startX, y: startY }]; const checkedPixels = new Set(); - + while (pixelsToCheck.length > 0) { - const {x, y} = pixelsToCheck.pop(); + const { x, y } = pixelsToCheck.pop(); const key = `${x},${y}`; - + if (checkedPixels.has(key)) continue; checkedPixels.add(key); - + if (x < 0 || x >= editCanvas.width || y < 0 || y >= editCanvas.height) continue; - + const index = (y * editCanvas.width + x) * 4; const r = data[index]; const g = data[index + 1]; const b = data[index + 2]; const a = data[index + 3]; - + if (r === startR && g === startG && b === startB && a === startA) { data[index] = fillR; data[index + 1] = fillG; data[index + 2] = fillB; data[index + 3] = 255; - - pixelsToCheck.push({x: x + 1, y}); - pixelsToCheck.push({x: x - 1, y}); - pixelsToCheck.push({x, y: y + 1}); - pixelsToCheck.push({x, y: y - 1}); + + pixelsToCheck.push({ x: x + 1, y }); + pixelsToCheck.push({ x: x - 1, y }); + pixelsToCheck.push({ x, y: y + 1 }); + pixelsToCheck.push({ x, y: y - 1 }); } } - + ctx.putImageData(imageData, 0, 0); } @@ -6854,38 +6900,54 @@ function getText(key, params) { function drawGrid(ctx, width, height) { if (!editState.showGrid || editState.zoom < 4) return; - + ctx.save(); ctx.strokeStyle = 'rgba(128, 128, 128, 0.3)'; ctx.lineWidth = 1 / editState.zoom; - + for (let x = 0; x <= width; x++) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, height); ctx.stroke(); } - + for (let y = 0; y <= height; y++) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(width, y); ctx.stroke(); } - + ctx.restore(); } + function drawCheckerboardBackground(ctx, width, height) { + const checkerSize = 8; // Size of each checker square + ctx.fillStyle = '#f0f0f0'; // Light gray + ctx.fillRect(0, 0, width, height); + + ctx.fillStyle = '#e0e0e0'; // Slightly darker gray + for (let x = 0; x < width; x += checkerSize) { + for (let y = 0; y < height; y += checkerSize) { + if ((Math.floor(x / checkerSize) + Math.floor(y / checkerSize)) % 2 === 1) { + ctx.fillRect(x, y, checkerSize, checkerSize); + } + } + } + } + function redrawCanvas() { if (editState.undoStack.length === 0) return; - + const currentState = editState.undoStack[editState.undoStack.length - 1]; const editCanvas = document.getElementById('editCanvas'); const ctx = editCanvas.getContext('2d'); - + const img = new Image(); img.onload = () => { ctx.clearRect(0, 0, editCanvas.width, editCanvas.height); + // REMOVED: drawCheckerboardBackground - prevents checkerboard from being saved with template ctx.drawImage(img, 0, 0); drawGrid(ctx, editCanvas.width, editCanvas.height); }; @@ -6895,17 +6957,17 @@ function getText(key, params) { function setupTouchSupport() { const canvas = document.getElementById('editCanvas'); if (!canvas) return; - + let touchStartTime = 0; - + canvas.addEventListener('touchstart', handleTouchStart, { passive: false }); canvas.addEventListener('touchmove', handleTouchMove, { passive: false }); canvas.addEventListener('touchend', handleTouchEnd, { passive: false }); - + function handleTouchStart(e) { e.preventDefault(); touchStartTime = Date.now(); - + if (e.touches.length === 1) { // Single touch - start painting const touch = e.touches[0]; @@ -6919,22 +6981,22 @@ function getText(key, params) { // Two finger - prepare for zoom/pan const touch1 = e.touches[0]; const touch2 = e.touches[1]; - + editState.lastTouchDistance = Math.hypot( touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY ); - + editState.lastTouchCenter = { x: (touch1.clientX + touch2.clientX) / 2, y: (touch1.clientY + touch2.clientY) / 2 }; } } - + function handleTouchMove(e) { e.preventDefault(); - + if (e.touches.length === 1 && editState.isDrawing) { // Continue painting const touch = e.touches[0]; @@ -6946,46 +7008,46 @@ function getText(key, params) { // Pinch zoom and pan const touch1 = e.touches[0]; const touch2 = e.touches[1]; - + const currentDistance = Math.hypot( touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY ); - + const currentCenter = { x: (touch1.clientX + touch2.clientX) / 2, y: (touch1.clientY + touch2.clientY) / 2 }; - + // Zoom based on distance change if (editState.lastTouchDistance > 0) { const zoomFactor = currentDistance / editState.lastTouchDistance; const newZoom = Math.max(0.1, Math.min(32, editState.zoom * zoomFactor)); zoomToPoint(newZoom, currentCenter.x, currentCenter.y); } - + // Pan based on center movement editState.panX += currentCenter.x - editState.lastTouchCenter.x; editState.panY += currentCenter.y - editState.lastTouchCenter.y; - + editState.lastTouchDistance = currentDistance; editState.lastTouchCenter = currentCenter; - + constrainPan(); updateCanvasTransform(); updateMinimap(); } } - + function handleTouchEnd(e) { e.preventDefault(); - + if (editState.isDrawing) { editState.isDrawing = false; editState.lastPaintPos = null; saveEditState(); } - + if (e.touches.length === 0) { editState.lastTouchDistance = 0; } @@ -6997,14 +7059,14 @@ function getText(key, params) { // Only handle shortcuts when edit panel is visible const editOverlay = document.getElementById('editOverlay'); if (!editOverlay || editOverlay.style.display === 'none') return; - + // Prevent default for handled keys const handledKeys = ['b', 'e', 'i', 'f', 'g', 'z', '[', ']']; if (handledKeys.includes(e.key.toLowerCase()) || (e.ctrlKey && e.key.toLowerCase() === 'z')) { e.preventDefault(); } - - switch(e.key.toLowerCase()) { + + switch (e.key.toLowerCase()) { case 'b': // Brush selectTool('paint'); break; @@ -7079,34 +7141,34 @@ function getText(key, params) { if (!editCanvas) { throw new Error('Edit canvas not found'); } - + // Find the resize canvas in the resize panel const resizeCanvas = document.getElementById('resizeCanvas'); if (!resizeCanvas) { throw new Error('Resize canvas not found'); } - + const baseCtx = resizeCanvas.getContext('2d'); if (!baseCtx) { throw new Error('Resize canvas context not available'); } - + // Make sure the resize canvas has the same dimensions as the edit canvas if (resizeCanvas.width !== editCanvas.width || resizeCanvas.height !== editCanvas.height) { resizeCanvas.width = editCanvas.width; resizeCanvas.height = editCanvas.height; } - + // Clear resize canvas baseCtx.clearRect(0, 0, resizeCanvas.width, resizeCanvas.height); - - // Copy edited image to resize canvas + + // Copy edited image to resize canvas (no checkerboard since we removed it entirely) baseCtx.imageSmoothingEnabled = false; baseCtx.drawImage(editCanvas, 0, 0); - + // CRITICAL: Completely replace the template system with edited artwork const editedImageData = editCanvas.toDataURL(); - + // Create new processor with edited image as the template if (window.WPlaceImageProcessor) { const newProcessor = new window.WPlaceImageProcessor(editedImageData); @@ -7115,7 +7177,7 @@ function getText(key, params) { const editCtx = editCanvas.getContext('2d'); const editImageData = editCtx.getImageData(0, 0, editCanvas.width, editCanvas.height); const pixels = editImageData.data; - + // Count valid pixels in the edited image let totalValidPixels = 0; for (let i = 0; i < pixels.length; i += 4) { @@ -7123,36 +7185,36 @@ function getText(key, params) { const r = pixels[i]; const g = pixels[i + 1]; const b = pixels[i + 2]; - + const isTransparent = !state.paintTransparentPixels && a < state.customTransparencyThreshold; const isWhiteAndSkipped = !state.paintWhitePixels && Utils.isWhitePixel(r, g, b); - + if (!isTransparent && !isWhiteAndSkipped) { totalValidPixels++; } } - + // COMPLETELY REBUILD state.imageData with the edited artwork state.imageData = { width: editCanvas.width, - height: editCanvas.height, + height: editCanvas.height, pixels: pixels, totalPixels: totalValidPixels, processor: newProcessor, }; - + // CRITICAL: Update state.originalImage so resize panel uses edited artwork as base template state.originalImage = { dataUrl: editedImageData, width: editCanvas.width, height: editCanvas.height }; - + // Update state with new totals state.totalPixels = totalValidPixels; state.paintedPixels = 0; // Reset progress since this is a new template state.imageLoaded = true; - + // Update local processors if (typeof processor !== 'undefined') { processor = newProcessor; @@ -7160,38 +7222,38 @@ function getText(key, params) { } if (typeof baseProcessor !== 'undefined') { baseProcessor = newProcessor; - console.log('🔄 Updated baseProcessor with edited artwork'); + console.log('🔄 Updated baseProcessor with edited artwork'); } - + // Force regeneration of overlays by clearing cached mask data if (typeof window._maskImageData !== 'undefined') { delete window._maskImageData; } - + // Update UI to reflect the new template if (typeof updateUI === 'function') { updateUI(); } - + // Show loading and properly reload resize panel with new template Utils.showAlert('Updating template... Please wait.', 'info'); - + // Give more time for template to fully update and force resize panel reload setTimeout(() => { // Hide edit overlay first document.getElementById('editOverlay').style.display = 'none'; - + // Completely reload resize dialog with updated processor setTimeout(() => { // Clean up existing dialog if (typeof _resizeDialogCleanup === 'function') { _resizeDialogCleanup(); } - + // Force complete reload of resize dialog with new processor setTimeout(() => { showResizeDialog(newProcessor); - + console.log('✅ Template COMPLETELY replaced with edited artwork'); console.log(`📊 New template stats: ${editCanvas.width}x${editCanvas.height}, ${totalValidPixels} pixels`); Utils.showAlert('Template successfully replaced with your edited artwork!', 'success'); @@ -7206,7 +7268,7 @@ function getText(key, params) { console.error('WPlaceImageProcessor not available'); Utils.showAlert('Image processor not available. Please reload the page.', 'error'); } - + console.log('✅ Edit changes applied - template replacement in progress'); } catch (error) { console.error('Error applying edit changes:', error); @@ -7373,6 +7435,11 @@ function getText(key, params) { fileData.state.availableColors = []; } + // Reset session-specific flags when loading extracted file data + state.preFilteringDone = false; + state.progressResetDone = false; + console.log('🔄 Reset session flags for extracted file load'); + // Use the existing restoreProgress function but with special handling const restoreSuccess = await Utils.restoreProgress(fileData); @@ -7702,6 +7769,10 @@ function getText(key, params) { // Perform progressive pixel detection from top-left to bottom-right console.log('🔍 Starting progressive pixel detection from top-left to bottom-right...'); await performProgressivePixelDetection(); + + // Mark pre-filtering as done to prevent duplicate scanning + state.preFilteringDone = true; + console.log('✅ Pre-filtering marked as complete - will not scan again this session'); } else { console.log('🔄 Continuing session - pre-filtering already done, but progress reset'); } @@ -8185,8 +8256,9 @@ function getText(key, params) { // --------- Traversal modes ---------- if (mode === 'rows') { + let rowIndex = 0; for (let y = yStart; y !== yEnd; y += yStep) { - if (snake && (y - yStart) % 2 !== 0) { + if (snake && rowIndex % 2 !== 0) { for (let x = xEnd - xStep; x !== xStart - xStep; x -= xStep) { coords.push([x, y]); } @@ -8195,10 +8267,12 @@ function getText(key, params) { coords.push([x, y]); } } + rowIndex++; } } else if (mode === 'columns') { + let colIndex = 0; for (let x = xStart; x !== xEnd; x += xStep) { - if (snake && (x - xStart) % 2 !== 0) { + if (snake && colIndex % 2 !== 0) { for (let y = yEnd - yStep; y !== yStart - yStep; y -= yStep) { coords.push([x, y]); } @@ -8207,6 +8281,7 @@ function getText(key, params) { coords.push([x, y]); } } + colIndex++; } } else if (mode === 'circle-out') { const cx = Math.floor(width / 2); @@ -8320,7 +8395,8 @@ function getText(key, params) { // Update last painted position to the last pixel in the successful batch if (pixelBatch.pixels.length > 0) { const lastPixel = pixelBatch.pixels[pixelBatch.pixels.length - 1]; - state.lastPaintedPosition = { x: lastPixel.x, y: lastPixel.y }; + // FIXED: Use localX/localY (image-relative coordinates) instead of x/y (absolute canvas coordinates) + state.lastPaintedPosition = { x: lastPixel.localX, y: lastPixel.localY }; } // IMPORTANT: Decrement charges locally to match Acc-Switch.js behavior @@ -8372,14 +8448,14 @@ function getText(key, params) { } if (paintingResult === 'charges_depleted') { - if (CONFIG.autoBuyToggle && CONFIG.autoBuy != buyTypes[0]) { - console.log('Trying to buy more charges before stopping'); + if (CONFIG.autoBuyToggle && CONFIG.autoBuy != 'none') { + console.log('Trying to buy more charges before swapping account...'); const purchaseResult = await purchase(CONFIG.autoBuy); if (purchaseResult == 2) { console.log('✅ Purchase successful, continuing painting'); await updateStats(); await updateCurrentAccountInList(); - continue; + if (CONFIG.autoBuy == 'paint_charges') continue; } else if (purchaseResult == 1) { console.log('😭 Not enough droplets to buy more charges, swapping account.'); @@ -8439,7 +8515,7 @@ function getText(key, params) { // Update current account status before switching console.log('📊 Updating current account status before switch...'); await updateCurrentAccountInList(); - + // Switch to next account immediately (no cooldown) - only if we have multiple accounts const nextAccount = accountManager.getNextAccount(); console.log(`🔄 Switching to next account: ${nextAccount?.displayName} (${accountManager.currentIndex + 2}/${totalAccounts})`); @@ -9030,6 +9106,11 @@ function getText(key, params) { return Math.max(1, targetBatchSize); } + // CRITICAL FIX: When speed control is ENABLED, use actual available charges as the batch size + // This ensures bot sends exactly the number of pixels matching available charges + // Example: 75 charges → send 75 pixels, 14 charges → send 14 pixels + const availableCharges = state.displayCharges; + if (state.batchMode === 'random') { // Generate random batch size within the specified range const min = Math.max(1, state.randomBatchMin); @@ -9041,11 +9122,13 @@ function getText(key, params) { targetBatchSize = state.paintingSpeed; } - // Always limit by available charges - const maxAllowed = state.displayCharges; - const finalBatchSize = Math.min(targetBatchSize, maxAllowed); + // FIXED: Use actual available charges as the maximum batch size + // This matches the available pixels exactly (e.g., 75 charges = 75 pixel batch) + const finalBatchSize = Math.min(targetBatchSize, availableCharges); + + console.log(`📊 Batch size: ${finalBatchSize} (target: ${targetBatchSize}, available: ${availableCharges})`); - return finalBatchSize; + return Math.max(1, finalBatchSize); } // Helper function to retry batch until success with exponential backoff @@ -9066,10 +9149,13 @@ function getText(key, params) { console.log(`🔑 Token error on attempt ${attempt} - no token available during processing`); console.log(`❌ Stopping batch processing - tokens must be generated at startup/start button only`); updateUI('captchaFailed', 'error'); - return false; // Stop processing entirely - don't regenerate during processing + await Utils.sleep(2000); // Wait longer before retrying after token failure + continue; // Continue to retry until maxRetries reached } else if (result === 'token_regenerated') { console.log(`🔄 Token regenerated on attempt ${attempt} after 403 error - retrying batch`); - updateUI('paintingPaused', 'warning', { message: 'Token refreshed, resuming...' }); + const pausedX = state.lastPaintedPosition.x; + const pausedY = state.lastPaintedPosition.y; + updateUI('paintingPaused', 'warning', { x: pausedX, y: pausedY }); // Don't count token regeneration as a failed attempt, retry immediately attempt--; await Utils.sleep(500); // Brief pause before retry @@ -9691,7 +9777,7 @@ function getText(key, params) { "amount": amounts } }; - const res = fetch("https://backend.wplace.live/purchase", { + const res = await fetch("https://backend.wplace.live/purchase", { method: "POST", headers: { "Content-Type": "text/plain;charset=UTF-8" @@ -9748,12 +9834,6 @@ function getText(key, params) { return new Promise((resolve, reject) => { console.log("Requesting accounts from extension..."); - // Set timeout for extension response - const timeout = setTimeout(() => { - window.removeEventListener("message", handler); - reject(new Error("Extension response timeout")); - }, 15000); // 15 second timeout (increased from 5 seconds) - // Ask extension for accounts window.postMessage({ source: "my-userscript", @@ -9764,10 +9844,9 @@ function getText(key, params) { if (event.source !== window) return; if (event.data.source !== "extension") return; if (event.data.type === "accountsData") { - // Clear timeout and remove listener when we get the response - clearTimeout(timeout); + // Remove listener when we get the response window.removeEventListener("message", handler); - + try { localStorage.setItem("accounts", JSON.stringify(event.data.accounts)); console.log("✅ Accounts saved to localStorage:", event.data.accounts); @@ -9852,7 +9931,7 @@ function getText(key, params) { // Switch to this account temporarily to fetch its data console.log(`🔄 [FETCH] Switching to ${account.displayName} to fetch fresh data...`); await switchToSpecificAccount(account.token, account.displayName); - await Utils.sleep(500); // Small delay to ensure switch takes effect + // await Utils.sleep(500); // Small delay to ensure switch takes effect // Fetch fresh account details const accountData = await WPlaceService.getCharges(); @@ -9879,7 +9958,7 @@ function getText(key, params) { console.log(`🔙 [FETCH] Switching back to original current account: ${originalCurrentAccount.displayName}`); try { await switchToSpecificAccount(originalCurrentAccount.token, originalCurrentAccount.displayName); - await Utils.sleep(300); + //await Utils.sleep(300); // Mark it as current again accountManager.updateAccountData(originalCurrentAccount.token, { @@ -9937,7 +10016,7 @@ function getText(key, params) { // Function to update current account spotlight when switching during painting async function updateCurrentAccountSpotlight() { if (accountManager.getAccountCount() === 0) return; - await Utils.sleep(500); // Wait a bit for the switch to take effect + // await Utils.sleep(500); // Wait a bit for the switch to take effect try { const currentAccountData = await WPlaceService.getCharges(); console.log("Current account after switch:", currentAccountData); @@ -10153,10 +10232,10 @@ function getText(key, params) { console.log(`✅ [SWITCH] Successfully switched to ${nextAccount.displayName}`); // Wait a moment for the switch to fully complete - await new Promise(resolve => setTimeout(resolve, 1000)); - + // await new Promise(resolve => setTimeout(resolve, 1000)); + // Update the account status and UI after successful switch - await updateCurrentAccountSpotlight(); + await updateCurrentAccountInList(); return true; } catch (error) { @@ -10181,7 +10260,7 @@ function getText(key, params) { //await new Promise(resolve => setTimeout(resolve, 1000)); try { - await fetchAccount(); + // await fetchAccount(); console.log('✅ [SPECIFIC SWITCH] Account swap confirmed.'); swapSuccess = true; } catch (error) { diff --git a/Extension/themes/classic.css b/Extension/themes/classic.css index c49135b..428d8f6 100644 --- a/Extension/themes/classic.css +++ b/Extension/themes/classic.css @@ -22,13 +22,13 @@ --wplace-icon-primary: #4facfe; --wplace-icon-secondary: #00f2fe; --wplace-icon-palette: #f093fb; - + /* Additional UI colors */ --wplace-danger: #ff6a6a; --wplace-danger-dark: #ff4757; --wplace-muted-text: #fffB; --wplace-highlight-secondary: #203C5D; - + /* Slider colors */ --wplace-slider-track-bg: linear-gradient(to right, #4facfe 0%, #00f2fe 100%); } @@ -231,7 +231,7 @@ :root .wplace-btn[aria-pressed="true"] i, .wplace-theme-classic .wplace-btn.active i, .wplace-theme-classic .wplace-btn[aria-pressed="true"] i { - filter: drop-shadow(0 0 3px #000); + filter: drop-shadow(0 0 3px #000); } :root .mask-mode-group .wplace-btn.active, @@ -355,6 +355,7 @@ /* Auto light/dark support for classic theme */ @media (prefers-color-scheme: light) { + :root .theme-auto, .wplace-theme-classic .theme-auto { --wplace-primary: #fff; @@ -365,6 +366,7 @@ } @media (prefers-color-scheme: dark) { + :root .theme-auto, .wplace-theme-classic .theme-auto { --wplace-primary: #1e1e1e; @@ -431,27 +433,27 @@ /* Icon colors for classic theme */ :root .wplace-icon-key, .wplace-theme-classic .wplace-icon-key { - color: #4facfe; + color: #4facfe; } :root .wplace-icon-robot, .wplace-theme-classic .wplace-icon-robot { - color: #4facfe; + color: #4facfe; } :root .wplace-icon-speed, .wplace-theme-classic .wplace-icon-speed { - color: #4facfe; + color: #4facfe; } :root .wplace-icon-bell, .wplace-theme-classic .wplace-icon-bell { - color: #ffd166; + color: #ffd166; } :root .wplace-icon-palette, .wplace-theme-classic .wplace-icon-palette { - color: #f093fb; + color: #f093fb; } :root .wplace-icon-globe, @@ -666,26 +668,26 @@ align-items: center; justify-content: center; transition: all 0.2s ease; - box-shadow: 0 2px 4px rgba(0,0,0,0.2); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); } :root .wplace-input-btn-compact:hover, .wplace-theme-classic .wplace-input-btn-compact:hover { transform: translateY(-1px); - box-shadow: 0 4px 8px rgba(0,0,0,0.3); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); background: linear-gradient(135deg, #00f2fe 0%, #4facfe 100%); } :root .wplace-input-btn-compact:active, .wplace-theme-classic .wplace-input-btn-compact:active { transform: translateY(0); - box-shadow: 0 2px 4px rgba(0,0,0,0.2); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); } :root .wplace-number-input-compact, .wplace-theme-classic .wplace-number-input-compact { - background: rgba(255,255,255,0.1); - border: 1px solid rgba(255,255,255,0.2); + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 4px; color: white; padding: 4px 8px; @@ -701,12 +703,12 @@ outline: none; border-color: #4facfe; box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.3); - background: rgba(255,255,255,0.15); + background: rgba(255, 255, 255, 0.15); } :root .wplace-input-label-compact, .wplace-theme-classic .wplace-input-label-compact { - color: rgba(255,255,255,0.8); + color: rgba(255, 255, 255, 0.8); font-size: 11px; margin-left: 4px; white-space: nowrap; @@ -811,13 +813,13 @@ border-radius: 50%; } -:root .wplace-switch input:checked + .wplace-slider-round, -.wplace-theme-classic .wplace-switch input:checked + .wplace-slider-round { +:root .wplace-switch input:checked+.wplace-slider-round, +.wplace-theme-classic .wplace-switch input:checked+.wplace-slider-round { background: var(--wplace-slider-track-bg); } -:root .wplace-switch input:checked + .wplace-slider-round:before, -.wplace-theme-classic .wplace-switch input:checked + .wplace-slider-round:before { +:root .wplace-switch input:checked+.wplace-slider-round:before, +.wplace-theme-classic .wplace-switch input:checked+.wplace-slider-round:before { transform: translateX(26px); } @@ -1388,6 +1390,14 @@ box-shadow: 0 0 8px rgba(0, 0, 0, 0.25) inset, 0 0 6px rgba(79, 172, 254, 0.5); } +/* Specific tool styling to ensure proper click handling */ +:root #fillTool, +.wplace-theme-classic #fillTool { + position: relative; + z-index: 10; + pointer-events: auto; +} + :root .edit-toggle, .wplace-theme-classic .edit-toggle { background: linear-gradient(135deg, #333 0%, #555 100%); @@ -1622,10 +1632,10 @@ justify-content: center; width: 100%; height: 100%; - background: - linear-gradient(45deg, #f0f0f0 25%, transparent 25%), - linear-gradient(-45deg, #f0f0f0 25%, transparent 25%), - linear-gradient(45deg, transparent 75%, #f0f0f0 75%), + background: + linear-gradient(45deg, #f0f0f0 25%, transparent 25%), + linear-gradient(-45deg, #f0f0f0 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, #f0f0f0 75%), linear-gradient(-45deg, transparent 75%, #f0f0f0 75%); background-size: 20px 20px; background-position: 0 0, 0 10px, 10px -10px, -10px 0px; @@ -1637,7 +1647,7 @@ position: relative; transform-origin: center center; display: inline-block; - box-shadow: 0 4px 20px rgba(0,0,0,0.3); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); border: 2px solid #333; background: white; transition: transform 0.1s ease-out; @@ -1648,12 +1658,12 @@ .wplace-theme-classic .brush-preview { position: absolute; pointer-events: none; - border: 2px solid rgba(255,255,255,0.8); - background: rgba(0,0,0,0.1); + border: 2px solid rgba(255, 255, 255, 0.8); + background: rgba(0, 0, 0, 0.1); z-index: 1000; transform-origin: top left; display: none; - box-shadow: 0 0 4px rgba(0,0,0,0.5); + box-shadow: 0 0 4px rgba(0, 0, 0, 0.5); } :root .minimap-container, @@ -1667,7 +1677,7 @@ padding: 8px; z-index: 100; backdrop-filter: blur(4px); - box-shadow: 0 4px 12px rgba(0,0,0,0.3); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); } :root .minimap-header, @@ -1862,6 +1872,7 @@ /* Mobile Responsive Adjustments */ @media (max-width: 1024px) { + :root .edit-container, .wplace-theme-classic .edit-container { width: 100vw; @@ -1872,31 +1883,31 @@ left: 0; border-radius: 0; } - + :root .edit-nav-controls, .wplace-theme-classic .edit-nav-controls { flex-direction: column; gap: 8px; } - + :root .edit-main-area, .wplace-theme-classic .edit-main-area { flex-direction: row; } - + :root .edit-toolbar, .wplace-theme-classic .edit-toolbar { width: 60px; min-width: 60px; } - + :root .minimap-container, .wplace-theme-classic .minimap-container { top: 80px; right: 8px; transform: scale(0.8); } - + :root .zoom-controls, .wplace-theme-classic .zoom-controls { flex-wrap: wrap; @@ -1905,6 +1916,7 @@ } @media (max-width: 768px) { + :root .edit-header, .wplace-theme-classic .edit-header { padding: 8px 12px; @@ -1912,28 +1924,28 @@ gap: 8px; min-height: 60px; } - + :root .edit-main-area, .wplace-theme-classic .edit-main-area { flex-direction: row; } - + :root .edit-toolbar, .wplace-theme-classic .edit-toolbar { width: 50px; min-width: 50px; } - + :root .zoom-controls, .wplace-theme-classic .zoom-controls { scale: 0.9; } - + :root .minimap-container, .wplace-theme-classic .minimap-container { display: none; } - + :root .edit-color-grid .color-btn, .wplace-theme-classic .edit-color-grid .color-btn { width: 16px; @@ -1943,19 +1955,20 @@ /* Touch Device Optimizations */ @media (hover: none) and (pointer: coarse) { + :root .zoom-btn, .wplace-theme-classic .zoom-btn { min-width: 44px; min-height: 44px; } - + :root .edit-tool, .wplace-theme-classic .edit-tool { min-width: 44px; min-height: 44px; padding: 12px; } - + :root .edit-color-btn, .wplace-theme-classic .edit-color-btn { width: 32px; @@ -1965,13 +1978,14 @@ /* High DPI Display Support */ @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { + :root .edit-canvas, .wplace-theme-classic .edit-canvas { image-rendering: pixelated; image-rendering: -moz-crisp-edges; image-rendering: crisp-edges; } - + :root .minimap-canvas, .wplace-theme-classic .minimap-canvas { image-rendering: pixelated; @@ -1999,5 +2013,4 @@ :root .zoom-btn:active, .wplace-theme-classic .zoom-btn:active { transform: translateY(1px) scale(0.95); -} - +} \ No newline at end of file