mirror of
https://github.com/tiennm99/WPlace-AutoBOT.git
synced 2026-10-14 00:16:30 +00:00
Merge remote-tracking branch 'origin/main'
# Conflicts: # Extension/scripts/Auto-Image.js
This commit is contained in:
commit
eaf34977cc
23 files changed
+652
-83
No files matched your search
+463
-15
@@ -57,8 +57,8 @@ localStorage.removeItem("lp");
|
||||
},
|
||||
OVERLAY: {
|
||||
OPACITY_DEFAULT: 0.6,
|
||||
BLUE_MARBLE_DEFAULT: false,
|
||||
ditheringEnabled: true,
|
||||
BLUE_MARBLE_DEFAULT: true, // Enable blue marble effect by default
|
||||
ditheringEnabled: false, // Default dithering OFF
|
||||
}, // --- START: Color data from colour-converter.js ---
|
||||
// New color structure with proper ID mapping
|
||||
COLOR_MAP: {
|
||||
@@ -692,7 +692,9 @@ localStorage.removeItem("lp");
|
||||
toggleOverlay: 'Toggle Overlay',
|
||||
scanColors: 'Scan Colors',
|
||||
uploadImage: 'Upload Image',
|
||||
resizeImage: 'Resize Image',
|
||||
resizeImage: 'Process/Edit Image',
|
||||
processImageFirst: '⚠️ Process the image first via "Process/Edit Image" before placing!',
|
||||
imageNeedsProcessing: 'Image needs processing',
|
||||
selectPosition: 'Select Position',
|
||||
startPainting: 'Start Painting',
|
||||
stopPainting: 'Stop Painting',
|
||||
@@ -1014,6 +1016,7 @@ localStorage.removeItem("lp");
|
||||
const state = {
|
||||
running: false,
|
||||
imageLoaded: false,
|
||||
imageProcessed: false, // Track if image has been processed through resize panel
|
||||
processing: false,
|
||||
totalPixels: 0,
|
||||
paintedPixels: 0,
|
||||
@@ -1087,6 +1090,7 @@ localStorage.removeItem("lp");
|
||||
accountIndex: 0, // Keep for backward compatibility with existing logic
|
||||
// Legacy state removed - now using accountManager instead
|
||||
isFetchingAllAccounts: false,
|
||||
paintingMode: 'auto', // 'auto' or 'assist' - default to auto
|
||||
};
|
||||
|
||||
// Expose state globally for the utils manager and other modules
|
||||
@@ -1173,12 +1177,198 @@ localStorage.removeItem("lp");
|
||||
}
|
||||
});
|
||||
|
||||
// Helper function to find closest color ID for assist mode
|
||||
function findClosestColorId(r, g, b) {
|
||||
let minDistance = Infinity;
|
||||
let closestColorId = null;
|
||||
|
||||
for (const colorData of Object.values(CONFIG.COLOR_MAP)) {
|
||||
if (!colorData.rgb) continue;
|
||||
|
||||
const distance = Math.sqrt(
|
||||
Math.pow(r - colorData.rgb.r, 2) +
|
||||
Math.pow(g - colorData.rgb.g, 2) +
|
||||
Math.pow(b - colorData.rgb.b, 2)
|
||||
);
|
||||
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
closestColorId = colorData.id;
|
||||
}
|
||||
}
|
||||
|
||||
return closestColorId;
|
||||
}
|
||||
|
||||
const originalFetch = window.fetch;
|
||||
// Setup fetch interceptions
|
||||
console.log('🚀 [Auto-Image] Fetch interception initialized');
|
||||
|
||||
// Expose state for debugging
|
||||
window.__WPLACE_AUTOBOT_STATE__ = state;
|
||||
|
||||
window.fetch = async function (...args) {
|
||||
let url = args[0] instanceof Request ? args[0].url : args[0];
|
||||
|
||||
// Parse URL if it's a string
|
||||
let urlObj = null;
|
||||
if (typeof url === 'string') {
|
||||
try {
|
||||
urlObj = new URL(url);
|
||||
} catch (e) {
|
||||
// Invalid URL, proceed normally
|
||||
}
|
||||
}
|
||||
|
||||
// ASSIST MODE: Intercept pixel placement requests
|
||||
if (state.paintingMode === 'assist' && urlObj !== null && urlObj.pathname.startsWith('/s0/pixel')) {
|
||||
console.log(`🎯 [Assist Mode] Intercepting pixel placement:`, urlObj.pathname);
|
||||
console.log(`🔍 [Assist Mode] Check state - imageData:`, !!state.imageData, 'startPosition:', !!state.startPosition, 'body:', !!args[1]?.body);
|
||||
|
||||
// Check if we have overlay data
|
||||
if (state.imageData && state.startPosition && args[1]?.body) {
|
||||
try {
|
||||
// Parse region coordinates from URL path: /s0/pixel/{tileX}/{tileY}
|
||||
const pathParts = urlObj.pathname.split('/');
|
||||
const regionX = parseInt(pathParts[3]);
|
||||
const regionY = parseInt(pathParts[4]);
|
||||
|
||||
console.log(`📦 [Assist Mode] Region coords: (${regionX}, ${regionY})`);
|
||||
|
||||
// Parse request body
|
||||
const payload = typeof args[1].body === 'string' ? JSON.parse(args[1].body) : args[1].body;
|
||||
console.log(`📝 [Assist Mode] Original payload:`, JSON.stringify(payload));
|
||||
|
||||
// Debug: Check what's in state.region
|
||||
console.log(`🔍 [Assist Mode] state.region:`, state.region);
|
||||
console.log(`🔍 [Assist Mode] state.startPosition:`, state.startPosition);
|
||||
|
||||
// CRITICAL FIX: The issue is that startPosition is in TILE-LOCAL coordinates (0-999)
|
||||
// but we're clicking on a tile at (1732, 1014). We need to find which tile
|
||||
// our image overlay is on by checking all loaded tiles.
|
||||
|
||||
// For now, let's use the REQUEST's tile coordinates as a reference
|
||||
// If the user is clicking within this tile, check if our LOCAL startPosition
|
||||
// would overlap with the click position
|
||||
let startX, startY;
|
||||
|
||||
// COORDINATE RECONSTRUCTION
|
||||
// state.startPosition = LOCAL coords within a 1000x1000 tile (0-999)
|
||||
// state.region = TILE coordinates {x, y} (e.g., {x: 1732, y: 1014})
|
||||
// Absolute canvas coords = region.x * 1000 + startPosition.x
|
||||
|
||||
if (state.region && state.region.x !== undefined && state.region.y !== undefined) {
|
||||
// Use saved region to reconstruct absolute coordinates
|
||||
startX = state.region.x * 1000 + state.startPosition.x;
|
||||
startY = state.region.y * 1000 + state.startPosition.y;
|
||||
console.log(`[Assist Mode] Using saved region: tile=(${state.region.x},${state.region.y}) + local=(${state.startPosition.x},${state.startPosition.y}) = absolute (${startX},${startY})`);
|
||||
} else {
|
||||
// Fallback: assume image is on the clicked tile (may be wrong!)
|
||||
// Since startPosition appears to be LOCAL coords within a tile,
|
||||
// we need to convert them to absolute by finding which tile they're on.
|
||||
// The simplest approach: assume the image is on the tile being clicked!
|
||||
startX = regionX * 1000 + state.startPosition.x;
|
||||
startY = regionY * 1000 + state.startPosition.y;
|
||||
console.log(`[Assist Mode] No saved region - assuming image on clicked tile (${regionX},${regionY}): absolute = (${startX},${startY})`);
|
||||
}
|
||||
console.log(`� [Assist Mode] Assuming image is on clicked tile: absolute coords = (${startX},${startY})`);
|
||||
|
||||
const { width, height } = state.imageData;
|
||||
|
||||
console.log(`🖼️ [Assist Mode] Image bounds: start=(${startX},${startY}), size=${width}x${height}`);
|
||||
|
||||
// Calculate which tiles the image spans
|
||||
// Note: startX/startY are ABSOLUTE canvas coordinates (e.g., 1732432 means tile 1732, pixel 432)
|
||||
const imageStartTileX = Math.floor(startX / 1000);
|
||||
const imageStartTileY = Math.floor(startY / 1000);
|
||||
const imageEndTileX = Math.floor((startX + width - 1) / 1000);
|
||||
const imageEndTileY = Math.floor((startY + height - 1) / 1000);
|
||||
|
||||
console.log(`🗺️ [Assist Mode] Image tiles: X[${imageStartTileX}-${imageEndTileX}], Y[${imageStartTileY}-${imageEndTileY}]`);
|
||||
console.log(`🎯 [Assist Mode] Checking if region (${regionX},${regionY}) overlaps...`);
|
||||
|
||||
if (regionX >= imageStartTileX && regionX <= imageEndTileX &&
|
||||
regionY >= imageStartTileY && regionY <= imageEndTileY) {
|
||||
|
||||
console.log(`✅ [Assist Mode] Region (${regionX},${regionY}) overlaps with image - modifying pixels`);
|
||||
|
||||
// Modify the colors array based on overlay data
|
||||
if (payload.coords && payload.colors) {
|
||||
let modifiedCount = 0;
|
||||
|
||||
for (let i = 0; i < payload.coords.length; i += 2) {
|
||||
// Calculate absolute pixel position
|
||||
const absoluteX = regionX * 1000 + payload.coords[i];
|
||||
const absoluteY = regionY * 1000 + payload.coords[i + 1];
|
||||
|
||||
// Calculate position relative to our image
|
||||
const imgX = absoluteX - startX;
|
||||
const imgY = absoluteY - startY;
|
||||
|
||||
console.log(`🎨 [Assist Mode] Pixel ${i/2}: absolute=(${absoluteX},${absoluteY}), relative=(${imgX},${imgY})`);
|
||||
|
||||
// Check if pixel is within our image bounds
|
||||
if (imgX >= 0 && imgX < width && imgY >= 0 && imgY < height) {
|
||||
// Get color from processed image data
|
||||
// Note: state.imageData uses 'pixels' property, not 'data'
|
||||
const pixelData = state.imageData.pixels || state.imageData.data;
|
||||
if (!pixelData) {
|
||||
console.error(`❌ [Assist Mode] No pixel data available in state.imageData`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const pixelIndex = (imgY * width + imgX) * 4;
|
||||
const r = pixelData[pixelIndex];
|
||||
const g = pixelData[pixelIndex + 1];
|
||||
const b = pixelData[pixelIndex + 2];
|
||||
const a = pixelData[pixelIndex + 3];
|
||||
|
||||
console.log(`🎨 [Assist Mode] Color at (${imgX},${imgY}): rgba(${r},${g},${b},${a}), original colorId: ${payload.colors[i/2]}`);
|
||||
|
||||
// Skip transparent pixels
|
||||
if (a < CONFIG.TRANSPARENCY_THRESHOLD) {
|
||||
console.log(`⏭️ [Assist Mode] Skipping transparent pixel`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find matching color from palette
|
||||
const colorId = findClosestColorId(r, g, b);
|
||||
console.log(`🔍 [Assist Mode] Found closest colorId: ${colorId}`);
|
||||
if (colorId) {
|
||||
const oldColorId = payload.colors[i / 2];
|
||||
payload.colors[i / 2] = colorId;
|
||||
console.log(`✏️ [Assist Mode] Changed colorId from ${oldColorId} to ${colorId}`);
|
||||
modifiedCount++;
|
||||
}
|
||||
} else {
|
||||
console.log(`❌ [Assist Mode] Pixel out of bounds`);
|
||||
}
|
||||
}
|
||||
|
||||
// Update request body with modified payload
|
||||
args[1] = {
|
||||
...args[1],
|
||||
body: JSON.stringify(payload)
|
||||
};
|
||||
|
||||
console.log(`✅ [Assist Mode] Modified ${modifiedCount} pixel color(s)`);
|
||||
console.log(`📝 [Assist Mode] Modified payload:`, JSON.stringify(payload));
|
||||
} else {
|
||||
console.warn(`⚠️ [Assist Mode] Payload missing coords or colors`);
|
||||
}
|
||||
} else {
|
||||
console.log(`⏭️ [Assist Mode] Region (${regionX},${regionY}) does NOT overlap with image`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ [Assist Mode] Error intercepting pixel placement:', error);
|
||||
console.error(error.stack);
|
||||
}
|
||||
} else {
|
||||
console.warn('⚠️ [Assist Mode] No overlay data available - imageData:', !!state.imageData, 'startPosition:', !!state.startPosition);
|
||||
}
|
||||
}
|
||||
|
||||
const response = await originalFetch.apply(this, args);
|
||||
const url = args[0] instanceof Request ? args[0].url : args[0];
|
||||
|
||||
// TILE REQUEST logging removed to reduce console spam
|
||||
|
||||
@@ -1304,6 +1494,37 @@ localStorage.removeItem("lp");
|
||||
}
|
||||
return getText(key, params);
|
||||
},
|
||||
|
||||
// Open website color palette if not already open
|
||||
openColorPalette: () => {
|
||||
try {
|
||||
// Check if color palette modal is already open
|
||||
const paletteModal = document.querySelector('dialog[open]');
|
||||
if (paletteModal) {
|
||||
console.log('✅ Color palette already open');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Search for Paint/Pintar button (like Auto-Farm does)
|
||||
const allButtons = Array.from(document.querySelectorAll('button'));
|
||||
const paletteButton = allButtons.find(btn =>
|
||||
/(Pintar|Paint)/i.test(btn.textContent.trim())
|
||||
);
|
||||
|
||||
if (paletteButton) {
|
||||
console.log('🎨 Opening color palette automatically...');
|
||||
paletteButton.click();
|
||||
return true;
|
||||
}
|
||||
|
||||
console.warn('⚠️ Color palette button not found');
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('❌ Error opening color palette:', error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
showAlert: (message, type) => {
|
||||
if (window.globalUtilsManager) {
|
||||
window.globalUtilsManager.showAlert(message, type);
|
||||
@@ -2476,12 +2697,19 @@ localStorage.removeItem("lp");
|
||||
<span>${Utils.t('stopPainting')}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="wplace-row single">
|
||||
<button id="toggleOverlayBtn" class="wplace-btn wplace-btn-overlay" disabled>
|
||||
<i class="fas fa-eye"></i>
|
||||
<span>${Utils.t('toggleOverlay')}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="wplace-row" style="display: flex; gap: 8px; align-items: center;">
|
||||
<button id="toggleOverlayBtn" class="wplace-btn wplace-btn-overlay" disabled style="flex: 1;">
|
||||
<i class="fas fa-eye"></i>
|
||||
<span>${Utils.t('toggleOverlay')}</span>
|
||||
</button>
|
||||
<label class="wplace-mode-toggle">
|
||||
<input type="checkbox" id="paintingModeToggle" aria-label="Toggle painting mode">
|
||||
<div class="wplace-mode-toggle-inner">
|
||||
<span class="wplace-mode-option wplace-mode-option-auto">Auto</span>
|
||||
<span class="wplace-mode-option wplace-mode-option-assist">Assist</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3186,6 +3414,80 @@ localStorage.removeItem("lp");
|
||||
.wplace-content::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
/* Painting Mode segmented control */
|
||||
.wplace-mode-toggle {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.wplace-mode-toggle input {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.wplace-mode-toggle-inner {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-width: 140px;
|
||||
border-radius: 999px;
|
||||
background: var(--surface-variant, #2a2a2a);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
padding: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wplace-mode-toggle-inner::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: calc(50% - 2px);
|
||||
height: calc(100% - 4px);
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12);
|
||||
border-radius: inherit;
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
|
||||
.wplace-mode-toggle input:checked + .wplace-mode-toggle-inner::before {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
.wplace-mode-option {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
padding: 8px 0;
|
||||
color: var(--text-secondary, #909090);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
transition: color 0.25s ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.wplace-mode-toggle input:not(:checked) + .wplace-mode-toggle-inner .wplace-mode-option-auto,
|
||||
.wplace-mode-toggle input:checked + .wplace-mode-toggle-inner .wplace-mode-option-assist {
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.wplace-mode-toggle:focus-within .wplace-mode-toggle-inner {
|
||||
outline: 2px solid rgba(79, 172, 254, 0.4);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
|
||||
@@ -4173,6 +4475,9 @@ localStorage.removeItem("lp");
|
||||
|
||||
if (loadBtn) {
|
||||
loadBtn.addEventListener('click', async () => {
|
||||
// Auto-open color palette if not already open
|
||||
Utils.openColorPalette();
|
||||
|
||||
const savedData = Utils.loadProgress();
|
||||
if (!savedData) {
|
||||
updateUI('noSavedData', 'warning');
|
||||
@@ -4998,9 +5303,12 @@ localStorage.removeItem("lp");
|
||||
};
|
||||
|
||||
// Skip expensive dithering while user is dragging sliders
|
||||
console.log(`🎨 Preview render - ditheringEnabled: ${state.ditheringEnabled}, _isDraggingSize: ${_isDraggingSize}`);
|
||||
if (state.ditheringEnabled && !_isDraggingSize) {
|
||||
console.log('✅ Applying dithering to preview');
|
||||
applyFSDither();
|
||||
} else {
|
||||
console.log('⛔ Skipping dithering - applying direct color conversion');
|
||||
for (let i = 0; i < data.length; i += 4) {
|
||||
const r = data[i],
|
||||
g = data[i + 1],
|
||||
@@ -5799,6 +6107,17 @@ localStorage.removeItem("lp");
|
||||
width: newWidth,
|
||||
height: newHeight,
|
||||
});
|
||||
|
||||
// ✅ Mark image as processed and enable position selection
|
||||
state.imageProcessed = true;
|
||||
selectPosBtn.disabled = false;
|
||||
selectPosBtn.title = Utils.t('selectPosition') || 'Select starting position on canvas';
|
||||
|
||||
// Enable start button if position is already set
|
||||
if (state.startPosition) {
|
||||
startBtn.disabled = false;
|
||||
}
|
||||
|
||||
closeResizeDialog();
|
||||
};
|
||||
|
||||
@@ -7450,6 +7769,9 @@ localStorage.removeItem("lp");
|
||||
|
||||
if (uploadBtn) {
|
||||
uploadBtn.addEventListener('click', async () => {
|
||||
// Auto-open color palette if not already open
|
||||
Utils.openColorPalette();
|
||||
|
||||
const availableColors = Utils.extractAvailableColors();
|
||||
if (availableColors === null || availableColors.length < 10) {
|
||||
updateUI('noColorsFound', 'error');
|
||||
@@ -7510,6 +7832,7 @@ localStorage.removeItem("lp");
|
||||
state.paintedPixels = 0;
|
||||
state.currentPaintingColor = null; // Reset color tracking
|
||||
state.imageLoaded = true;
|
||||
state.imageProcessed = false; // New upload - not processed yet
|
||||
|
||||
// Reset session-specific flags when a new image is loaded
|
||||
state.preFilteringDone = false;
|
||||
@@ -7530,7 +7853,7 @@ localStorage.removeItem("lp");
|
||||
state.originalImage = { dataUrl: imageSrc, width, height };
|
||||
saveBotSettings();
|
||||
|
||||
// Use the original image for the overlay initially
|
||||
// ⚠️ Use the ORIGINAL image for the overlay initially (will be replaced after processing)
|
||||
const imageBitmap = await createImageBitmap(processor.img);
|
||||
await overlayManager.setImage(imageBitmap);
|
||||
overlayManager.enable();
|
||||
@@ -7544,12 +7867,20 @@ localStorage.removeItem("lp");
|
||||
}
|
||||
saveBtn.disabled = false;
|
||||
|
||||
// ⚠️ IMPORTANT: Disable position selection until image is processed
|
||||
selectPosBtn.disabled = true;
|
||||
selectPosBtn.title = Utils.t('imageNeedsProcessing');
|
||||
|
||||
// Disable start button since position can't be set yet
|
||||
if (state.startPosition) {
|
||||
startBtn.disabled = false;
|
||||
startBtn.disabled = true; // Disable even if position was set before
|
||||
}
|
||||
|
||||
await updateStats();
|
||||
updateDataButtons();
|
||||
|
||||
// Show warning alert to process image first
|
||||
Utils.showAlert(Utils.t('processImageFirst'), 'warning');
|
||||
updateUI('imageLoaded', 'success', { count: totalValidPixels });
|
||||
} catch {
|
||||
updateUI('imageError', 'error');
|
||||
@@ -7562,6 +7893,9 @@ localStorage.removeItem("lp");
|
||||
if (loadExtractedBtn) {
|
||||
loadExtractedBtn.addEventListener('click', async () => {
|
||||
try {
|
||||
// Auto-open color palette if not already open
|
||||
Utils.openColorPalette();
|
||||
|
||||
updateUI('loadingImage', 'default');
|
||||
const fileData = await Utils.loadExtractedFileData();
|
||||
|
||||
@@ -7673,6 +8007,15 @@ localStorage.removeItem("lp");
|
||||
// For extracted images, ensure overlay is enabled but don't set position yet
|
||||
try {
|
||||
if (overlayManager && state.imageData) {
|
||||
console.log('🖼️ Creating overlay from loaded pixel data:', {
|
||||
width: state.imageData.width,
|
||||
height: state.imageData.height,
|
||||
pixelCount: state.imageData.pixels?.length,
|
||||
firstPixels: state.imageData.pixels?.slice(0, 12), // First 3 pixels (RGBA)
|
||||
isUint8ClampedArray: state.imageData.pixels instanceof Uint8ClampedArray,
|
||||
isArray: Array.isArray(state.imageData.pixels)
|
||||
});
|
||||
|
||||
// Create image bitmap from the restored image data
|
||||
const canvas = new OffscreenCanvas(state.imageData.width, state.imageData.height);
|
||||
const ctx = canvas.getContext('2d');
|
||||
@@ -7686,7 +8029,15 @@ localStorage.removeItem("lp");
|
||||
|
||||
await overlayManager.setImage(imageBitmap);
|
||||
overlayManager.enable();
|
||||
console.log('✅ Overlay enabled for extracted artwork');
|
||||
console.log('✅ Overlay enabled for extracted artwork - should show PROCESSED pixels');
|
||||
|
||||
// ✅ Loaded files already contain processed pixels - no need to reprocess
|
||||
// Mark as processed since the overlay is showing the correct processed data
|
||||
state.imageProcessed = true;
|
||||
|
||||
// Enable position selection immediately
|
||||
selectPosBtn.disabled = false;
|
||||
selectPosBtn.title = Utils.t('selectPosition') || 'Select starting position on canvas';
|
||||
}
|
||||
} catch (overlayError) {
|
||||
console.warn('⚠️ Could not set overlay for extracted artwork:', overlayError);
|
||||
@@ -7978,10 +8329,52 @@ localStorage.removeItem("lp");
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to update start button state
|
||||
function updateStartButtonState() {
|
||||
if (!startBtn) return;
|
||||
|
||||
if (state.paintingMode === 'assist') {
|
||||
startBtn.disabled = true;
|
||||
startBtn.title = 'Start button is disabled in Assist mode. Manually place pixels with overlay guidance.';
|
||||
} else {
|
||||
startBtn.disabled = !state.imageLoaded || state.running || !state.startPosition;
|
||||
startBtn.title = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (startBtn) {
|
||||
startBtn.addEventListener('click', startPainting);
|
||||
}
|
||||
|
||||
// Painting Mode Toggle
|
||||
const paintingModeToggle = container.querySelector('#paintingModeToggle');
|
||||
if (paintingModeToggle) {
|
||||
// Initialize toggle state - MUST be unchecked for auto mode (default)
|
||||
paintingModeToggle.checked = state.paintingMode === 'assist';
|
||||
console.log(`🎨 [Painting Mode] Initial state: ${state.paintingMode}, Toggle checked: ${paintingModeToggle.checked}`);
|
||||
|
||||
paintingModeToggle.addEventListener('change', () => {
|
||||
state.paintingMode = paintingModeToggle.checked ? 'assist' : 'auto';
|
||||
|
||||
// Update start button state
|
||||
updateStartButtonState();
|
||||
|
||||
// NOTE: We don't save paintingMode - it always resets to 'auto' on page load
|
||||
|
||||
// Show notification
|
||||
const modeName = state.paintingMode === 'assist' ? 'Assist' : 'Auto';
|
||||
const modeDesc = state.paintingMode === 'assist'
|
||||
? 'Overlay will guide your manual pixel placement'
|
||||
: 'Bot will automatically paint pixels';
|
||||
Utils.showAlert(`Painting Mode: ${modeName}\n${modeDesc}`, 'info');
|
||||
|
||||
console.log(`🎨 [Painting Mode] Switched to ${modeName.toUpperCase()} mode`);
|
||||
});
|
||||
|
||||
// Initial state update
|
||||
updateStartButtonState();
|
||||
}
|
||||
|
||||
if (stopBtn) {
|
||||
stopBtn.addEventListener('click', () => {
|
||||
state.stopFlag = true;
|
||||
@@ -8114,6 +8507,13 @@ localStorage.removeItem("lp");
|
||||
loadBotSettings();
|
||||
// Ensure notification poller reflects current settings
|
||||
NotificationManager.syncFromState();
|
||||
|
||||
// Sync painting mode toggle with loaded state
|
||||
if (paintingModeToggle) {
|
||||
paintingModeToggle.checked = state.paintingMode === 'assist';
|
||||
updateStartButtonState();
|
||||
console.log(`🔄 [Painting Mode] Synced after load: ${state.paintingMode}, Toggle checked: ${paintingModeToggle.checked}`);
|
||||
}
|
||||
}
|
||||
|
||||
function getMsToTargetCharges(current, target, cooldown, intervalMs = 0) {
|
||||
@@ -9464,6 +9864,12 @@ localStorage.removeItem("lp");
|
||||
notifyOnlyWhenUnfocused: state.notifyOnlyWhenUnfocused,
|
||||
notificationIntervalMinutes: state.notificationIntervalMinutes,
|
||||
originalImage: state.originalImage,
|
||||
ditheringMigrated: true, // Migration flag for dithering default change
|
||||
blueMarbleMigrated: true, // Migration flag for blue marble default change
|
||||
// NOTE: paintingMode is intentionally NOT saved - always defaults to 'auto' on page load
|
||||
// Save region and startPosition for assist mode coordinate calculations
|
||||
region: state.region,
|
||||
startPosition: state.startPosition,
|
||||
};
|
||||
CONFIG.PAINTING_SPEED_ENABLED = settings.paintingSpeedEnabled;
|
||||
// AUTO_CAPTCHA_ENABLED is always true - no need to save/load
|
||||
@@ -9492,8 +9898,31 @@ localStorage.removeItem("lp");
|
||||
CONFIG.PAINTING_SPEED_ENABLED = settings.paintingSpeedEnabled ?? false;
|
||||
CONFIG.AUTO_CAPTCHA_ENABLED = settings.autoCaptchaEnabled ?? false;
|
||||
state.overlayOpacity = settings.overlayOpacity ?? CONFIG.OVERLAY.OPACITY_DEFAULT;
|
||||
state.blueMarbleEnabled = settings.blueMarbleEnabled ?? CONFIG.OVERLAY.BLUE_MARBLE_DEFAULT;
|
||||
state.ditheringEnabled = settings.ditheringEnabled ?? false;
|
||||
|
||||
// MIGRATION: Force enable blue marble for existing users if not explicitly set
|
||||
// This ensures the new default (blue marble ON) is applied to existing users
|
||||
if (settings.blueMarbleMigrated !== true) {
|
||||
state.blueMarbleEnabled = true;
|
||||
console.log('🔄 Migrated: Blue Marble enabled (new default)');
|
||||
// Save the migration flag
|
||||
settings.blueMarbleMigrated = true;
|
||||
localStorage.setItem('wplace-bot-settings', JSON.stringify(settings));
|
||||
} else {
|
||||
state.blueMarbleEnabled = settings.blueMarbleEnabled ?? CONFIG.OVERLAY.BLUE_MARBLE_DEFAULT;
|
||||
}
|
||||
|
||||
// MIGRATION: Force reset dithering to false if not explicitly set in saved settings
|
||||
// This ensures the new default (dithering OFF) is applied to existing users
|
||||
if (settings.ditheringMigrated !== true) {
|
||||
state.ditheringEnabled = false;
|
||||
console.log('🔄 Migrated: Dithering reset to OFF (new default)');
|
||||
// Save the migration flag
|
||||
settings.ditheringMigrated = true;
|
||||
localStorage.setItem('wplace-bot-settings', JSON.stringify(settings));
|
||||
} else {
|
||||
state.ditheringEnabled = settings.ditheringEnabled ?? false;
|
||||
}
|
||||
|
||||
state.colorMatchingAlgorithm = settings.colorMatchingAlgorithm || 'lab';
|
||||
state.enableChromaPenalty = settings.enableChromaPenalty ?? true;
|
||||
state.chromaPenaltyWeight = settings.chromaPenaltyWeight ?? 0.15;
|
||||
@@ -9518,6 +9947,18 @@ localStorage.removeItem("lp");
|
||||
settings.notifyOnlyWhenUnfocused ?? CONFIG.NOTIFICATIONS.ONLY_WHEN_UNFOCUSED;
|
||||
state.notificationIntervalMinutes =
|
||||
settings.notificationIntervalMinutes ?? CONFIG.NOTIFICATIONS.REPEAT_MINUTES;
|
||||
// NOTE: paintingMode is NOT restored - always defaults to 'auto' on page load
|
||||
|
||||
// Restore region and startPosition for assist mode coordinate calculations
|
||||
if (settings.region) {
|
||||
state.region = settings.region;
|
||||
console.log('✅ Restored region from save file:', state.region);
|
||||
}
|
||||
if (settings.startPosition) {
|
||||
state.startPosition = settings.startPosition;
|
||||
console.log('✅ Restored startPosition from save file:', state.startPosition);
|
||||
}
|
||||
|
||||
// Restore ignore mask if dims match current resizeSettings
|
||||
if (
|
||||
settings.resizeIgnoreMask &&
|
||||
@@ -10695,6 +11136,13 @@ localStorage.removeItem("lp");
|
||||
const transInput = document.getElementById('transparencyThresholdInput');
|
||||
const whiteInput = document.getElementById('whiteThresholdInput');
|
||||
const ditherToggle = document.getElementById('enableDitheringToggle');
|
||||
|
||||
// Ensure dithering checkbox matches state (explicit sync on init)
|
||||
if (ditherToggle) {
|
||||
ditherToggle.checked = state.ditheringEnabled;
|
||||
console.log(`🎨 Dithering initialized: ${state.ditheringEnabled ? 'ON' : 'OFF'}`);
|
||||
}
|
||||
|
||||
if (algoSelect)
|
||||
algoSelect.addEventListener('change', (e) => {
|
||||
state.colorMatchingAlgorithm = e.target.value;
|
||||
|
||||
@@ -149,6 +149,7 @@ class OverlayManager {
|
||||
|
||||
// Method 2: Force browser to bypass cache by adding cache-busting parameter
|
||||
// We need to intercept and modify tile requests to add cache-busting
|
||||
// IMPORTANT: Preserve any existing fetch override (e.g., from Auto-Image assist mode)
|
||||
const originalFetch = window.fetch;
|
||||
let fetchOverrideActive = true;
|
||||
|
||||
@@ -167,6 +168,7 @@ class OverlayManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Chain to previous fetch (which may have assist mode logic)
|
||||
return originalFetch.apply(this, args);
|
||||
};
|
||||
|
||||
|
||||
@@ -1165,6 +1165,13 @@ class WPlaceUtilsManager {
|
||||
|
||||
try {
|
||||
console.log('🔄 Restoring overlay from saved data...');
|
||||
console.log('📊 Pixel data info:', {
|
||||
width: window.state.imageData.width,
|
||||
height: window.state.imageData.height,
|
||||
pixelCount: window.state.imageData.pixels?.length,
|
||||
firstPixels: window.state.imageData.pixels?.slice(0, 12), // First 3 pixels (RGBA)
|
||||
isProcessed: '⚠️ These should be PROCESSED pixels if saved after processing'
|
||||
});
|
||||
|
||||
// Recreate ImageBitmap from loaded pixel data (match old version exactly)
|
||||
const imageData = new ImageData(
|
||||
|
||||
Reference in new issue
Block a user