Files
2025-12-09 23:21:09 +00:00

174 lines
5.8 KiB
JavaScript

(function () {
const MB = window.MagicBot;
// Store the raw full state to apply patches against
let rawState = null;
MB.on('packet_received', (msg) => {
// Log to discover flow
if (msg.type !== 'Ping' && msg.type !== 'Pong' && msg.type !== 'PartialState') {
console.log(`[MagicBot] RX: ${msg.type}`, Object.keys(msg));
}
// --- DATA COLLECTION STREAM ---
if (msg.type !== 'Ping' && msg.type !== 'Pong') { // Stream everything non-heartbeat
fetch('http://localhost:5454', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type: msg.type, payload: msg })
}).catch(e => console.warn('[MagicBot] Data Stream Error:', e));
}
// ------------------------------
if (msg.type === 'Welcome') {
handleWelcome(msg);
} else if (msg.type === 'State') {
handleState(msg);
} else if (msg.type === 'PartialState') {
handlePartialState(msg);
}
});
function handleWelcome(msg) {
console.log("[MagicBot] Parsing Welcome...");
rawState = msg.fullState.data; // Store raw root
// Fix: Explicitly attach child (Quinoa/Game Scope) because 'data' doesn't contain it
if (msg.fullState.child) {
rawState.child = msg.fullState.child;
}
// Initial parse
parseRawState();
}
function handleState(msg) {
console.log("[MagicBot] Handling State message...", msg);
// Replace or merge? Usually 'State' is a full dump of a specific scope.
// For simplicity, we might just re-process.
// CAUTION: If it's just 'Quinoa' data, we attach it to rawState.child.data?
// Let's assume it updates the relevant part of rawState if we knew where it fits.
// For now, let's just try to process it directly.
const data = msg.data || msg.state || msg;
if (msg.scope === 'Quinoa' || (data.scope === 'Quinoa')) {
// It's the game state!
if (!rawState.child) rawState.child = {};
rawState.child.data = data.data || data;
parseRawState();
}
}
function handlePartialState(msg) {
if (!rawState) return;
// Apply patches to rawState
if (msg.patches && Array.isArray(msg.patches)) {
// console.log(`[MagicBot] Applying ${msg.patches.length} patches...`);
applyPatches(rawState, msg.patches);
// Re-eval the simplified state
parseRawState();
}
}
function parseRawState() {
if (!rawState) return;
const root = rawState;
// 1. Players
if (root.players) MB.state.players = root.players;
// 2. PlayerID
if (!MB.state.playerId && root.hostPlayerId) {
MB.state.playerId = root.hostPlayerId;
}
// 3. Game Data
let gameData = null;
if (root.child && root.child.data) {
gameData = root.child.data;
}
if (gameData) {
processGameData(gameData);
} else if (!MB.state.players) {
// Only warn if we have NOTHING
console.warn("[MagicBot] State looks empty.");
}
MB.emit('state_updated', MB.state);
}
function processGameData(gameData) {
if (gameData.shops) MB.state.shops = gameData.shops;
if (gameData.timer) MB.state.timer = gameData.timer;
if (gameData.userSlots && Array.isArray(gameData.userSlots)) {
const userSlot = gameData.userSlots.find(s => s.playerId === MB.state.playerId);
if (userSlot) {
MB.state.coins = userSlot.data.coinsCount;
MB.state.garden = userSlot.data.garden;
MB.state.inventory = userSlot.data.inventory;
MB.state.shopPurchases = userSlot.data.shopPurchases;
MB.state.self = userSlot;
} else {
console.warn("[MagicBot] State: Could not find user slot for self:", MB.state.playerId);
}
} else {
console.warn("[MagicBot] State: userSlots missing or invalid in gameData");
}
}
// --- JSON PATCH UTILS ---
function applyPatches(doc, patches) {
patches.forEach(patch => {
try {
applyOperation(doc, patch);
} catch (e) {
console.warn("[MagicBot] Patch failed:", e, patch);
}
});
}
function applyOperation(doc, patch) {
const path = patch.path.split('/').filter(p => p.length > 0);
let current = doc;
// Navigate to parent
for (let i = 0; i < path.length - 1; i++) {
const key = path[i];
if (current[key] === undefined) {
if (patch.op === 'add') current[key] = {}; // Auto-create path for add?
else return; // Path doesn't exist
}
current = current[key];
}
const lastKey = path[path.length - 1];
switch (patch.op) {
case 'add':
case 'replace':
if (Array.isArray(current) && !isNaN(lastKey)) {
// Array set/insert
const idx = parseInt(lastKey);
if (patch.op === 'add') current.splice(idx, 0, patch.value);
else current[idx] = patch.value;
} else {
current[lastKey] = patch.value;
}
break;
case 'remove':
if (Array.isArray(current)) {
current.splice(parseInt(lastKey), 1);
} else {
delete current[lastKey];
}
break;
}
}
console.log('[Magic Bot Extension] State module loaded.');
})();