(function () { const MB = window.MagicBot; // --- UI CREATION --- const sidebar = document.createElement('div'); sidebar.id = 'magic-bot-sidebar'; sidebar.style.cssText = ` position: fixed; top: 20px; right: 20px; width: 280px; background: rgba(20, 20, 25, 0.95); color: #eee; padding: 20px; border-radius: 12px; z-index: 9999999; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; box-shadow: 0 4px 15px rgba(0,0,0,0.6); border: 1px solid #444; backdrop-filter: blur(5px); `; sidebar.innerHTML = `

🌱 Magic Bot

● Disconnected
Start Slot
End Slot
Iter. (Count)
Delay (ms)
`; document.body.appendChild(sidebar); // Stop keyboard propagation sidebar.addEventListener('keydown', (e) => e.stopPropagation()); sidebar.addEventListener('keyup', (e) => e.stopPropagation()); sidebar.addEventListener('keypress', (e) => e.stopPropagation()); // --- UI LOG UPDATE --- MB.on('log', (msg) => { logToOverlay(msg.type, msg.data); }); MB.on('socket_connected', (connected) => { const el = document.getElementById('status-indicator'); if (connected) { el.innerHTML = '● Connected'; el.style.color = '#66bb6a'; } else { el.innerHTML = '● Disconnected'; el.style.color = '#ff5252'; } }); // --- BINDINGS --- const setupCollapse = (headId, bodyId, arrowId) => { document.getElementById(headId).onclick = () => { const el = document.getElementById(bodyId); const arrow = document.getElementById(arrowId); if (el.style.display === 'none') { el.style.display = 'block'; arrow.textContent = '▼'; } else { el.style.display = 'none'; arrow.textContent = '▶'; } }; }; setupCollapse('head-tp', 'section-tp', 'arrow-tp'); setupCollapse('head-hv', 'section-hv', 'arrow-hv'); setupCollapse('head-auto', 'section-auto', 'arrow-auto'); setupCollapse('head-diet', 'section-diet', 'arrow-diet'); // Automation Toggles document.getElementById('chk-auto-plant').onchange = (e) => { if (MB.automation) MB.automation.autoPlant = e.target.checked; }; document.getElementById('chk-auto-harvest').onchange = (e) => { if (MB.automation) MB.automation.autoHarvest = e.target.checked; }; document.getElementById('chk-auto-sell').onchange = (e) => { if (MB.automation) MB.automation.autoSell = e.target.checked; }; document.getElementById('chk-smart-harvest').onchange = (e) => { if (MB.automation) MB.automation.smartHarvest = e.target.checked; }; /* Auto Feed listener is likely already added if chunk 1 ran, but let's double check context */ /* Wait, the previous chunk 4 relied on chk-auto-feed onchange being added. Check if it's there. */ /* Viewing the file, I see the HTML for chk-auto-feed. I do NOT see the listener in the lines I viewed (up to 120). Listeners are further down. */ /* Assuming listener is missing. */ document.getElementById('chk-auto-feed').onchange = (e) => { if (MB.automation) MB.automation.autoFeed = e.target.checked; }; // Populate Diet List const populateDietList = () => { const list = document.getElementById('diet-list'); if (!list) return; list.innerHTML = ''; // Known crops list - could be dynamic from shop but let's hardcode common ones or try to read from state if available // Reading from shop state is better if available let crops = ["Carrot", "Tomato", "Potato", "Corn", "Wheat", "Strawberry", "Blueberry", "Pumpkin", "Watermelon", "Radish", "Cabbage", "Lettuce", "Pepper", "Apple", "Orange", "Banana", "Coconut", "Grape", "Lemon", "Lime", "Peach", "Pear", "Plum", "Cherry", "Mango", "Pineapple", "Aloe", "Daffodil", "OrangeTulip"]; // Try to merge with shop data if present if (MB.state && MB.state.shops && MB.state.shops.seed && MB.state.shops.seed.inventory) { const shopCrops = MB.state.shops.seed.inventory.map(i => i.species); // Union crops = [...new Set([...crops, ...shopCrops])]; } crops.sort(); crops.forEach(crop => { const label = document.createElement('label'); label.style.cssText = "display: flex; align-items: center; gap: 4px; font-size: 10px; cursor: pointer; user-select: none;"; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.style.cursor = 'pointer'; if (MB.automation && MB.automation.petDiet && MB.automation.petDiet.has(crop)) { checkbox.checked = true; } checkbox.onchange = (e) => { if (!MB.automation) return; if (!MB.automation.petDiet) MB.automation.petDiet = new Set(); if (e.target.checked) MB.automation.petDiet.add(crop); else MB.automation.petDiet.delete(crop); }; label.appendChild(checkbox); label.appendChild(document.createTextNode(crop)); list.appendChild(label); }); }; // Initial populate setTimeout(populateDietList, 2000); // Wait for state to load maybe? // Also re-populate on state update if empty? MB.on('state_updated', () => { const list = document.getElementById('diet-list'); if (list && list.children.length === 0) populateDietList(); }); document.getElementById('btn-diet-all').onclick = () => { const list = document.getElementById('diet-list'); if (!list) return; const checkboxes = list.querySelectorAll('input[type="checkbox"]'); checkboxes.forEach(cb => { if (cb.checked) { cb.click(); // Trigger change event } }); }; document.getElementById('btn-diet-none').onclick = () => { const list = document.getElementById('diet-list'); if (!list) return; const checkboxes = list.querySelectorAll('input[type="checkbox"]'); checkboxes.forEach(cb => { if (cb.checked) { cb.click(); } }); }; document.getElementById('btn-tp').onclick = () => { const x = parseInt(document.getElementById('tp-x').value); const y = parseInt(document.getElementById('tp-y').value); MB.teleport(x, y); }; document.getElementById('btn-harvest').onclick = () => { const start = parseInt(document.getElementById('hv-start').value); const end = parseInt(document.getElementById('hv-end').value); const count = parseInt(document.getElementById('hv-count').value); const delay = parseInt(document.getElementById('hv-delay').value); MB.harvestLoop(start, end, count, delay); }; document.getElementById('btn-sell').onclick = () => { MB.sellAll(); }; document.getElementById('btn-logs').onclick = () => { const el = document.getElementById('magic-bot-logs'); el.style.display = el.style.display === 'none' ? 'flex' : 'none'; if (el.style.display === 'flex') { el.style.top = 'auto'; el.style.bottom = '20px'; el.style.left = '20px'; } }; document.getElementById('btn-state').onclick = () => { createFloatingWindow('window-wallet', '💰 Wallet', 20, 20, 200, 80); createFloatingWindow('window-garden', '🌻 Garden', 240, 20, 600, 320); createFloatingWindow('window-inventory', '🎒 Inventory', 560, 20, 200, 200); createFloatingWindow('window-shop', '🏪 Shop', 780, 20, 250, 300); createFloatingWindow('window-players', '👥 Players', 20, 120, 200, 150); updateVisuals(); }; // --- VISUALIZERS --- MB.on('state_updated', updateVisuals); function updateVisuals() { console.log("[MagicBot] Updating visuals...", MB.state); // --- Wallet --- const wallet = document.getElementById('window-wallet-content'); if (wallet) { if (MB.state.coins !== undefined) { wallet.innerHTML = `
CA$H: ${MB.state.coins.toLocaleString()}
`; } else { console.warn("[MagicBot] Wallet: MB.state.coins is undefined"); wallet.innerHTML = '
No Coin Data
'; } } // --- Garden --- const garden = document.getElementById('window-garden-content'); if (garden) { if (MB.state.garden && MB.state.garden.tileObjects) { console.log("[MagicBot] Render Garden: Split Grid 1-200"); const now = Date.now(); // Containers for the two grids const container = document.createElement('div'); container.style.cssText = "display: flex; gap: 10px; justify-content: center;"; // Harvest All Button const btnHarvestAll = document.createElement('button'); btnHarvestAll.textContent = "Harvest All Ready"; btnHarvestAll.style.cssText = "width: 100%; margin-bottom: 10px; padding: 8px; background: #66bb6a; color: white; border: none; border-radius: 4px; font-weight: bold; cursor: pointer;"; btnHarvestAll.onclick = async () => { console.log("[MagicBot] Harvest All Triggered"); let count = 0; const now = Date.now(); // Iterate all slots for (let i = 0; i < 200; i++) { const slotId = i.toString(); const tile = MB.state.garden.tileObjects[slotId]; const slots = tile && tile.slots ? tile.slots : []; slots.forEach((s, idx) => { if (now >= s.endTime) { MB.sendMsg({ type: 'HarvestCrop', slot: i, slotsIndex: idx, scopePath: ["Room", "Quinoa"] }); count++; } }); } console.log(`[MagicBot] Sent harvest for ${count} crops.`); btnHarvestAll.textContent = `Harvested ${count}!`; setTimeout(() => btnHarvestAll.textContent = "Harvest All Ready", 1000); }; // Wrapper to hold button + grids const wrapper = document.createElement('div'); wrapper.appendChild(btnHarvestAll); wrapper.appendChild(container); const leftGrid = document.createElement('div'); leftGrid.style.cssText = "display: grid; grid-template-columns: repeat(10, 1fr); gap: 2px;"; const rightGrid = document.createElement('div'); rightGrid.style.cssText = "display: grid; grid-template-columns: repeat(10, 1fr); gap: 2px;"; container.appendChild(leftGrid); container.appendChild(rightGrid); // Helper to get color based on progress (0.0 to 1.0) const getProgressColor = (p) => { // Red (0) -> Yellow (0.5) -> Green (1.0) // Simple HSL: 0 (Red) -> 120 (Green) const hue = Math.floor(Math.max(0, Math.min(1, p)) * 120); return `hsl(${hue}, 70%, 50%)`; }; // Loop 0 to 199 for (let i = 0; i < 200; i++) { const slotId = i.toString(); const tile = MB.state.garden.tileObjects[slotId]; // tile.slots is an array of harvestable instances const slots = tile && tile.slots ? tile.slots : []; const crop = slots.length > 0 ? slots[0] : null; const cell = document.createElement('div'); cell.style.cssText = "width: 25px; height: 25px; border: 1px solid #333; border-radius: 3px; background: rgba(255,255,255,0.02); position: relative; cursor: pointer;"; if (crop) { // Calculate visuals based on first crop (or average? First is simplest) // Use the most mature crop for color? Or just the first one. First one is fine for background. const totalTime = crop.endTime - crop.startTime; const elapsed = Math.max(0, now - crop.startTime); const progress = totalTime > 0 ? (elapsed / totalTime) : 1; const color = getProgressColor(progress); // Check readiness of ALL slots let anyReady = false; let tooltip = `Slot Index: ${i}\nSpecies: ${tile.species}`; // Build tooltip and check anyReady slots.forEach((s, idx) => { const isReady = now >= s.endTime; // Individual readiness if (isReady) anyReady = true; const timeLeft = Math.max(0, Math.ceil((s.endTime - now) / 1000)); // Format: "#0: READY" or "#0: 45s" tooltip += `\n#${idx}: ${isReady ? 'READY' : timeLeft + 's'}`; if (s.mutations && s.mutations.length > 0) { tooltip += ` [${s.mutations.join(',')}]`; } }); cell.style.background = color; // Bright border if ANY are ready cell.style.border = `1px solid ${anyReady ? '#fff' : '#555'}`; cell.title = tooltip; // Optional: Small letter for species cell.innerHTML = `${tile.species[0]}`; // Click to harvest ALL ready slots cell.onclick = () => { let harvestedCount = 0; slots.forEach((s, idx) => { if (now >= s.endTime) { console.log(`[MagicBot] Harvesting Slot ${i} Index ${idx}`); MB.sendMsg({ type: 'HarvestCrop', slot: i, slotsIndex: idx, scopePath: ["Room", "Quinoa"] }); harvestedCount++; } }); if (harvestedCount > 0) { cell.style.opacity = '0.5'; } else { console.log("[MagicBot] No ready crops in slot", i); } }; } else { cell.title = `Slot Index: ${i}\nEmpty`; } // Logic: 0-9 -> Left Grid, 10-19 -> Right Grid // i % 20 gives 0-19 column index const colIndex = i % 20; if (colIndex < 10) { leftGrid.appendChild(cell); } else { rightGrid.appendChild(cell); } } garden.innerHTML = ''; garden.appendChild(wrapper); } else { console.warn("[MagicBot] Garden: MB.state.garden is missing or invalid", MB.state.garden); garden.innerHTML = '
No Garden Data
'; } } // --- Inventory --- const inv = document.getElementById('window-inventory-content'); if (inv) { if (MB.state.inventory && MB.state.inventory.items) { console.log("[MagicBot] Render Inventory:", MB.state.inventory.items.length, "items"); console.log("[MagicBot] Render Inventory:", MB.state.inventory.items.length, "items"); const container = document.createElement('div'); container.style.cssText = "display: grid; grid-template-columns: repeat(2, 1fr); gap: 5px; font-size: 11px;"; MB.state.inventory.items.forEach(item => { // Try to resolve name from parameters (e.g. Seed -> Carrot) let name = item.itemType; let species = null; if (item.species) { species = item.species; } else if (item.parameters) { if (item.parameters.species) { species = item.parameters.species; } else if (item.parameters.speciesIds) { // Handle speciesIds map/array (e.g. { "0": "Carrot" }) const vals = Object.values(item.parameters.speciesIds); if (vals.length > 0) species = vals[0]; } } if (species) { name = species + " " + item.itemType; } else if (item.itemType === 'Seed') { console.warn("[MagicBot] Seed item missing species:", item); } const count = item.quantity || item.count || 1; const itemDiv = document.createElement('div'); itemDiv.style.cssText = "background: rgba(255,255,255,0.05); padding: 3px; border-radius: 3px; display: flex; justify-content: space-between; cursor: pointer; transition: background 0.2s;"; itemDiv.innerHTML = ` ${name} x${count} `; // Click to Plant itemDiv.onclick = () => { // Check if it's a seed if (item.itemType === 'Seed' && species) { console.log("[MagicBot] Attempting to plant:", species); // Find first empty slot (0-199) let emptySlot = -1; for (let i = 0; i < 200; i++) { const slotId = i.toString(); const tile = MB.state.garden.tileObjects[slotId]; const hasCrop = tile && tile.slots && tile.slots.length > 0; if (!hasCrop) { emptySlot = i; break; } } if (emptySlot !== -1) { console.log("[MagicBot] Found empty slot:", emptySlot); MB.sendMsg({ type: "PlantSeed", slot: emptySlot, species: species, scopePath: ["Room", "Quinoa"] }); // Visual feedback? Maybe flash the inventory item or something? itemDiv.style.background = 'rgba(100,255,100,0.2)'; setTimeout(() => itemDiv.style.background = 'rgba(255,255,255,0.05)', 200); } else { console.warn("[MagicBot] No empty slots available!"); alert("Garden is full!"); } } else { console.log("[MagicBot] Item is not a plantable seed."); } }; // Hover itemDiv.onmouseenter = () => itemDiv.style.background = 'rgba(255,255,255,0.1)'; itemDiv.onmouseleave = () => itemDiv.style.background = 'rgba(255,255,255,0.05)'; container.appendChild(itemDiv); }); if (MB.state.inventory.items.length === 0) { container.innerHTML = '
Inventory empty
'; } inv.innerHTML = ''; inv.appendChild(container); } else { console.warn("[MagicBot] Inventory: MB.state.inventory is missing", MB.state.inventory); inv.innerHTML = '
No Inventory Data
'; } } // --- Shop (Seeds) --- const shop = document.getElementById('window-shop-content'); if (shop) { if (MB.state.shops && MB.state.shops.seed) { console.log("[MagicBot] Render Shop:", MB.state.shops.seed.inventory.length, "items"); console.log("[MagicBot] Render Shop:", MB.state.shops.seed.inventory.length, "items"); const container = document.createElement('div'); container.style.cssText = "display: grid; grid-template-columns: repeat(2, 1fr); gap: 5px; font-size: 11px;"; MB.state.shops.seed.inventory.forEach(item => { // Calculate dynamic stock // Purchases are stored in MB.state.shopPurchases.seed.purchases[SpeciesName] = quantity let purchased = 0; if (MB.state.shopPurchases && MB.state.shopPurchases.seed && MB.state.shopPurchases.seed.purchases) { purchased = MB.state.shopPurchases.seed.purchases[item.species] || 0; } const currentStock = Math.max(0, item.initialStock - purchased); const stockColor = currentStock > 0 ? '#66bb6a' : '#ff5252'; const isAutoBuying = MB.automation && MB.automation.autoBuyItems.has(item.species); const borderColor = isAutoBuying ? '#ffd700' : 'transparent'; // If not auto-buying, fallback to stock indicator on left border const itemDiv = document.createElement('div'); itemDiv.style.cssText = `background: rgba(255,255,255,0.05); padding: 5px; border-radius: 4px; border-left: 2px solid ${stockColor}; border-right: 2px solid ${borderColor}; cursor: pointer; transition: background 0.2s;`; itemDiv.innerHTML = `
${item.species} ${isAutoBuying ? '✅' : ''}
Stock: ${currentStock}/${item.initialStock}
`; // Right-click to toggle Auto-Buy itemDiv.oncontextmenu = (e) => { e.preventDefault(); if (MB.automation) { if (MB.automation.autoBuyItems.has(item.species)) { MB.automation.autoBuyItems.delete(item.species); console.log("[MagicBot] Stopped auto-buying:", item.species); } else { MB.automation.autoBuyItems.add(item.species); console.log("[MagicBot] Started auto-buying:", item.species); } updateVisuals(); } }; // Click to Buy itemDiv.onclick = () => { if (currentStock > 0) { console.log("[MagicBot] Buying Seed:", item.species); MB.sendMsg({ scopePath: ["Room", "Quinoa"], type: "PurchaseSeed", species: item.species }); } else { console.log("[MagicBot] Out of stock:", item.species); } }; // Hover effect itemDiv.onmouseenter = () => itemDiv.style.background = 'rgba(255,255,255,0.1)'; itemDiv.onmouseleave = () => itemDiv.style.background = 'rgba(255,255,255,0.05)'; container.appendChild(itemDiv); }); shop.innerHTML = ''; shop.appendChild(container); } else { console.warn("[MagicBot] Shop: MB.state.shops is missing", MB.state.shops); shop.innerHTML = '
No Shop Data
'; } } // --- Players --- const players = document.getElementById('window-players-content'); if (players) { if (MB.state.players) { console.log("[MagicBot] Render Players:", MB.state.players.length); let html = '
'; MB.state.players.forEach(p => { const isSelf = p.id === MB.state.playerId; const statusColor = p.isConnected ? '#66bb6a' : '#666'; html += `
${p.name} ${isSelf ? 'YOU' : ''}
`; }); html += '
'; players.innerHTML = html; } else { console.warn("[MagicBot] Players: MB.state.players is missing", MB.state.players); players.innerHTML = '
No Player Data
'; } } } // --- OVERLAYS --- // Log Overlay const logOverlay = document.createElement('div'); logOverlay.id = 'magic-bot-logs'; logOverlay.style.cssText = `display: none; position: fixed; bottom: 20px; left: 20px; width: 600px; height: 300px; background: rgba(10, 10, 15, 0.95); border: 1px solid #444; border-radius: 8px; z-index: 9999999; font-family: 'Consolas', 'Monaco', monospace; font-size: 11px; color: #eee; display: flex; flex-direction: column; box-shadow: 0 4px 15px rgba(0,0,0,0.6); resize: both; overflow: hidden;`; logOverlay.innerHTML = `
Network Logs
`; document.body.appendChild(logOverlay); makeDraggable(logOverlay, 'log-header'); // Stop logs propagation logOverlay.addEventListener('keydown', (e) => e.stopPropagation()); logOverlay.addEventListener('keyup', (e) => e.stopPropagation()); logOverlay.addEventListener('keypress', (e) => e.stopPropagation()); document.getElementById('btn-clear-logs').onclick = () => { document.getElementById('log-content').innerHTML = ''; }; function logToOverlay(type, data) { const container = document.getElementById('log-content'); if (!container) return; const isAtBottom = (container.scrollHeight - container.scrollTop - container.clientHeight) < 50; const line = document.createElement('div'); line.style.borderBottom = '1px solid #222'; line.style.padding = '2px 0'; line.style.cursor = 'pointer'; line.title = 'Click to copy raw message'; const timestamp = new Date().toLocaleTimeString(); let color = type === 'TX' ? '#66bb6a' : '#448aff'; let content = data; try { if (typeof data === 'string' && (data.startsWith('{') || data.startsWith('['))) { content = JSON.stringify(JSON.parse(data)); } } catch (e) { } line.innerHTML = `[${timestamp}] ${type} ${content}`; line.addEventListener('click', () => { navigator.clipboard.writeText(data).then(() => { line.style.background = 'rgba(255, 255, 255, 0.1)'; setTimeout(() => line.style.background = 'transparent', 200); }).catch(e => console.error(e)); }); container.appendChild(line); if (isAtBottom) container.scrollTop = container.scrollHeight; if (container.children.length > 500) container.removeChild(container.firstChild); } // --- HELPER: Draggable Windows --- function makeDraggable(el, headerId) { const header = el.querySelector('#' + headerId); let isDragging = false, offX = 0, offY = 0; header.addEventListener('mousedown', (e) => { isDragging = true; offX = e.clientX - el.offsetLeft; offY = e.clientY - el.offsetTop; }); window.addEventListener('mousemove', (e) => { if (isDragging) { el.style.left = (e.clientX - offX) + 'px'; el.style.top = (e.clientY - offY) + 'px'; } }); window.addEventListener('mouseup', () => isDragging = false); } function createFloatingWindow(id, title, x, y, width, height) { if (document.getElementById(id)) return; const win = document.createElement('div'); win.id = id; win.style.cssText = `position: fixed; top: ${y}px; left: ${x}px; width: ${width}px; height: ${height}px; background: rgba(10, 10, 20, 0.9); border: 1px solid #444; border-radius: 8px; z-index: 9999995; display: flex; flex-direction: column; box-shadow: 0 4px 15px rgba(0,0,0,0.5); font-family: sans-serif; color: #eee; backdrop-filter: blur(5px); resize: both; overflow: hidden;`; win.innerHTML = `
${title}
`; document.body.appendChild(win); makeDraggable(win, `${id}-header`); } // Expose helpers globally if needed by other modules (e.g. state) window.createFloatingWindow = createFloatingWindow; console.log('[MagicBot] UI module loaded.'); })();