476 lines
19 KiB
JavaScript
476 lines
19 KiB
JavaScript
(function () {
|
|
console.log("%c Magic Garden Bot Extension Loaded ", "background: #222; color: #bada55; font-size: 20px");
|
|
|
|
// --- LOGIC: WebSocket Hook ---
|
|
window.gameSocket = null;
|
|
const OriginalSend = WebSocket.prototype.send;
|
|
|
|
WebSocket.prototype.send = function (data) {
|
|
if (!window.gameSocket || window.gameSocket.readyState >= 2) {
|
|
window.gameSocket = this;
|
|
updateStatus(true);
|
|
|
|
// Extract Player ID from URL
|
|
try {
|
|
const urlObj = new URL(this.url);
|
|
let pid = urlObj.searchParams.get('playerId');
|
|
// Remove quotes content if wrapped in " " (e.g. from %22p_...%22)
|
|
if (pid) pid = pid.replace(/^"|"$/g, '');
|
|
gameState.playerId = pid;
|
|
console.log("[MagicBot] Captured Player ID:", gameState.playerId);
|
|
} catch (e) {
|
|
console.error("[MagicBot] Failed to parse Player ID:", e);
|
|
}
|
|
|
|
this.addEventListener('close', () => {
|
|
updateStatus(false);
|
|
});
|
|
|
|
// Capture Incoming
|
|
this.addEventListener('message', (e) => {
|
|
logToOverlay('RX', e.data);
|
|
try {
|
|
const msg = JSON.parse(e.data);
|
|
if (msg.type === 'Welcome') {
|
|
handleWelcome(msg);
|
|
}
|
|
} catch (err) { }
|
|
});
|
|
}
|
|
|
|
// Capture Outgoing
|
|
logToOverlay('TX', data);
|
|
|
|
return OriginalSend.apply(this, arguments);
|
|
};
|
|
|
|
// --- STATE MANAGEMENT ---
|
|
let gameState = {
|
|
coins: 0,
|
|
garden: {},
|
|
inventory: {},
|
|
playerId: null
|
|
};
|
|
|
|
function handleWelcome(msg) {
|
|
console.log("Parsing Welcome Message...");
|
|
const root = msg.fullState.data;
|
|
|
|
console.log("Looking for slot for PlayerID:", gameState.playerId);
|
|
|
|
// Find our user slot
|
|
const userSlot = root.userSlots.find(s => s.playerId === gameState.playerId);
|
|
|
|
if (userSlot) {
|
|
console.log("Found User Slot!", userSlot);
|
|
gameState.coins = userSlot.data.coinsCount;
|
|
gameState.garden = userSlot.data.garden.tileObjects;
|
|
gameState.inventory = userSlot.data.inventory;
|
|
|
|
updateVisuals();
|
|
} else {
|
|
console.error("Could not find user slot for ID:", gameState.playerId);
|
|
// Debug: Log all available IDs
|
|
console.log("Available Slots:", root.userSlots.map(s => s.playerId));
|
|
}
|
|
}
|
|
|
|
function updateVisuals() {
|
|
// Update Wallet
|
|
const walletContent = document.getElementById('window-wallet-content');
|
|
if (walletContent) {
|
|
walletContent.innerHTML = `
|
|
<div style="font-size: 24px; color: #ffd700; text-align: center;">CA$H: ${gameState.coins.toLocaleString()}</div>
|
|
`;
|
|
}
|
|
|
|
// Update Garden
|
|
const gardenContent = document.getElementById('window-garden-content');
|
|
if (gardenContent) {
|
|
let html = '<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px;">';
|
|
const now = Date.now();
|
|
|
|
Object.entries(gameState.garden).forEach(([slotId, tile]) => {
|
|
const crop = tile.slots[0]; // Assuming single slot for now
|
|
if (!crop) return;
|
|
|
|
const isReady = now >= tile.maturedAt;
|
|
const timeLeft = Math.max(0, Math.ceil((tile.maturedAt - now) / 1000));
|
|
const color = isReady ? '#66bb6a' : '#ffa726';
|
|
|
|
html += `
|
|
<div style="background: rgba(255,255,255,0.05); padding: 5px; border-radius: 4px; border: 1px solid ${color}; font-size: 10px; text-align: center;">
|
|
<div style="font-weight: bold; color: ${color};">${tile.species}</div>
|
|
<div>${slotId}</div>
|
|
<div>${isReady ? 'READY' : timeLeft + 's'}</div>
|
|
</div>
|
|
`;
|
|
});
|
|
html += '</div>';
|
|
gardenContent.innerHTML = html;
|
|
}
|
|
}
|
|
|
|
// --- UI HELPER: Floating Windows ---
|
|
function createFloatingWindow(id, title, x, y, width, height) {
|
|
if (document.getElementById(id)) return; // Already exists
|
|
|
|
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 44px 15px rgba(0,0,0,0.5);
|
|
font-family: sans-serif;
|
|
color: #eee;
|
|
backdrop-filter: blur(5px);
|
|
resize: both;
|
|
overflow: hidden;
|
|
`;
|
|
|
|
win.innerHTML = `
|
|
<div id="${id}-header" style="padding: 8px; background: rgba(255,255,255,0.05); border-bottom: 1px solid #444; cursor: move; font-weight: bold; font-size: 12px; display: flex; justify-content: space-between;">
|
|
<span>${title}</span>
|
|
<span style="cursor: pointer; color: #ff5252;" onclick="document.getElementById('${id}').remove()">✕</span>
|
|
</div>
|
|
<div id="${id}-content" style="flex: 1; overflow-y: auto; padding: 10px;"></div>
|
|
`;
|
|
|
|
document.body.appendChild(win);
|
|
|
|
// Drag Logic
|
|
const header = win.querySelector(`#${id}-header`);
|
|
let isDragging = false, offX = 0, offY = 0;
|
|
|
|
header.addEventListener('mousedown', (e) => {
|
|
isDragging = true;
|
|
offX = e.clientX - win.offsetLeft;
|
|
offY = e.clientY - win.offsetTop;
|
|
});
|
|
window.addEventListener('mousemove', (e) => {
|
|
if (isDragging) {
|
|
win.style.left = (e.clientX - offX) + 'px';
|
|
win.style.top = (e.clientY - offY) + 'px';
|
|
}
|
|
});
|
|
window.addEventListener('mouseup', () => isDragging = false);
|
|
}
|
|
|
|
// --- LOGIC: Commands ---
|
|
function sendMsg(msg) {
|
|
if (!window.gameSocket) {
|
|
alert("Socket not connected! Wait for game to load.");
|
|
return;
|
|
}
|
|
window.gameSocket.send(JSON.stringify(msg));
|
|
}
|
|
|
|
async function harvestLoop(start, end, count, delay) {
|
|
let sent = 0;
|
|
for (let slot = start; slot <= end; slot++) {
|
|
for (let i = 0; i < count; i++) {
|
|
sendMsg({
|
|
type: 'HarvestCrop',
|
|
slot: slot,
|
|
slotsIndex: i,
|
|
scopePath: ["Room", "Quinoa"]
|
|
});
|
|
sent++;
|
|
await new Promise(r => setTimeout(r, delay));
|
|
}
|
|
}
|
|
console.log(`Harvested ${sent} items.`);
|
|
}
|
|
|
|
// --- UI: Sidebar ---
|
|
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 = `
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
|
<h3 style="margin: 0; color: #bada55; font-size: 18px;">🌱 Magic Bot</h3>
|
|
<div id="status-indicator" style="font-size: 12px; font-weight: bold; color: #ff5252;">● Disconnected</div>
|
|
</div>
|
|
|
|
<!-- Teleport -->
|
|
<div style="background: rgba(255,255,255,0.05); padding: 10px; border-radius: 8px; margin-bottom: 15px;">
|
|
<div id="head-tp" style="display: flex; justify-content: space-between; cursor: pointer; margin-bottom: 5px; user-select: none;">
|
|
<label style="font-size: 12px; color: #aaa; cursor: pointer;">Teleport</label>
|
|
<span id="arrow-tp" style="font-size: 10px; color: #aaa;">▼</span>
|
|
</div>
|
|
<div id="section-tp">
|
|
<div style="display: flex; gap: 8px;">
|
|
<input type="number" id="tp-x" placeholder="X" value="15" style="width: 100%; padding: 6px; background: #333; border: 1px solid #555; color: white; border-radius: 4px;">
|
|
<input type="number" id="tp-y" placeholder="Y" value="15" style="width: 100%; padding: 6px; background: #333; border: 1px solid #555; color: white; border-radius: 4px;">
|
|
<button id="btn-tp" style="cursor: pointer; background: #448aff; color: white; border: none; padding: 0 15px; border-radius: 4px; font-weight: bold;">Go</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Harvest -->
|
|
<div style="background: rgba(255,255,255,0.05); padding: 10px; border-radius: 8px; margin-bottom: 15px;">
|
|
<div id="head-hv" style="display: flex; justify-content: space-between; cursor: pointer; margin-bottom: 5px; user-select: none;">
|
|
<label style="font-size: 12px; color: #aaa; cursor: pointer;">Harvest</label>
|
|
<span id="arrow-hv" style="font-size: 10px; color: #aaa;">▼</span>
|
|
</div>
|
|
<div id="section-hv">
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 8px;">
|
|
<div>
|
|
<span style="font-size: 10px; color: #888;">Start Slot</span>
|
|
<input type="number" id="hv-start" value="140" style="width: 100%; padding: 6px; background: #333; border: 1px solid #555; color: white; border-radius: 4px;">
|
|
</div>
|
|
<div>
|
|
<span style="font-size: 10px; color: #888;">End Slot</span>
|
|
<input type="number" id="hv-end" value="160" style="width: 100%; padding: 6px; background: #333; border: 1px solid #555; color: white; border-radius: 4px;">
|
|
</div>
|
|
</div>
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 10px;">
|
|
<div>
|
|
<span style="font-size: 10px; color: #888;">Iter. (Count)</span>
|
|
<input type="number" id="hv-count" value="1" style="width: 100%; padding: 6px; background: #333; border: 1px solid #555; color: white; border-radius: 4px;">
|
|
</div>
|
|
<div>
|
|
<span style="font-size: 10px; color: #888;">Delay (ms)</span>
|
|
<input type="number" id="hv-delay" value="20" style="width: 100%; padding: 6px; background: #333; border: 1px solid #555; color: white; border-radius: 4px;">
|
|
</div>
|
|
</div>
|
|
<button id="btn-harvest" style="width: 100%; cursor: pointer; background: #66bb6a; color: white; border: none; padding: 8px; border-radius: 4px; font-weight: bold; transition: background 0.2s;">Run Harvest</button>
|
|
</div>
|
|
</div>
|
|
|
|
<button id="btn-sell" style="width: 100%; cursor: pointer; background: #ffa726; color: white; border: none; padding: 10px; border-radius: 8px; font-weight: bold; box-shadow: 0 2px 5px rgba(0,0,0,0.2);">Sell All Crops</button>
|
|
|
|
<div style="border-top: 1px solid #444; margin: 10px 0;"></div>
|
|
|
|
<div style="display: flex; gap: 5px;">
|
|
<button id="btn-logs" style="flex: 1; cursor: pointer; background: #5c6bc0; color: white; border: none; padding: 8px; border-radius: 4px; font-weight: bold;">Logs</button>
|
|
<button id="btn-state" style="flex: 1; cursor: pointer; background: #ab47bc; color: white; border: none; padding: 8px; border-radius: 4px; font-weight: bold;">State</button>
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(sidebar);
|
|
|
|
document.getElementById('btn-state').onclick = () => {
|
|
createFloatingWindow('window-wallet', '💰 Wallet', 20, 400, 200, 80);
|
|
createFloatingWindow('window-garden', '🌻 Garden', 240, 400, 300, 300);
|
|
updateVisuals(); // Refresh if data exists
|
|
};
|
|
|
|
// --- 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 = `
|
|
<div style="padding: 5px 10px; background: #222; border-bottom: 1px solid #444; display: flex; justify-content: space-between; align-items: center; cursor: move;" id="log-header">
|
|
<span style="font-weight: bold; color: #aaa;">Network Logs</span>
|
|
<div>
|
|
<button id="btn-clear-logs" style="background: none; border: none; color: #888; cursor: pointer; margin-right: 10px;">Clear</button>
|
|
<button id="btn-close-logs" style="background: none; border: none; color: #ff5252; cursor: pointer;">X</button>
|
|
</div>
|
|
</div>
|
|
<div id="log-content" style="flex: 1; overflow-y: auto; padding: 5px; word-break: break-all;"></div>
|
|
`;
|
|
document.body.appendChild(logOverlay);
|
|
logOverlay.style.display = 'none'; // Ensure hidden initially
|
|
|
|
// Make Draggable
|
|
let isDragging = false;
|
|
let dragOffsetX = 0;
|
|
let dragOffsetY = 0;
|
|
const header = logOverlay.querySelector('#log-header');
|
|
|
|
header.addEventListener('mousedown', (e) => {
|
|
isDragging = true;
|
|
dragOffsetX = e.clientX - logOverlay.offsetLeft;
|
|
dragOffsetY = e.clientY - logOverlay.offsetTop;
|
|
});
|
|
|
|
window.addEventListener('mousemove', (e) => {
|
|
if (isDragging) {
|
|
logOverlay.style.left = (e.clientX - dragOffsetX) + 'px';
|
|
logOverlay.style.top = (e.clientY - dragOffsetY) + 'px';
|
|
}
|
|
});
|
|
|
|
window.addEventListener('mouseup', () => isDragging = false);
|
|
|
|
// Stop propagation for logs too
|
|
logOverlay.addEventListener('keydown', (e) => e.stopPropagation());
|
|
logOverlay.addEventListener('keyup', (e) => e.stopPropagation());
|
|
logOverlay.addEventListener('keypress', (e) => e.stopPropagation());
|
|
|
|
function logToOverlay(type, data) {
|
|
const container = document.getElementById('log-content');
|
|
if (!container) return;
|
|
|
|
// Check if we are near the bottom BEFORE adding new content (within 50px)
|
|
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';
|
|
|
|
const timestamp = new Date().toLocaleTimeString();
|
|
let color = type === 'TX' ? '#66bb6a' : '#448aff';
|
|
let content = data;
|
|
|
|
// Pretty print JSON
|
|
try {
|
|
if (typeof data === 'string' && (data.startsWith('{') || data.startsWith('['))) {
|
|
content = JSON.stringify(JSON.parse(data)); // Minify
|
|
}
|
|
} catch (e) { }
|
|
|
|
line.innerHTML = `<span style="color: #666;">[${timestamp}]</span> <span style="color: ${color}; font-weight: bold;">${type}</span> <span style="color: #ccc;">${content}</span>`;
|
|
|
|
// Click to copy
|
|
line.style.cursor = 'pointer';
|
|
line.title = 'Click to copy raw message';
|
|
line.addEventListener('click', () => {
|
|
navigator.clipboard.writeText(data).then(() => {
|
|
// Visual feedback
|
|
line.style.background = 'rgba(255, 255, 255, 0.1)';
|
|
setTimeout(() => line.style.background = 'transparent', 200);
|
|
console.log('Copied to clipboard:', data);
|
|
}).catch(err => console.error('Failed to copy matches:', err));
|
|
});
|
|
|
|
container.appendChild(line);
|
|
|
|
// Auto scroll ONLY if we were already at the bottom
|
|
if (isAtBottom) {
|
|
container.scrollTop = container.scrollHeight;
|
|
}
|
|
|
|
// Limit history (500 lines)
|
|
if (container.children.length > 500) {
|
|
container.removeChild(container.firstChild);
|
|
}
|
|
}
|
|
|
|
// Stop keyboard events from reaching the game (Fixes specific keys not working)
|
|
sidebar.addEventListener('keydown', (e) => e.stopPropagation());
|
|
sidebar.addEventListener('keyup', (e) => e.stopPropagation());
|
|
sidebar.addEventListener('keypress', (e) => e.stopPropagation());
|
|
|
|
// --- COLLAPSE LOGIC ---
|
|
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');
|
|
|
|
document.getElementById('btn-logs').onclick = () => {
|
|
const el = document.getElementById('magic-bot-logs');
|
|
el.style.display = el.style.display === 'none' ? 'flex' : 'none';
|
|
// Reset position if off-screen (optional safety)
|
|
if (el.style.display === 'flex') {
|
|
el.style.top = 'auto';
|
|
el.style.bottom = '20px';
|
|
el.style.left = '20px';
|
|
}
|
|
};
|
|
|
|
document.getElementById('btn-close-logs').onclick = () => {
|
|
document.getElementById('magic-bot-logs').style.display = 'none';
|
|
};
|
|
|
|
document.getElementById('btn-clear-logs').onclick = () => {
|
|
document.getElementById('log-content').innerHTML = '';
|
|
};
|
|
|
|
// --- UI LOGIC ---
|
|
function updateStatus(connected) {
|
|
const el = document.getElementById('status-indicator');
|
|
if (connected) {
|
|
el.innerHTML = '● Connected';
|
|
el.style.color = '#66bb6a'; // Green
|
|
} else {
|
|
el.innerHTML = '● Disconnected';
|
|
el.style.color = '#ff5252'; // Red
|
|
}
|
|
}
|
|
|
|
document.getElementById('btn-tp').onclick = () => {
|
|
const x = parseInt(document.getElementById('tp-x').value);
|
|
const y = parseInt(document.getElementById('tp-y').value);
|
|
|
|
sendMsg({
|
|
type: 'Teleport',
|
|
position: { x, y },
|
|
scopePath: ["Room", "Quinoa"]
|
|
});
|
|
};
|
|
|
|
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);
|
|
|
|
harvestLoop(start, end, count, delay);
|
|
};
|
|
|
|
document.getElementById('btn-sell').onclick = () => {
|
|
sendMsg({
|
|
type: 'SellAllCrops',
|
|
scopePath: ["Room", "Quinoa"]
|
|
});
|
|
};
|
|
|
|
})();
|