// ==UserScript== // @name DiepIOSyncControl // @description Diep.io // @version 2.0.2 // @author tampermonkey // @include https://diep.io/* // @connect diep.io // @run-at document-start // @namespace tampermonkey // ==/UserScript== /** * This script is an example of Broadcast Channel API for syncing user actions accross tabs. * It could be tested on dipe.io online game. Allowing to control multiple units together of different tabs. 'Tampermokey' browser extension could be used to load the script automatically. */ const screenConstant = getScreenConstant(); // diep.io specific values function getScreenConstant({ width = window.innerWidth, height = window.innerHeight } = {}) { if(width > height * 16 / 9) { return width; } return height * 16 / 9; } // diep.io canvas dimension logic as example for syncing movements accross tabs with different viewport width and height - copied from https://github.com/BE1A/48666/blob/master/DiepBox.user.js function GetCoordClamp(mouseX, mouseY) { let ret = {}; let hsx = window.innerWidth / 2; let hsy = window.innerHeight / 2; let amx = mouseX - hsx; let amy = mouseY - hsy; let dc = 0.96; if((amx > -hsx * dc) && (amx < hsx * dc) && (amy > -hsy * dc) && (amy < hsy * dc)) { ret[0] = mouseX; ret[1] = mouseY; return ret; } else { let fA = (amx * hsy) - (amy * hsx); let fB = (amx * hsy) + (amy * hsx); if(fA > 0) { if(fB > 0) { amy = amy * (hsx * dc / amx); amx = hsx * dc; } else { amx = amx * (-hsy * dc / amy); amy = -hsy * dc; } } else { if(fB > 0) { amx = amx * (hsy * dc / amy); amy = hsy * dc; } else { amy = amy * (-hsx * dc / amx); amx = -hsx * dc; } } ret[0] = amx + hsx; ret[1] = amy + hsy; } return ret; } // here are used specific calculations of viewport dimension differences between message sender and receiver tabs, taking into account specific diep.io canvas values. function simulateMouseMove({ type, clientX, clientY, senderContextViewportWidth, senderContextViewportHeight }) { let eventTarget = (typeof canvas !== 'undefined') ? canvas : document; let cX = (clientX / senderContextViewportWidth) * window.innerWidth /* of receiver */; let cY = (clientY / senderContextViewportHeight) * window.innerHeight; let dX = clientX - (senderContextViewportWidth / 2) let dY = clientY - (senderContextViewportHeight / 2) let clamped = GetCoordClamp(dX + window.innerWidth / 2, dY + window.innerHeight / 2); eventTarget.dispatchEvent(new MouseEvent(type, { 'clientX': clamped[0], 'clientY': clamped[1] })); } function simulateKeyPress({ keyCode, type }) { let eventObj; eventObj = document.createEvent("Events"); eventObj.initEvent(type, true, true); eventObj.keyCode = keyCode; window.dispatchEvent(eventObj); } function simulateMousePress({ button, clientX, clientY, type }) { let eventTarget = (typeof canvas !== 'undefined') ? canvas : document; eventTarget.dispatchEvent(new MouseEvent(type, { 'clientX': clientX, 'clientY': clientY, 'button': button, 'mozPressure' : 1.0 })); } function synchronizeControl() { let broadcastChannel = new BroadcastChannel('sharedChannel') let stopBroadcast = false; // stop propagation of control / user event to other tabs // Receive other browsing context action event broadcastChannel.onmessage = ({ data: actionEvent}) => { if(stopBroadcast) return; // don't react to incoming messages. // handle different types switch (actionEvent.type) { case 'mouseup': case 'mousedown': simulateMousePress({ button: actionEvent.button, clientX: actionEvent.clientX, clientY: actionEvent.clientY, type: actionEvent.type }) break; case 'keydown': case 'keyup': simulateKeyPress({ keyCode: actionEvent.keyCode, type: actionEvent.type }) break; case 'mousemove': simulateMouseMove({ type: actionEvent.type, clientX: actionEvent.clientX, clientY: actionEvent.clientY, senderContextViewportWidth: actionEvent.viewportWidth, senderContextViewportHeight: actionEvent.viewportHeight }) break; default: console.log(`Receieved unhandle event type ${actionEvent.type}`) break; } } /* * Add action event listener & broadcast event */ let evenTypeArray = ['keydown', 'keyup', 'mousedown', 'mouseup', 'mousemove'] // event to propagate to other contexts for(let eventType of evenTypeArray) { document.addEventListener(eventType, eventAction => { // check if event triggered by a user and not programmatically (prevent infinite loop) if(!eventAction.isTrusted) return; // console.log(eventAction) // Check for shift key - holding shift key will prevent propagation if(eventAction.key == 'Shift') { switch (eventAction.type) { case 'keydown': stopBroadcast = true console.log('%s %c%s', '🕹️ Propagation', 'color: Red; font-weight: bold;', 'stopped') break; case 'keyup': stopBroadcast = false console.log('%s %c%s', '🕹️ Propagation', 'color: Green; font-weight: bold;', 'active') break; } } // handle '0 key' to toggle broadcast / reacting to messages. if(eventAction.key == '0' && eventAction.type == 'keyup') stopBroadcast = !stopBroadcast // toggle // check propagation condition if(stopBroadcast) return; // Extract properies eventActionObject = { // extract all relevant properties type: eventAction.type, clientX: eventAction.clientX, clientY: eventAction.clientY, button: eventAction.button, keyCode: eventAction.keyCode, which: eventAction.which, shiftKey: eventAction.shiftKey, } if(eventAction.type == 'mousemove') { // calculate dimension only for mousemove to prevent unnecessary work. eventActionObject = Object.assign(eventActionObject, { viewportWidth: window.innerWidth, viewportHeight: window.innerHeight }) } // let eventActionObject = { ...eventAction } // Event properties are propagated to higher prtotype chain // shallow copy, to extract only non nested & non methods values. // let eventActionObject = JSON.parse(JSON.stringify(eventAction)) // because Event has functions, JSON.strigify cannot handle it properly. // Insures all methods/functions are removed, as postMessage accepts only objects. // Propagate current browsing context's action event broadcastChannel.postMessage(eventActionObject) }) } console.group('%c%s', 'color: Green; font-weight: bold;', `🕹️ Synchronizing control to other tabs.`) console.log('%c%s', 'color: Grey;', `• Hold 'Shift key' to temporarly stop propagation of user event.`) console.log('%c%s', 'color: Grey;', `• Click '0 key' to toggle propagation of user event.`) console.groupEnd() } synchronizeControl() // initialize on runtime