// Main Entry Point console.log("%c Magic Garden Bot Starting... ", "background: #222; color: #bada55; font-size: 20px"); (function () { const MB = window.MagicBot; // --- AUTOMATION STATE --- MB.settings = { debug: false }; MB.automation = { autoPlant: false, autoHarvest: false, autoSell: false, autoSell: false, autoFeed: false, smartHarvest: false, autoBuyItems: new Set(), // Set of species names to auto-buy petDiet: new Set(), // Set of species names allowed for pets petDiet: new Set(), // Set of species names allowed for pets selectedSeed: null, // The seed user wants to auto-plant weatherTracker: { lastEndTime: Date.now(), // Assume clear sky starts now if unknown currentEvent: null } }; function updateWeatherTracker() { if (!MB.state || !MB.state.child || !MB.state.child.data) return; const currentWeather = MB.state.child.data.weather; // 'Rain', 'Frost', or null const tracker = MB.automation.weatherTracker; // Detect end of event (Value -> Null) if (tracker.currentEvent && !currentWeather) { console.log(`[WeatherTracker] Event ${tracker.currentEvent} ended. Timer reset.`); tracker.lastEndTime = Date.now(); } // Detect start of event (Null -> Value) if (!tracker.currentEvent && currentWeather) { console.log(`[WeatherTracker] Event ${currentWeather} started!`); } tracker.currentEvent = currentWeather; } // --- AUTOMATION LOOP --- setInterval(() => { if (!MB.state || !MB.socket) return; updateWeatherTracker(); // Update Weather Stats for UI if (MB.automation.weatherTracker) { MB.automation.weatherTracker.probabilities = getWorldEvents(MB.state); } // 1. Auto Harvest if (MB.automation.autoHarvest && MB.state.garden && MB.state.garden.tileObjects) { const now = Date.now(); Object.keys(MB.state.garden.tileObjects).forEach(slotId => { const tile = MB.state.garden.tileObjects[slotId]; if (tile && tile.slots) { tile.slots.forEach((s, idx) => { // Debug Logging if (MB.settings && MB.settings.debug) { console.log(`[AutoHarvest] Checking Slot ${slotId} Index ${idx}:`, { species: s.species, ready: now >= s.endTime, timeLeft: (s.endTime - now) / 1000, mutations: s.mutations }); } if (now >= s.endTime) { const mutations = s.mutations || []; // Smart Harvest Logic if (MB.automation.smartHarvest) { // 0. Safety: Check for Planted Eggs // Eggs usually have objectType 'egg' or similar. // Based on standard state structures, if checks fail, better safe than sorry. // Inspecting fullstate shows objectType property on the tile itself. if (tile.objectType && tile.objectType.toLowerCase().includes('egg')) { if (MB.settings && MB.settings.debug) console.log(`[AutoHarvest] Skipping Egg at slot ${slotId}`); return; } const isRainbow = mutations.includes('Rainbow'); const isGold = mutations.includes('Gold'); const isFrozen = mutations.includes('Frozen'); const isWet = mutations.includes('Wet'); const duration = s.endTime - s.startTime; // Duration in ms // 1. Rainbow Strategy: Harvest Priority (x50) // Rainbow doesn't stack well and is huge value. Cash out. if (isRainbow) { // Harvest immediately! // Fall through to harvest call. if (MB.settings && MB.settings.debug) console.log(`[AutoHarvest] Harvesting Rainbow at ${slotId}`); } // 2. Gold Strategy: Wait for Frozen (x200 EV) else if (isGold && !isFrozen) { if (MB.settings && MB.settings.debug) console.log(`[AutoHarvest] Skipping Gold (Waiting for Frozen) at ${slotId}`); return; // Skip } // 3. Long Crop Strategy (> 10 mins): Wait for Wet (x2 EV) // Continues to Bellman Logic below... // 4. Bellman Step (Section 3) Smart Harvest // Check if decision module is loaded if (MB.Decision) { const worldEvents = getWorldEvents(MB.state); const cropState = { species: s.species || tile.species, baseValue: s.baseValue || 1, // Need base value source? scale: s.scale || 1, // Can use targetScale or derive current scale? mutations: mutations }; const events = { ...worldEvents, Time_Remaining_Hrs: (s.endTime - Date.now()) / 3600000 }; const logic = MB.Decision.shouldHarvest(cropState, events); if (MB.settings && MB.settings.debug) { console.log(`[SmartHarvest] Slot ${slotId}: ${logic.action}`, logic); } if (logic.action === 'WAIT') { return; // Skip harvest } } } // Don't spam: check if we just sent it? // For simplicity, relying on server or next state update. // A simple dedupe could be added if needed. MB.sendMsg({ type: 'HarvestCrop', slot: parseInt(slotId), slotsIndex: idx, scopePath: ["Room", "Quinoa"] }); if (MB.settings && MB.settings.debug) console.log(`[AutoHarvest] Sent HarvestCrop for ${slotId}`); } }); } }); } // 2. Auto Plant if (MB.automation.autoPlant && MB.state.garden && MB.state.garden.tileObjects) { // Find a seed to plant let seedToPlant = MB.automation.selectedSeed; // Fallback: If no selected seed, find FIRST available seed in inventory if (!seedToPlant && MB.state.inventory && MB.state.inventory.items) { const firstSeed = MB.state.inventory.items.find(i => i.itemType === 'Seed' && (i.quantity > 0 || i.count > 0)); if (firstSeed) { // Extract species name similar to ui.js logic if (firstSeed.species) seedToPlant = firstSeed.species; else if (firstSeed.parameters && firstSeed.parameters.species) seedToPlant = firstSeed.parameters.species; else if (firstSeed.parameters && firstSeed.parameters.speciesIds) { const vals = Object.values(firstSeed.parameters.speciesIds); if (vals.length > 0) seedToPlant = vals[0]; } } } if (seedToPlant) { // Find empty slot // We'll plant ONE seed per tick to avoid flooding and ensuring state sync 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) { MB.sendMsg({ type: "PlantSeed", slot: emptySlot, species: seedToPlant, scopePath: ["Room", "Quinoa"] }); } } } // 3. Auto Sell if (MB.automation.autoSell) { // Check if we have any produce? For now, just trigger SellAll periodically. // Just trigger it every loop? Might be too much. // Let's check inventory for "Produce" if (MB.state.inventory && MB.state.inventory.items) { const hasProduce = MB.state.inventory.items.some(i => i.itemType === 'Produce'); if (hasProduce) { MB.sellAll(); } } } // 4. Auto Buy if (MB.automation.autoBuyItems.size > 0 && MB.state.shops && MB.state.shops.seed) { MB.state.shops.seed.inventory.forEach(item => { if (MB.automation.autoBuyItems.has(item.species)) { // Check stock 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); if (currentStock > 0) { // Buy ONE MB.sendMsg({ scopePath: ["Room", "Quinoa"], type: "PurchaseSeed", species: item.species }); console.log(`[MagicBot] Auto-buying ${item.species}`); } } }); } }, 1000); // Run every 1 second // --- HELPER FUNCTIONS --- MB.feedPets = function () { if (!MB.state.inventory || !MB.state.inventory.items) return; const inventory = MB.state.inventory.items; const pets = inventory.filter(i => i.itemType === 'Pet'); if (pets.length === 0) return; // Group food by species for easy lookup const foodMap = {}; inventory.forEach(i => { if (i.itemType === 'Produce' || i.itemType === 'Seed' || i.itemType === 'Plant') { // Assuming crops are Produce, but could be Plant? Wiki says "crops". Usually 'Produce' in inventory. // Double check itemType for crops in inventory. // In fullstate.json, crops aren't explicitly shown in inventory in the snippet, but seeds are. // Assuming harvested crops are 'Produce'. // We will check matches against petDiet. let species = i.species; // Fallback for complex objects if (!species && i.parameters && i.parameters.species) species = i.parameters.species; if (species && (i.quantity > 0 || i.count > 0)) { if (!foodMap[species]) foodMap[species] = []; foodMap[species].push(i); } } }); pets.forEach(pet => { // Check hunger? // "hunger": 405.555... This likely means current fullness or hunger? // Wiki says "restores hunger". // If it's 0-100 scale, 405 is odd. Maybe it's max hunger? // Or maybe it's "hunger points" where higher = more hungry? // Or higher = more full? // Let's assume we feed if we can. The server will reject if full. if (MB.automation.petDiet.size === 0) return; // Find valid food for (const allowedSpecies of MB.automation.petDiet) { if (foodMap[allowedSpecies]) { const foodItems = foodMap[allowedSpecies]; // Use the first available stack const foodItem = foodItems[0]; if (foodItem) { // Send Feed Command // We need the proper message structure. // User request: {"scopePath":["Room","Quinoa"],"type":"FeedPet","petItemId":"...","cropItemId":"..."} console.log(`[MagicBot] Feeding ${pet.petSpecies} with ${allowedSpecies}`); MB.sendMsg({ scopePath: ["Room", "Quinoa"], // value from user request, should be dynamic? but hardcoded in other places in this file. "Quinoa" seems to be the game instance. type: "FeedPet", petItemId: pet.id, cropItemId: foodItem.id }); // Optimistically decrement count to prevent double usage in same tick (if we loop)? // But we are sending async. // Just one food per pet per tick is safer to avoid spam. break; } } } }); }; // Override sellAll to chain feeding const originalSellAll = MB.sellAll || (() => { }); MB.sellAll = function () { if (MB.automation.autoFeed) { MB.feedPets(); // Small delay? Or just send immediately after? // WebSocket is ordered. } // originalSellAll implementation wasn't defined in the file view I saw, // but 'MB.sellAll()' was called in line 131. // It must be defined somewhere or attached to MB. // Wait, line 85 of browser_cheats.js defines window.sell. // line 184 of ui.js calls MB.sellAll(). // I don't see MB.sellAll defined in core.js or main.js PROPERLY. // Let's look at ui.js again. // ui.js calls `MB.sellAll()`. // check `browser_cheats.js` - it defines `window.sell`. // `main.js` calls `MB.sellAll()`. // Detailed check: // core.js defines MB = {...}. // main.js uses MB. // ui.js uses MB. // I suspect MB.sellAll is NOT defined yet or I missed it in `main.js` or `core.js` or `ui.js`. // Ah, `ui.js` line 184 calls it. // `main.js` line 131 calls it. // If it's not defined, it will crash. // Let's define it if missing. if (!MB.state.socket && window.gameSocket) { // Fallback to browser cheats style if needed } MB.sendMsg({ type: 'SellAllCrops', scopePath: ["Room", "Quinoa"] }); console.log("[MagicBot] Selling all crops"); console.log("[MagicBot] Selling all crops"); }; function getWorldEvents(state) { const defaults = { Time_Until_Next_Event_Hrs: 0.5, // Fallback P_Next_Rain_Thunderstorm: 0.0, P_Next_Frost: 0.0, Time_To_Next_Blood_Moon_Hrs: 999, // Unknown for now Event_Active: false }; if (!state || !MB.automation.weatherTracker) return defaults; const tracker = MB.automation.weatherTracker; const currentWeather = tracker.currentEvent; // Logic: 20-35 mins interval (Avg 27.5m) // Rain 75%, Frost 25% const AVG_INTERVAL_MS = 27.5 * 60 * 1000; let timeUntilNextHrs = 0; let pRain = 0; let pFrost = 0; let active = false; if (currentWeather) { // Event is ACTIVE active = true; // If active, we are IN the weather. // Probabilities for "Next Event types" are effectively 1.0 for the current type for short-term planning? if (currentWeather.includes('Rain') || currentWeather.includes('Thunder')) { pRain = 1.0; } else if (currentWeather.includes('Frost') || currentWeather.includes('Snow')) { pFrost = 1.0; } // Planning Horizon: 5 mins (0.083 hrs) to accumulate mutation checks timeUntilNextHrs = 0.083; } else { // Clear Sky - Waiting for Next Event const nextEventTime = tracker.lastEndTime + AVG_INTERVAL_MS; const diffMs = nextEventTime - Date.now(); timeUntilNextHrs = Math.max(0, diffMs) / 3600000; // Hours // If we heavily overdue (e.g. > 35 mins), probability increases? // For V1, fixed probs: pRain = 0.75; pFrost = 0.25; } return { Time_Until_Next_Event_Hrs: timeUntilNextHrs, P_Next_Rain_Thunderstorm: pRain, P_Next_Frost: pFrost, Time_To_Next_Blood_Moon_Hrs: 999, Event_Active: active }; } })();