Compare commits

..

1 Commits

Author SHA1 Message Date
Scratch-hv | Oeil-de-Lynx
b23cc18a82
Merge dac4cf74ae into b2c577ad4f 2024-06-24 11:42:34 +02:00

View File

@ -6,14 +6,25 @@
// @downloadURL https://raw.githubusercontent.com/FlamedDogo99/EaglerMobile/main/eaglermobile.user.js
// @license Apache License 2.0 - http://www.apache.org/licenses/
// @match https://eaglercraft.com/mc/*
// @version 3.0.5b
// @version 3.0.5
// @updateURL https://raw.githubusercontent.com/FlamedDogo99/EaglerMobile/main/eaglermobile.user.js
// @run-at document-start
// @grant none
// @grant unsafeWindow
// ==/UserScript==
// Removed brainless unsafeWindow
console.log("Eagler Mobile v3.0.5b")
// This is generally a bad practice, but we need to run scripts in the main context before the DOM loads. Because we are only matching eaglercraft.com, unsafeWindow should be safe to use.
// If someone knows a better way of doing this, please create an issue
try {
unsafeWindow.console.warn("DANGER: This userscript is using unsafeWindow. Unsafe websites could potentially use this to gain access to data and other content that the browser normally wouldn't allow!")
Object.defineProperty(window, "clientWindow", {
value: unsafeWindow
}); // If this is a userscript, use unsafeWindow
} catch {
Object.defineProperty(window, "clientWindow", {
value: window
}); // If this is plain javascript, use window
}
clientWindow.console.log("Eagler Mobile v3.0.4")
// TODO: remove the mobile check is implement the dynamic enabling and disabling of individual features
function isMobile() {
try {
@ -27,13 +38,13 @@ if(!isMobile()) {
alert("WARNING: This script was created for mobile, and may break functionality in non-mobile browsers!");
}
// TODO: consolidate all of these into a single object?
window.crouchLock = false; // Used for crouch mobile control
window.sprintLock = false; // Used for sprint mobile control
window.keyboardFix = false; // keyboardFix ? "Standard Keyboard" : "Compatibility Mode"
window.inputFix = false; // If true, Duplicate Mode
window.blockNextInput = false; // Used for Duplicate Mode
window.hiddenInputFocused = false; // Used for keyboard display on mobile
window.canvasTouchMode = 0; // Used for canvas touch handling
clientWindow.crouchLock = false; // Used for crouch mobile control
clientWindow.sprintLock = false; // Used for sprint mobile control
clientWindow.keyboardFix = false; // keyboardFix ? "Standard Keyboard" : "Compatibility Mode"
clientWindow.inputFix = false; // If true, Duplicate Mode
clientWindow.blockNextInput = false; // Used for Duplicate Mode
clientWindow.hiddenInputFocused = false; // Used for keyboard display on mobile
clientWindow.canvasTouchMode = 0; // Used for canvas touch handling
/*
0 Idle
1 Touch initiated
@ -42,12 +53,12 @@ window.canvasTouchMode = 0; // Used for canvas touch handling
4 Scroll
5 Finished
*/
window.canvasTouchStartX = null;
window.canvasTouchStartY = null;
window.canvasTouchPreviousX = null;
window.canvasTouchPreviousY = null;
window.canvasPrimaryID = null;
window.buttonTouchStartX = null;
clientWindow.canvasTouchStartX = null;
clientWindow.canvasTouchStartY = null;
clientWindow.canvasTouchPreviousX = null;
clientWindow.canvasTouchPreviousY = null;
clientWindow.canvasPrimaryID = null;
clientWindow.buttonTouchStartX = null;
// charCodeAt is designed for unicode characters, and doesn't match the behavior of the keyCodes used by KeyboardEvents, thus necessitating this function
String.prototype.toKeyCode = function() {
@ -61,7 +72,7 @@ Object.defineProperty(EventTarget.prototype, "addEventListener", {
value: function (type, fn, ...rest) {
if(type == 'keydown') { // Check if a keydown event is being added
_addEventListener.call(this, type, function(...args) {
if(args[0].isTrusted && window.keyboardFix) { // When we are in compatibility mode, we ignore all trusted keyboard events
if(args[0].isTrusted && clientWindow.keyboardFix) { // When we are in compatibility mode, we ignore all trusted keyboard events
return;
}
return fn.apply(this, args); // Appends the rest of the function specified by addEventListener
@ -91,7 +102,7 @@ function keyEvent(name, state) {
"keyCode": charCode,
"which": charCode
});
window.dispatchEvent(evt);
clientWindow.dispatchEvent(evt);
}
function mouseEvent(number, state, element, event = {"clientX": 0, "clientY" : 0, "screenX": 0, "screenY": 0}) {
element.dispatchEvent(new PointerEvent(state, {
@ -115,12 +126,12 @@ function setButtonVisibility(pointerLocked) {
inMenuStyle.disabled = !pointerLocked;
}
// POINTERLOCK
// When requestpointerlock is called, this dispatches an event, saves the requested element to window.fakelock, and unhides the touch controls
window.fakelock = null;
// When requestpointerlock is called, this dispatches an event, saves the requested element to clientWindow.fakelock, and unhides the touch controls
clientWindow.fakelock = null;
Object.defineProperty(Element.prototype, "requestPointerLock", {
value: function() {
window.fakelock = this
clientWindow.fakelock = this
document.dispatchEvent(new Event('pointerlockchange'));
setButtonVisibility(true);
return true
@ -128,16 +139,16 @@ Object.defineProperty(Element.prototype, "requestPointerLock", {
});
// Makes pointerLockElement return window.fakelock
// Makes pointerLockElement return clientWindow.fakelock
Object.defineProperty(Document.prototype, "pointerLockElement", {
get: function() {
return window.fakelock;
return clientWindow.fakelock;
}
});
// When exitPointerLock is called, this dispatches an event, clears the
Object.defineProperty(Document.prototype, "exitPointerLock", {
value: function() {
window.fakelock = null
clientWindow.fakelock = null
document.dispatchEvent(new Event('pointerlockchange'));
setButtonVisibility(false);
return true
@ -145,23 +156,23 @@ Object.defineProperty(Document.prototype, "exitPointerLock", {
});
// FULLSCREEN
window.fakefull = null;
clientWindow.fakefull = null;
// Stops the client from crashing when fullscreen is requested
Object.defineProperty(Element.prototype, "requestFullscreen", {
value: function() {
window.fakefull = this
clientWindow.fakefull = this
document.dispatchEvent(new Event('fullscreenchange'));
return true
}
});
Object.defineProperty(document, "fullscreenElement", {
get: function() {
return window.fakefull;
return clientWindow.fakefull;
}
});
Object.defineProperty(Document.prototype, "exitFullscreen", {
value: function() {
window.fakefull = null
clientWindow.fakefull = null
document.dispatchEvent(new Event('fullscreenchange'));
return true
}
@ -181,7 +192,7 @@ document.createElement = function(type, ignore) {
element.hidden = true;
element.style.display = "none";
}, {passive: false, once: true});
window.addEventListener('focus', function(e) {
clientWindow.addEventListener('focus', function(e) {
setTimeout(() => {
element.hidden = true;
element.style.display = "none";
@ -244,29 +255,29 @@ function insertCanvasElements() {
// Translates touchmove events to mousemove events when inGame, and touchmove events to wheele events when inMenu
var canvas = document.querySelector('canvas');
canvas.addEventListener("touchstart", function(e) {
if(window.canvasTouchMode < 2) { // If a touch is initiated but not assigned
if(window.canvasPrimaryID == null) {
window.canvasTouchMode = 1;
if(clientWindow.canvasTouchMode < 2) { // If a touch is initiated but not assigned
if(clientWindow.canvasPrimaryID == null) {
clientWindow.canvasTouchMode = 1;
const primaryTouch = e.changedTouches[0];
window.canvasPrimaryID = primaryTouch.identifier
clientWindow.canvasPrimaryID = primaryTouch.identifier
canvasTouchStartX = primaryTouch.clientX;
canvasTouchStartY = primaryTouch.clientY;
canvasTouchPreviousX = canvasTouchStartX
canvasTouchPreviousY = canvasTouchStartY
window.touchTimer = setTimeout(function(e) {
clientWindow.touchTimer = setTimeout(function(e) {
// If our touch is still set to initiaited, set it to secondary touch
if(window.canvasTouchMode == 1) {
window.canvasTouchMode = 3;
if(clientWindow.canvasTouchMode == 1) {
clientWindow.canvasTouchMode = 3;
mouseEvent(2, "mousedown", canvas, primaryTouch)
if(window.fakelock) { // We only dispatch mouseup inGame because we want to be able to click + drag items in GUI's
if(clientWindow.fakelock) { // We only dispatch mouseup inGame because we want to be able to click + drag items in GUI's
mouseEvent(2, "mouseup", canvas, primaryTouch)
}
}
}, 300);
} else if(window.canvasTouchMode == 1 && !window.fakelock) { // If we already have a primary touch, it means we're using two fingers
window.canvasTouchMode = 4;
clearTimeout(window.crouchTimer); // TODO: Find out why this isn't redudnant
} else if(clientWindow.canvasTouchMode == 1 && !clientWindow.fakelock) { // If we already have a primary touch, it means we're using two fingers
clientWindow.canvasTouchMode = 4;
clearTimeout(clientWindow.crouchTimer); // TODO: Find out why this isn't redudnant
}
}
}, false);
@ -275,7 +286,7 @@ function insertCanvasElements() {
e.preventDefault() // Prevents window zoom when using two fingers
var primaryTouch;
for (let touchIndex = 0; touchIndex < e.targetTouches.length; touchIndex++) { // Iterate through our touches to find a touch event matching the primary touch ID
if(e.targetTouches[touchIndex].identifier == window.canvasPrimaryID) {
if(e.targetTouches[touchIndex].identifier == clientWindow.canvasPrimaryID) {
primaryTouch = e.targetTouches[touchIndex];
break;
}
@ -286,16 +297,16 @@ function insertCanvasElements() {
primaryTouch.squaredNorm = (primaryTouch.distanceX * primaryTouch.distanceX) + (primaryTouch.distanceY * primaryTouch.distanceY);
primaryTouch.movementX = primaryTouch.clientX - canvasTouchPreviousX;
primaryTouch.movementY = primaryTouch.clientY - canvasTouchPreviousY;
if(window.canvasTouchMode == 1) { // If the primary touch is still only initiated
if(clientWindow.canvasTouchMode == 1) { // If the primary touch is still only initiated
if (primaryTouch.squaredNorm > 25) { // If our touch becomes a touch + drag
clearTimeout(window.crouchTimer);
window.canvasTouchMode = 2;
if(!window.fakelock) { // When we're inGame, we don't want to be placing blocks when we are moving the camera around
clearTimeout(clientWindow.crouchTimer);
clientWindow.canvasTouchMode = 2;
if(!clientWindow.fakelock) { // When we're inGame, we don't want to be placing blocks when we are moving the camera around
mouseEvent(1, "mousedown", canvas, primaryTouch);
}
}
} else { // If our touch is primary, secondary, scroll or finished
if(window.canvasTouchMode == 4) { // If our touch is scrolling
if(clientWindow.canvasTouchMode == 4) { // If our touch is scrolling
wheelEvent(canvas, primaryTouch.movementY)
} else {
canvas.dispatchEvent(new MouseEvent("mousemove", {
@ -315,27 +326,27 @@ function insertCanvasElements() {
function canvasTouchEnd(e) {
for(let touchIndex = 0; touchIndex < e.changedTouches.length; touchIndex++) { // Iterate through changed touches to find primary touch
if(e.changedTouches[touchIndex].identifier == window.canvasPrimaryID) {
if(e.changedTouches[touchIndex].identifier == clientWindow.canvasPrimaryID) {
let primaryTouch = e.changedTouches[touchIndex]
// When any of the controlling fingers go away, we want to wait until we aren't receiving any other touch events
if(window.canvasTouchMode == 2) {
if(clientWindow.canvasTouchMode == 2) {
mouseEvent(1, "mouseup", canvas, primaryTouch)
} else if (window.canvasTouchMode == 3) {
} else if (clientWindow.canvasTouchMode == 3) {
e.preventDefault(); // This prevents some mobile devices from dispatching a mousedown + mouseup event after a touch is ended
mouseEvent(2, "mouseup", canvas, primaryTouch)
}
window.canvasTouchMode = 5;
clientWindow.canvasTouchMode = 5;
}
}
if(e.targetTouches.length == 0) { // We want to wait until all fingers are off the canvas before we reset for the next cycle
window.canvasTouchMode = 0;
window.canvasPrimaryID = null;
clientWindow.canvasTouchMode = 0;
clientWindow.canvasPrimaryID = null;
}
}
canvas.addEventListener("touchend", canvasTouchEnd, false);
canvas.addEventListener("touchcancel", canvasTouchEnd, false); // TODO: Find out why this is different than touchend
setButtonVisibility(window.fakelock != null); //Updates our mobile controls when the canvas finally loads
setButtonVisibility(clientWindow.fakelock != null); //Updates our mobile controls when the canvas finally loads
// All of the touch buttons
let strafeRightButton = createTouchButton("strafeRightButton", "inGame", "div");
strafeRightButton.style.cssText = "left:20vh;bottom:20vh;"
@ -360,13 +371,13 @@ function insertCanvasElements() {
buttonTouchStartX = touch.pageX;
}
let movementX = touch.pageX - buttonTouchStartX;
if((movementX * 10) > window.innerHeight) {
if((movementX * 10) > clientWindow.innerHeight) {
strafeRightButton.classList.add("active");
strafeLeftButton.classList.remove("active");
keyEvent("d", "keydown");
keyEvent("a", "keyup");
} else if ((movementX * 10) < (0 - window.innerHeight)) {
} else if ((movementX * 10) < (0 - clientWindow.innerHeight)) {
strafeLeftButton.classList.add("active");
strafeRightButton.classList.remove("active");
keyEvent("a", "keydown");
@ -419,20 +430,20 @@ function insertCanvasElements() {
crouchButton.style.cssText = "left:10vh;bottom:10vh;"
crouchButton.addEventListener("touchstart", function(e){
keyEvent("shift", "keydown")
window.crouchLock = window.crouchLock ? null : false
window.crouchTimer = setTimeout(function(e) { // Allows us to lock the button after a long press
window.crouchLock = (window.crouchLock != null);
clientWindow.crouchLock = clientWindow.crouchLock ? null : false
clientWindow.crouchTimer = setTimeout(function(e) { // Allows us to lock the button after a long press
clientWindow.crouchLock = (clientWindow.crouchLock != null);
crouchButton.classList.toggle('active');
}, 1000);
}, false);
crouchButton.addEventListener("touchend", function(e) {
if(!window.crouchLock) {
if(!clientWindow.crouchLock) {
keyEvent("shift", "keyup")
crouchButton.classList.remove('active');
window.crouchLock = false
clientWindow.crouchLock = false
}
clearTimeout(window.crouchTimer);
clearTimeout(clientWindow.crouchTimer);
}, false);
document.body.appendChild(crouchButton);
let inventoryButton = createTouchButton("inventoryButton", "inGame");
@ -467,7 +478,7 @@ function insertCanvasElements() {
// Additionally, programmatically setting the input's text contents (BECAUSE ANDROID IGNORES PREVENTDEFAULT AGHHHHH) dispatches a repeat of the previous event
// Luckily, we can check if this happens when we first create the input, which necessitates the third mode:
// 3) Duplicate mode:
// If we are getting duplicate inputs, this mode ignores every other input if it matches the state saved in window.previousKey
// If we are getting duplicate inputs, this mode ignores every other input if it matches the state saved in clientWindow.previousKey
// If users make it to this mode and still are having issues, it may be best just to remove support for their device
// ---Input Handling---
let hiddenInput = document.createElement('input', true);
@ -478,20 +489,20 @@ function insertCanvasElements() {
e.stopImmediatePropagation(); // Android ignores this and the prevent default, so this will probably be removed in the future
e.preventDefault(true); // We pass a value because we've hijacked the prevent default function to have a "should bypass" boolean value
let inputData = (e.inputType == "insertLineBreak") ? "return" : (e.data == null ? "delete" : e.data.slice(-1)); // Saves the last key press.
if(!window.lastKey) { // When we first set hiddenInput's text contents to " " we can use this to check if Duplicate Mode is needed
window.console.warn("Enabling blocking duplicate key events. Some functionality may be lost.")
window.inputFix = true;
if(!clientWindow.lastKey) { // When we first set hiddenInput's text contents to " " we can use this to check if Duplicate Mode is needed
clientWindow.console.warn("Enabling blocking duplicate key events. Some functionality may be lost.")
clientWindow.inputFix = true;
}
if(window.keyboardFix) {
if(clientWindow.keyboardFix) {
if(e.inputType == "insertLineBreak") { // Detects return key press
keyEvent("enter", "keydown");
keyEvent("enter", "keyup");
} else {
const sliceInputType = e.inputType.slice(0,1); // Android doesn't constiently dispatch the correct inputType, but most of them either start with i for insert or d for delete, so this dumb solution should be good enough.
if(sliceInputType== 'i' && e.data) { // Android sometimes always dispatches insertCompositionText inputTypes, so checking that e.data isn't null is necessary
const isDuplicate = (window.lastKey == inputData) && window.blockNextInput && window.inputFix;
const isDuplicate = (clientWindow.lastKey == inputData) && clientWindow.blockNextInput && clientWindow.inputFix;
if(isDuplicate) { // If our key press matches the last unblocked key press and we are in duplicaye mode, ignore the input
window.blockNextInput = false;
clientWindow.blockNextInput = false;
} else {
let isShift = (inputData.toLowerCase() != inputData);
if(isShift) { // The Eaglerclient only uses e.key, e.keyCode and e.which, so we have to dispatch the shift key event separately
@ -503,16 +514,16 @@ function insertCanvasElements() {
keyEvent(inputData, "keydown");
keyEvent(inputData, "keyup");
}
window.blockNextInput = true;
clientWindow.blockNextInput = true;
}
} else if (sliceInputType == 'd' || !e.data) {
keyEvent("backspace", "keydown");
keyEvent("backspace", "keyup");
window.blockNextInput = false; // If we delete a character, there couldn't be a duplicate of the previous key press
clientWindow.blockNextInput = false; // If we delete a character, there couldn't be a duplicate of the previous key press
}
}
}
window.lastKey = inputData // Saves the last key pressed
clientWindow.lastKey = inputData // Saves the last key pressed
hiddenInput.value = " " //This previously allowed us to have a character to delete, but beforeinput doesn't require this. This does allow us to check wether Duplicate Mode is necessary though
@ -523,17 +534,17 @@ function insertCanvasElements() {
}
}, false);
hiddenInput.addEventListener("keydown", function(e) { // Enables Compatibility Mode if we receive an invalid key press event
if((e.keyCode == 229 || e.which == 229) && !window.keyboardFix) {
window.console.warn("Switching from keydown to input events due to invalid KeyboardEvent. Some functionality will be lost.")
window.keyboardFix = true;
if(window.lastKey) { // Resend the last saved key press (which is being tracked by the beforeinput event listener) so the transition to Compatibility Mode isn't noticeable
keyEvent(window.lastKey, "keydown");
keyEvent(window.lastKey, "keyup");
if((e.keyCode == 229 || e.which == 229) && !clientWindow.keyboardFix) {
clientWindow.console.warn("Switching from keydown to input events due to invalid KeyboardEvent. Some functionality will be lost.")
clientWindow.keyboardFix = true;
if(clientWindow.lastKey) { // Resend the last saved key press (which is being tracked by the beforeinput event listener) so the transition to Compatibility Mode isn't noticeable
keyEvent(clientWindow.lastKey, "keydown");
keyEvent(clientWindow.lastKey, "keyup");
}
}
}, false);
hiddenInput.addEventListener("blur", function(e) { // Updates window.hiddenInputFocused to reflect the actual state of the focus
window.hiddenInputFocused = false;
hiddenInput.addEventListener("blur", function(e) { // Updates clientWindow.hiddenInputFocused to reflect the actual state of the focus
clientWindow.hiddenInputFocused = false;
});
document.body.appendChild(hiddenInput);
let keyboardButton = createTouchButton("keyboardButton", "inMenu");
@ -543,11 +554,11 @@ function insertCanvasElements() {
}, false);
keyboardButton.addEventListener("touchend", function(e){
e.preventDefault();
if(window.hiddenInputFocused) {
if(clientWindow.hiddenInputFocused) {
hiddenInput.blur()
} else {
hiddenInput.select()
window.hiddenInputFocused = true;
clientWindow.hiddenInputFocused = true;
}
}, false);
document.body.appendChild(keyboardButton);
@ -583,20 +594,20 @@ function insertCanvasElements() {
sprintButton.style.cssText = "right:0vh;bottom:10vh;"
sprintButton.addEventListener("touchstart", function(e) {
keyEvent("r", "keydown");
window.sprintLock = window.sprintLock ? null : false
window.sprintTimer = setTimeout(function(e) {
window.sprintLock = (window.sprintLock != null);
clientWindow.sprintLock = clientWindow.sprintLock ? null : false
clientWindow.sprintTimer = setTimeout(function(e) {
clientWindow.sprintLock = (clientWindow.sprintLock != null);
sprintButton.classList.toggle('active');
}, 1000);
}, false);
sprintButton.addEventListener("touchend", function(e) {
if(!window.sprintLock) {
if(!clientWindow.sprintLock) {
keyEvent("r", "keyup");
sprintButton.classList.remove('active');
window.sprintLock = false
clientWindow.sprintLock = false
}
clearTimeout(window.sprintTimer);
clearTimeout(clientWindow.sprintTimer);
}, false);
document.body.appendChild(sprintButton);
let pauseButton = createTouchButton("pauseButton", "inGame");