Merge pull request #10 from FlamedDogo99/keyboard-fix
Android Keyboard Fallback & Other Improvements
78
README.md
|
@ -1,24 +1,60 @@
|
||||||
# Eagler Mobile
|
# <img src="images/logo128.png" alt="Eagler Mobile Logo" align="right" width="128px"></img>Eagler Mobile
|
||||||
|
|
||||||
## A userscript that allows eaglercraft to run on mobile devices
|
|
||||||
To-do
|
|
||||||
- [ ] Gamepad support
|
|
||||||
- [ ] Cancel button for file upload
|
|
||||||
- [ ] Styling for file upload
|
|
||||||
- [ ] Back button for Kiwi browser?
|
|
||||||
- [ ] Fix keyboard input for Android devices
|
|
||||||
|
|
||||||
Implemented
|
|
||||||
|
|
||||||
- [x] Fake pointerlock API (tricks the client into loading)
|
![](https://img.shields.io/badge/Github-v3.0.1-blue?style=flat-square&logo=github&logoColor=white&label=GitHub&color=181717)
|
||||||
- [x] Fake fullscreen API (so that selecting full screen doesn't crash the client)
|
[![](https://img.shields.io/github/license/FlamedDogo99/EaglerMobile?style=flat-square)](https://github.com/FlamedDogo99/EaglerMobile/blob/master/LICENSE)
|
||||||
- [x] Custom upload button for files (because safari is a pain with button clicks)
|
|
||||||
- [x] Fake mousemove events (Allows touch and drag to simulate mouse movement)
|
## About
|
||||||
- [x] Fake cursor scroll events (Allows scrolling through hotbar and in menus)
|
|
||||||
- [x] On-screen controls (Movement, block placement/removal/picking, inventory, item dropping, keyboard, and exiting keys)
|
Eagler Mobile brings new functionality and benefits for the EaglerCraft web client by providing mobile-friendly touch controls, keyboard access, and other settings configirable through the EaglerCraft client.
|
||||||
- [x] Styling for html, body, and canvas (So that the canvas doesn't ignore the navigation bars for viewport sizing)
|
|
||||||
- [x] Strafe buttons when holding forward
|
<div align="center">
|
||||||
- [x] Crouch lock on hold
|
|
||||||
- [x] Re-orginize button layout
|
![Eagler Mobile Screenshot](images/preview.png)
|
||||||
- [x] Redo the display button functions
|
|
||||||
- [x] Sprint button (sperate from double tapping forward)
|
</div>
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
The easiest way to use Eagler Mobile is to use a userscript application such as Greasemonkey. That being said, because Eagler Mobile is plain JavaScript you can easily deploy it in other ways as well. For instance we've included `eaglermobile.ef.js` which can be run as a mod on [EaglerForge](https://github.com/eaglerforge/EaglerForge).
|
||||||
|
|
||||||
|
If you want to download the source, no building is required. The best way to download the source is with Git:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/FlamedDogo99/EaglerMobile.git
|
||||||
|
```
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
### Suggestions and bug reports
|
||||||
|
|
||||||
|
If you found a bug or have a suggestion [create an issue](https://github.com/FlamedDogo99/EaglerMobile/issues/new/choose) after checking for duplicates.
|
||||||
|
|
||||||
|
|
||||||
|
### Features and documentation
|
||||||
|
#### Fake API's
|
||||||
|
- Pointerlock methods such as `Element.prototype.requestPointerLock`, `document.pointerLockElement`, and `document.exitPointerLock` are replaced with vanilla JavaScript that mimics pointerlock functionality. This allows the EaglerCraft client to load.
|
||||||
|
- Fullscreen methods such as `Element.prototype.requestFullscreen`, `document.fullscreenElement`, and `document.exitFullscreen` are replaced with vanilla JavaScript that mimics fullscreen functionality. This fixes a crash due with viewport dimensions.
|
||||||
|
|
||||||
|
#### Keyboard Events
|
||||||
|
- The EaglerCraft client captures keypress through a `keydown` event listener. Because Android devices currently have an issue with `keydown` and `keyup` events, Eagler Mobile dynamically toggles between capturing `keydown` and `input` events. The state is saved in window.keyboardFix, and is toggled if a faulty keyboard event is detected.
|
||||||
|
- To dispatch keyboard events, Eagler Mobile requires the use of the `keyEvent` function, in order to maintain functionality for `input` event listeners. For example, typing an uppercase `h` in the chat is as simple as:
|
||||||
|
```js
|
||||||
|
keyEvent("shift", "keydown");
|
||||||
|
keyEvent("h", "keydown");
|
||||||
|
```
|
||||||
|
#### Mobile controls
|
||||||
|
- Eagler Mobile controls can either be shown in-game or in-menu. When creating a an element you can achieve this simply by either adding the `inGame` or `inMenu` class.
|
||||||
|
- Simple gesture controls such as scrolling, single pressing, and long pressing have been implemented, however there are currently no functions provided to easily bring this functionality to other elements.
|
||||||
|
|
||||||
|
#### File uploads
|
||||||
|
- On MacOS and iOS safari, the EaglerCraft client's implementation of triggering the file selection dialog does not work. A rudimentary fix has been added for now.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Eagler Mobile is licensed under the terms of the [Apache License, Version 2.0](https://github.com/FlamedDogo99/EaglerMobile/blob/main/LICENSE).
|
||||||
|
|
||||||
|
## Intended future features
|
||||||
|
- [ ] **Gamepad support**: Mapping gamepad inputs to `keyEvent`, `wheelEvent` and `mouseEvent` functions, and implenting a controllable fake cursor for menus.
|
||||||
|
- [ ] **File upload improvements**: Adding a cancel button and improving the styling
|
||||||
|
- [ ] **Dynamic enable and disable of features**: Seperating gamepad controls, touch controls, pointerlock fix, and upload fix into seperate functions which can be enabled and disabled by the user
|
|
@ -1,23 +1,3 @@
|
||||||
// ==UserScript==
|
|
||||||
// @name Eagler Mobile
|
|
||||||
// @description Allows eaglercraft to run on mobile, adds touch controls, and fixes a few mobile-related crashes
|
|
||||||
//
|
|
||||||
// @author FlamedDogo99
|
|
||||||
// @namespace http://github.com/FlamedDogo99
|
|
||||||
// @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 2.1
|
|
||||||
// @updateURL https://raw.githubusercontent.com/FlamedDogo99/EaglerMobile/main/eaglermobile.user.js
|
|
||||||
//
|
|
||||||
// @run-at document-start
|
|
||||||
// @unwrap
|
|
||||||
// ==/UserScript==
|
|
||||||
|
|
||||||
|
|
||||||
function isMobile() {
|
function isMobile() {
|
||||||
try {
|
try {
|
||||||
document.createEvent("TouchEvent");
|
document.createEvent("TouchEvent");
|
||||||
|
@ -29,26 +9,57 @@ function isMobile() {
|
||||||
if(!isMobile()) {
|
if(!isMobile()) {
|
||||||
alert("WARNING: This script was created for mobile, and may break functionality in non-mobile browsers!");
|
alert("WARNING: This script was created for mobile, and may break functionality in non-mobile browsers!");
|
||||||
}
|
}
|
||||||
|
window.keyboardEnabled = false;
|
||||||
window.crouchLock = false;
|
window.crouchLock = false;
|
||||||
window.sprintLock = false;
|
window.sprintLock = false;
|
||||||
|
window.keyboardFix = false; // temporarily set to true until I can figure out whats going wrong with the event listener in charge of switching it
|
||||||
// Used for changing touchmove events to mousemove events
|
// Used for changing touchmove events to mousemove events
|
||||||
var previousTouchX = null;
|
var previousTouchX = null;
|
||||||
var previousTouchY = null;
|
var previousTouchY = null;
|
||||||
var startTouchX = null;
|
var startTouchX = null;
|
||||||
// Key and mouse events
|
// better charCodeAt function
|
||||||
function keyEvent(name, state) {
|
String.prototype.toKeyCode = function() {
|
||||||
const keyName = name.toUpperCase().charCodeAt(0)
|
const keyCodeList = {"0": 48, "1": 49, "2": 50, "3": 51, "4": 52, "5": 53, "6": 54, "7": 55, "8": 56, "9": 57, "backspace": 8, "tab": 9, "enter": 13, "shift": 16, "ctrl": 17, "alt": 18, "pause_break": 19, "caps_lock": 20, "escape": 27, " ": 32, "page_up": 33, "page_down": 34, "end": 35, "home": 36, "left_arrow": 37, "up_arrow": 38, "right_arrow": 39, "down_arrow": 40, "insert": 45, "delete": 46, "a": 65, "b": 66, "c": 67, "d": 68, "e": 69, "f": 70, "g": 71, "h": 72, "i": 73, "j": 74, "k": 75, "l": 76, "m": 77, "n": 78, "o": 79, "p": 80, "q": 81, "r": 82, "s": 83, "t": 84, "u": 85, "v": 86, "w": 87, "x": 88, "y": 89, "z": 90, "left_window_key": 91, "right_window_key": 92, "select_key": 93, "numpad_0": 96, "numpad_1": 97, "numpad_2": 98, "numpad_3": 99, "numpad_4": 100, "numpad_5": 101, "numpad_6": 102, "numpad_7": 103, "numpad_8": 104, "numpad_9": 105, "*": 106, "+": 107, "-": 109, ".": 110, "/": 111, "f1": 112, "f2": 113, "f3": 114, "f4": 115, "f5": 116, "f6": 117, "f7": 118, "f8": 119, "f9": 120, "f10": 121, "f11": 122, "f12": 123, "num_lock": 144, "scroll_lock": 145, ";": 186, "=": 187, ",": 188, "-": 189, ".": 190, "/": 191, "`": 192, "[": 219, "\\": 220, "]": 221, "\"": 222};
|
||||||
window.dispatchEvent(new KeyboardEvent(state, {
|
return keyCodeList[this];
|
||||||
key: name,
|
|
||||||
keyCode: keyName,
|
|
||||||
which: keyName
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
function shiftKeyEvent(state) {
|
// Ignores keydown events that don't have the isValid parameter set to true
|
||||||
window.dispatchEvent(new KeyboardEvent(state, {
|
const _addEventListener = EventTarget.prototype.addEventListener;
|
||||||
keyCode: 16,
|
Object.defineProperty(EventTarget.prototype, "addEventListener", {
|
||||||
which: 16
|
value: function (type, fn, ...rest) {
|
||||||
}));
|
if(type == 'keydown') {
|
||||||
|
_addEventListener.call(this, type, function(...args) {
|
||||||
|
if(!args[0].isValid && window.keyboardFix) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return fn.apply(this, args);
|
||||||
|
}, ...rest);
|
||||||
|
} else {
|
||||||
|
_addEventListener.call(this, type, fn, ...rest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Allows typing in #hiddenInput
|
||||||
|
const _preventDefault = Event.prototype.preventDefault;
|
||||||
|
Event.prototype.preventDefault = function() {
|
||||||
|
if(document.activeElement.id != "hiddenInput") {
|
||||||
|
this._preventDefault = _preventDefault;
|
||||||
|
this._preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Key and mouse events
|
||||||
|
// Note: the client must have the key, keyCode, and which parameters defined or it will crash
|
||||||
|
// Note: for text inputs, the client only reads from the "key" paramater
|
||||||
|
// * an exception to this appears to be the shift and backspace key
|
||||||
|
// Note: for inGame inputs, the client only reads from the "keyCode character"
|
||||||
|
function keyEvent(name, state) {
|
||||||
|
const charCode = name.toKeyCode();
|
||||||
|
let evt = new KeyboardEvent(state, {
|
||||||
|
key: name,
|
||||||
|
keyCode: charCode,
|
||||||
|
which: charCode
|
||||||
|
});
|
||||||
|
evt.isValid = true; // Disables fix for bad keyboard input
|
||||||
|
window.dispatchEvent(evt);
|
||||||
}
|
}
|
||||||
function mouseEvent(number, state, canvas) {
|
function mouseEvent(number, state, canvas) {
|
||||||
canvas.dispatchEvent(new PointerEvent(state, {"button": number}))
|
canvas.dispatchEvent(new PointerEvent(state, {"button": number}))
|
||||||
|
@ -68,55 +79,62 @@ function setButtonVisibility(pointerLocked) {
|
||||||
// When requestpointerlock is called, this dispatches an event, saves the requested element to window.fakelock, and unhides the touch controls
|
// When requestpointerlock is called, this dispatches an event, saves the requested element to window.fakelock, and unhides the touch controls
|
||||||
window.fakelock = null;
|
window.fakelock = null;
|
||||||
|
|
||||||
Element.prototype.requestPointerLock = function() {
|
Object.defineProperty(Element.prototype, "requestPointerLock", {
|
||||||
|
value: function() {
|
||||||
window.fakelock = this
|
window.fakelock = this
|
||||||
document.dispatchEvent(new Event('pointerlockchange'));
|
document.dispatchEvent(new Event('pointerlockchange'));
|
||||||
console.log("requested pointerlock")
|
|
||||||
setButtonVisibility(true);
|
setButtonVisibility(true);
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// Makes pointerLockElement return window.fakelock
|
// Makes pointerLockElement return window.fakelock
|
||||||
Object.defineProperty(document, "pointerLockElement", {
|
Object.defineProperty(Document.prototype, "pointerLockElement", {
|
||||||
get: function() {
|
get: function() {
|
||||||
return window.fakelock;
|
return window.fakelock;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// When exitPointerLock is called, this dispatches an event, clears the
|
// When exitPointerLock is called, this dispatches an event, clears the
|
||||||
document.exitPointerLock = function() {
|
Object.defineProperty(Document.prototype, "exitPointerLock", {
|
||||||
|
value: function() {
|
||||||
window.fakelock = null
|
window.fakelock = null
|
||||||
document.dispatchEvent(new Event('pointerlockchange'));
|
document.dispatchEvent(new Event('pointerlockchange'));
|
||||||
setButtonVisibility(false);
|
setButtonVisibility(false);
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// FULLSCREEN
|
// FULLSCREEN
|
||||||
window.fakefull = null;
|
window.fakefull = null;
|
||||||
// Stops the client from crashing when fullscreen is requested
|
// Stops the client from crashing when fullscreen is requested
|
||||||
Element.prototype.requestFullscreen = function() {
|
Object.defineProperty(Element.prototype, "requestFullscreen", {
|
||||||
|
value: function() {
|
||||||
window.fakefull = this
|
window.fakefull = this
|
||||||
document.dispatchEvent(new Event('fullscreenchange'));
|
document.dispatchEvent(new Event('fullscreenchange'));
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
});
|
||||||
Object.defineProperty(document, "fullscreenElement", {
|
Object.defineProperty(document, "fullscreenElement", {
|
||||||
get: function() {
|
get: function() {
|
||||||
return window.fakefull;
|
return window.fakefull;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
document.exitFullscreen = function() {
|
Object.defineProperty(Document.prototype, "exitFullscreen", {
|
||||||
|
value: function() {
|
||||||
window.fakefull = null
|
window.fakefull = null
|
||||||
document.dispatchEvent(new Event('fullscreenchange'));
|
document.dispatchEvent(new Event('fullscreenchange'));
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// FILE UPLOADING
|
// FILE UPLOADING
|
||||||
// Safari doesn't recognize the element.click() used to display the file uplaoder as an action performed by the user, so it ignores it.
|
// Safari doesn't recognize the element.click() used to display the file uploader as an action performed by the user, so it ignores it.
|
||||||
// This hijacks the element.createElement() function to add the file upload to the DOM, so the user can manually press the button again.
|
// This hijacks the element.createElement() function to add the file upload to the DOM, so the user can manually press the button again.
|
||||||
var oldCreate = document.createElement;
|
const _createElement = document.createElement;
|
||||||
document.createElement = function(type, ignore) {
|
document.createElement = function(type, ignore) {
|
||||||
this.oldCreate = oldCreate;
|
this._createElement = _createElement;
|
||||||
let element = this.oldCreate(type);
|
let element = this._createElement(type);
|
||||||
if(type == "input" && !ignore) {
|
if(type == "input" && !ignore) {
|
||||||
let newElement = document.querySelector('input');
|
let newElement = document.querySelector('input');
|
||||||
if(!newElement) {
|
if(!newElement) {
|
||||||
|
@ -131,7 +149,7 @@ document.createElement = function(type, ignore) {
|
||||||
newElement.hidden = false;
|
newElement.hidden = false;
|
||||||
return newElement;
|
return newElement;
|
||||||
}
|
}
|
||||||
return this.oldCreate(type);
|
return this._createElement(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lazy way to hide touch controls through CSS.
|
// Lazy way to hide touch controls through CSS.
|
||||||
|
@ -179,6 +197,17 @@ function createTouchButton(buttonClass, buttonDisplay, elementName) {
|
||||||
return touchButton;
|
return touchButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleKeyboard() {
|
||||||
|
const keyboardInput = document.getElementById('hiddenInput');
|
||||||
|
if (window.keyboardEnabled) {
|
||||||
|
window.keyboardEnabled = false;
|
||||||
|
keyboardInput.blur();
|
||||||
|
} else {
|
||||||
|
window.keyboardEnabled = true;
|
||||||
|
keyboardInput.select();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
waitForElm('canvas').then(() => {insertCanvasElements()});
|
waitForElm('canvas').then(() => {insertCanvasElements()});
|
||||||
function insertCanvasElements() {
|
function insertCanvasElements() {
|
||||||
// Translates touchmove events to mousemove events when inGame, and touchmove events to wheele events when inMenu
|
// Translates touchmove events to mousemove events when inGame, and touchmove events to wheele events when inMenu
|
||||||
|
@ -288,7 +317,7 @@ function insertCanvasElements() {
|
||||||
let crouchButton = createTouchButton("crouchButton", "inGame");
|
let crouchButton = createTouchButton("crouchButton", "inGame");
|
||||||
crouchButton.style.cssText = "left:10vh;bottom:10vh;"
|
crouchButton.style.cssText = "left:10vh;bottom:10vh;"
|
||||||
crouchButton.addEventListener("touchstart", function(e){
|
crouchButton.addEventListener("touchstart", function(e){
|
||||||
shiftKeyEvent("keydown");
|
keyEvent("shift", "keydown")
|
||||||
window.crouchLock = window.crouchLock ? null : false
|
window.crouchLock = window.crouchLock ? null : false
|
||||||
crouchTimer = setTimeout(function(e) {
|
crouchTimer = setTimeout(function(e) {
|
||||||
window.crouchLock = (window.crouchLock != null);
|
window.crouchLock = (window.crouchLock != null);
|
||||||
|
@ -298,7 +327,7 @@ function insertCanvasElements() {
|
||||||
|
|
||||||
crouchButton.addEventListener("touchend", function(e) {
|
crouchButton.addEventListener("touchend", function(e) {
|
||||||
if(!window.crouchLock) {
|
if(!window.crouchLock) {
|
||||||
shiftKeyEvent("keyup");
|
keyEvent("shift", "keyup")
|
||||||
crouchButton.classList.remove('active');
|
crouchButton.classList.remove('active');
|
||||||
window.crouchLock = false
|
window.crouchLock = false
|
||||||
}
|
}
|
||||||
|
@ -312,18 +341,53 @@ function insertCanvasElements() {
|
||||||
document.body.appendChild(inventoryButton);
|
document.body.appendChild(inventoryButton);
|
||||||
let exitButton = createTouchButton("exitButton", "inMenu");
|
let exitButton = createTouchButton("exitButton", "inMenu");
|
||||||
exitButton.style.cssText = "top: 0vh; margin: auto; left: 0vh; right:8vh; width: 8vh; height: 8vh;"
|
exitButton.style.cssText = "top: 0vh; margin: auto; left: 0vh; right:8vh; width: 8vh; height: 8vh;"
|
||||||
exitButton.addEventListener("touchstart", function(e){keyEvent("À", "keydown")}, false);
|
exitButton.addEventListener("touchstart", function(e){keyEvent("`", "keydown")}, false);
|
||||||
exitButton.addEventListener("touchend", function(e){keyEvent("À", "keyup")}, false);
|
exitButton.addEventListener("touchend", function(e){keyEvent("`", "keyup")}, false);
|
||||||
document.body.appendChild(exitButton);
|
document.body.appendChild(exitButton);
|
||||||
// input for keyboard button
|
// input for keyboard button
|
||||||
let hiddenInput = document.createElement('input', true);
|
let hiddenInput = document.createElement('input', true);
|
||||||
hiddenInput.id = "hiddenInput"
|
hiddenInput.id = "hiddenInput"
|
||||||
hiddenInput.style.cssText = "opacity:0;z-index:-99999";
|
hiddenInput.classList.add("inMenu")
|
||||||
|
// We are hiding the text input behind button because opacity was causing problems.
|
||||||
|
hiddenInput.style.cssText = "position:absolute;top: 0vh; margin: auto; left: 8vh; right:0vh; width: 8vh; height: 8vh;font-size:20px;z-index:-10;color: transparent;text-shadow: 0 0 0 black;";
|
||||||
|
hiddenInput.value = " " //Allows delete to be detected before input is changed
|
||||||
|
hiddenInput.addEventListener("input", function hiddenInputHandler(e) {
|
||||||
|
let inputData = e.data ?? "delete"; // backspace makes null
|
||||||
|
window.lastKey = inputData
|
||||||
|
hiddenInput.value = " "; // We need a character to detect deleting
|
||||||
|
if(window.keyboardFix) {
|
||||||
|
if(e.inputType == 'insertText') {
|
||||||
|
let isShift = (inputData.toLowerCase() != inputData);
|
||||||
|
if(isShift) {
|
||||||
|
keyEvent("shift", "keydown")
|
||||||
|
keyEvent(inputData, "keydown");
|
||||||
|
keyEvent(inputData, "keyup");
|
||||||
|
keyEvent("shift", "keyup")
|
||||||
|
} else {
|
||||||
|
keyEvent(inputData, "keydown");
|
||||||
|
keyEvent(inputData, "keyup");
|
||||||
|
}
|
||||||
|
} else if (e.inputType == 'deleteContentForward' || e.inputType == 'deleteContentBackward') {
|
||||||
|
keyEvent("backspace", "keydown")
|
||||||
|
keyEvent("backspace", "keyup")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
hiddenInput.addEventListener("keydown", function(e) {
|
||||||
|
if(!(e.key && e.keyCode && e.which) && !window.keyboardFix) {
|
||||||
|
console.warn("Switching from keydown to input events due to invalid KeyboardEvent. Some functionality will be lost.")
|
||||||
|
window.keyboardFix = true;
|
||||||
|
if(window.lastKey) {
|
||||||
|
keyEvent(window.lastKey, "keydown")
|
||||||
|
keyEvent(window.lastKey, "keyup")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
document.body.appendChild(hiddenInput);
|
document.body.appendChild(hiddenInput);
|
||||||
let keyboardButton = createTouchButton("keyboardButton", "inMenu");
|
let keyboardButton = createTouchButton("keyboardButton", "inMenu");
|
||||||
keyboardButton.style.cssText = "top: 0vh; margin: auto; left: 8vh; right:0vh; width: 8vh; height: 8vh;"
|
keyboardButton.style.cssText = "top: 0vh; margin: auto; left: 8vh; right:0vh; width: 8vh; height: 8vh;"
|
||||||
keyboardButton.addEventListener("touchstart", function(e){e.preventDefault();hiddenInput.blur()}, false);
|
keyboardButton.addEventListener("touchstart", function(e){e.preventDefault();hiddenInput.blur()}, false);
|
||||||
keyboardButton.addEventListener("touchend", function(e){hiddenInput.select()}, false);
|
keyboardButton.addEventListener("touchend", function(e){e.preventDefault();toggleKeyboard()}, false);
|
||||||
document.body.appendChild(keyboardButton);
|
document.body.appendChild(keyboardButton);
|
||||||
let placeButton = createTouchButton("placeButton", "inGame");
|
let placeButton = createTouchButton("placeButton", "inGame");
|
||||||
placeButton.style.cssText = "right:0vh;bottom:20vh;"
|
placeButton.style.cssText = "right:0vh;bottom:20vh;"
|
||||||
|
@ -375,8 +439,8 @@ function insertCanvasElements() {
|
||||||
document.body.appendChild(sprintButton);
|
document.body.appendChild(sprintButton);
|
||||||
let pauseButton = createTouchButton("pauseButton", "inGame");
|
let pauseButton = createTouchButton("pauseButton", "inGame");
|
||||||
pauseButton.style.cssText = "top: 0vh; margin: auto; left: 0vh; right: 32vh; width: 8vh; height: 8vh;"
|
pauseButton.style.cssText = "top: 0vh; margin: auto; left: 0vh; right: 32vh; width: 8vh; height: 8vh;"
|
||||||
pauseButton.addEventListener("touchstart", function(e){keyEvent("À", "keydown")}, false);
|
pauseButton.addEventListener("touchstart", function(e){keyEvent("`", "keydown")}, false);
|
||||||
pauseButton.addEventListener("touchend", function(e){keyEvent("À", "keyup")}, false);
|
pauseButton.addEventListener("touchend", function(e){keyEvent("`", "keyup")}, false);
|
||||||
document.body.appendChild(pauseButton);
|
document.body.appendChild(pauseButton);
|
||||||
let chatButton = createTouchButton("chatButton", "inGame");
|
let chatButton = createTouchButton("chatButton", "inGame");
|
||||||
chatButton.style.cssText = "top: 0vh; margin: auto; left: 0vh; right: 16vh; width: 8vh; height: 8vh;"
|
chatButton.style.cssText = "top: 0vh; margin: auto; left: 0vh; right: 16vh; width: 8vh; height: 8vh;"
|
||||||
|
@ -389,8 +453,8 @@ function insertCanvasElements() {
|
||||||
keyEvent("5", "keydown");
|
keyEvent("5", "keydown");
|
||||||
}, false);
|
}, false);
|
||||||
perspectiveButton.addEventListener("touchend", function(e) {
|
perspectiveButton.addEventListener("touchend", function(e) {
|
||||||
keyEvent("5", "keyup");
|
|
||||||
keyEvent("f", "keyup");
|
keyEvent("f", "keyup");
|
||||||
|
keyEvent("5", "keyup");
|
||||||
}, false);
|
}, false);
|
||||||
document.body.appendChild(perspectiveButton);
|
document.body.appendChild(perspectiveButton);
|
||||||
let screenshotButton = createTouchButton("screenshotButton", "inGame");
|
let screenshotButton = createTouchButton("screenshotButton", "inGame");
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
// @downloadURL https://raw.githubusercontent.com/FlamedDogo99/EaglerMobile/main/eaglermobile.user.js
|
// @downloadURL https://raw.githubusercontent.com/FlamedDogo99/EaglerMobile/main/eaglermobile.user.js
|
||||||
// @license Apache License 2.0 - http://www.apache.org/licenses/
|
// @license Apache License 2.0 - http://www.apache.org/licenses/
|
||||||
// @match https://eaglercraft.com/mc/*
|
// @match https://eaglercraft.com/mc/*
|
||||||
// @version 2.4
|
// @version 3.0.1
|
||||||
// @updateURL https://raw.githubusercontent.com/FlamedDogo99/EaglerMobile/main/eaglermobile.user.js
|
// @updateURL https://raw.githubusercontent.com/FlamedDogo99/EaglerMobile/main/eaglermobile.user.js
|
||||||
// @run-at document-start
|
// @run-at document-start
|
||||||
// ==/UserScript==
|
// ==/UserScript==
|
||||||
|
@ -26,24 +26,54 @@ if(!isMobile()) {
|
||||||
window.keyboardEnabled = false;
|
window.keyboardEnabled = false;
|
||||||
window.crouchLock = false;
|
window.crouchLock = false;
|
||||||
window.sprintLock = false;
|
window.sprintLock = false;
|
||||||
|
window.keyboardFix = false; // temporarily set to true until I can figure out whats going wrong with the event listener in charge of switching it
|
||||||
// Used for changing touchmove events to mousemove events
|
// Used for changing touchmove events to mousemove events
|
||||||
var previousTouchX = null;
|
var previousTouchX = null;
|
||||||
var previousTouchY = null;
|
var previousTouchY = null;
|
||||||
var startTouchX = null;
|
var startTouchX = null;
|
||||||
// Key and mouse events
|
// better charCodeAt function
|
||||||
function keyEvent(name, state) {
|
String.prototype.toKeyCode = function() {
|
||||||
const keyName = name.toUpperCase().charCodeAt(0)
|
const keyCodeList = {"0": 48, "1": 49, "2": 50, "3": 51, "4": 52, "5": 53, "6": 54, "7": 55, "8": 56, "9": 57, "backspace": 8, "tab": 9, "enter": 13, "shift": 16, "ctrl": 17, "alt": 18, "pause_break": 19, "caps_lock": 20, "escape": 27, " ": 32, "page_up": 33, "page_down": 34, "end": 35, "home": 36, "left_arrow": 37, "up_arrow": 38, "right_arrow": 39, "down_arrow": 40, "insert": 45, "delete": 46, "a": 65, "b": 66, "c": 67, "d": 68, "e": 69, "f": 70, "g": 71, "h": 72, "i": 73, "j": 74, "k": 75, "l": 76, "m": 77, "n": 78, "o": 79, "p": 80, "q": 81, "r": 82, "s": 83, "t": 84, "u": 85, "v": 86, "w": 87, "x": 88, "y": 89, "z": 90, "left_window_key": 91, "right_window_key": 92, "select_key": 93, "numpad_0": 96, "numpad_1": 97, "numpad_2": 98, "numpad_3": 99, "numpad_4": 100, "numpad_5": 101, "numpad_6": 102, "numpad_7": 103, "numpad_8": 104, "numpad_9": 105, "*": 106, "+": 107, "-": 109, ".": 110, "/": 111, "f1": 112, "f2": 113, "f3": 114, "f4": 115, "f5": 116, "f6": 117, "f7": 118, "f8": 119, "f9": 120, "f10": 121, "f11": 122, "f12": 123, "num_lock": 144, "scroll_lock": 145, ";": 186, "=": 187, ",": 188, "-": 189, ".": 190, "/": 191, "`": 192, "[": 219, "\\": 220, "]": 221, "\"": 222};
|
||||||
window.dispatchEvent(new KeyboardEvent(state, {
|
return keyCodeList[this];
|
||||||
key: name,
|
|
||||||
keyCode: keyName,
|
|
||||||
which: keyName
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
function shiftKeyEvent(state) {
|
// Ignores keydown events that don't have the isValid parameter set to true
|
||||||
window.dispatchEvent(new KeyboardEvent(state, {
|
const _addEventListener = EventTarget.prototype.addEventListener;
|
||||||
keyCode: 16,
|
Object.defineProperty(EventTarget.prototype, "addEventListener", {
|
||||||
which: 16
|
value: function (type, fn, ...rest) {
|
||||||
}));
|
if(type == 'keydown') {
|
||||||
|
_addEventListener.call(this, type, function(...args) {
|
||||||
|
if(!args[0].isValid && window.keyboardFix) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return fn.apply(this, args);
|
||||||
|
}, ...rest);
|
||||||
|
} else {
|
||||||
|
_addEventListener.call(this, type, fn, ...rest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Allows typing in #hiddenInput
|
||||||
|
const _preventDefault = Event.prototype.preventDefault;
|
||||||
|
Event.prototype.preventDefault = function() {
|
||||||
|
if(document.activeElement.id != "hiddenInput") {
|
||||||
|
this._preventDefault = _preventDefault;
|
||||||
|
this._preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Key and mouse events
|
||||||
|
// Note: the client must have the key, keyCode, and which parameters defined or it will crash
|
||||||
|
// Note: for text inputs, the client only reads from the "key" paramater
|
||||||
|
// * an exception to this appears to be the shift and backspace key
|
||||||
|
// Note: for inGame inputs, the client only reads from the "keyCode character"
|
||||||
|
function keyEvent(name, state) {
|
||||||
|
const charCode = name.toKeyCode();
|
||||||
|
let evt = new KeyboardEvent(state, {
|
||||||
|
key: name,
|
||||||
|
keyCode: charCode,
|
||||||
|
which: charCode
|
||||||
|
});
|
||||||
|
evt.isValid = true; // Disables fix for bad keyboard input
|
||||||
|
window.dispatchEvent(evt);
|
||||||
}
|
}
|
||||||
function mouseEvent(number, state, canvas) {
|
function mouseEvent(number, state, canvas) {
|
||||||
canvas.dispatchEvent(new PointerEvent(state, {"button": number}))
|
canvas.dispatchEvent(new PointerEvent(state, {"button": number}))
|
||||||
|
@ -63,55 +93,62 @@ function setButtonVisibility(pointerLocked) {
|
||||||
// When requestpointerlock is called, this dispatches an event, saves the requested element to window.fakelock, and unhides the touch controls
|
// When requestpointerlock is called, this dispatches an event, saves the requested element to window.fakelock, and unhides the touch controls
|
||||||
window.fakelock = null;
|
window.fakelock = null;
|
||||||
|
|
||||||
Element.prototype.requestPointerLock = function() {
|
Object.defineProperty(Element.prototype, "requestPointerLock", {
|
||||||
|
value: function() {
|
||||||
window.fakelock = this
|
window.fakelock = this
|
||||||
document.dispatchEvent(new Event('pointerlockchange'));
|
document.dispatchEvent(new Event('pointerlockchange'));
|
||||||
console.log("requested pointerlock")
|
|
||||||
setButtonVisibility(true);
|
setButtonVisibility(true);
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// Makes pointerLockElement return window.fakelock
|
// Makes pointerLockElement return window.fakelock
|
||||||
Object.defineProperty(document, "pointerLockElement", {
|
Object.defineProperty(Document.prototype, "pointerLockElement", {
|
||||||
get: function() {
|
get: function() {
|
||||||
return window.fakelock;
|
return window.fakelock;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// When exitPointerLock is called, this dispatches an event, clears the
|
// When exitPointerLock is called, this dispatches an event, clears the
|
||||||
document.exitPointerLock = function() {
|
Object.defineProperty(Document.prototype, "exitPointerLock", {
|
||||||
|
value: function() {
|
||||||
window.fakelock = null
|
window.fakelock = null
|
||||||
document.dispatchEvent(new Event('pointerlockchange'));
|
document.dispatchEvent(new Event('pointerlockchange'));
|
||||||
setButtonVisibility(false);
|
setButtonVisibility(false);
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// FULLSCREEN
|
// FULLSCREEN
|
||||||
window.fakefull = null;
|
window.fakefull = null;
|
||||||
// Stops the client from crashing when fullscreen is requested
|
// Stops the client from crashing when fullscreen is requested
|
||||||
Element.prototype.requestFullscreen = function() {
|
Object.defineProperty(Element.prototype, "requestFullscreen", {
|
||||||
|
value: function() {
|
||||||
window.fakefull = this
|
window.fakefull = this
|
||||||
document.dispatchEvent(new Event('fullscreenchange'));
|
document.dispatchEvent(new Event('fullscreenchange'));
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
});
|
||||||
Object.defineProperty(document, "fullscreenElement", {
|
Object.defineProperty(document, "fullscreenElement", {
|
||||||
get: function() {
|
get: function() {
|
||||||
return window.fakefull;
|
return window.fakefull;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
document.exitFullscreen = function() {
|
Object.defineProperty(Document.prototype, "exitFullscreen", {
|
||||||
|
value: function() {
|
||||||
window.fakefull = null
|
window.fakefull = null
|
||||||
document.dispatchEvent(new Event('fullscreenchange'));
|
document.dispatchEvent(new Event('fullscreenchange'));
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// FILE UPLOADING
|
// FILE UPLOADING
|
||||||
// Safari doesn't recognize the element.click() used to display the file uploader as an action performed by the user, so it ignores it.
|
// Safari doesn't recognize the element.click() used to display the file uploader as an action performed by the user, so it ignores it.
|
||||||
// This hijacks the element.createElement() function to add the file upload to the DOM, so the user can manually press the button again.
|
// This hijacks the element.createElement() function to add the file upload to the DOM, so the user can manually press the button again.
|
||||||
var oldCreate = document.createElement;
|
const _createElement = document.createElement;
|
||||||
document.createElement = function(type, ignore) {
|
document.createElement = function(type, ignore) {
|
||||||
this.oldCreate = oldCreate;
|
this._createElement = _createElement;
|
||||||
let element = this.oldCreate(type);
|
let element = this._createElement(type);
|
||||||
if(type == "input" && !ignore) {
|
if(type == "input" && !ignore) {
|
||||||
let newElement = document.querySelector('input');
|
let newElement = document.querySelector('input');
|
||||||
if(!newElement) {
|
if(!newElement) {
|
||||||
|
@ -126,7 +163,7 @@ document.createElement = function(type, ignore) {
|
||||||
newElement.hidden = false;
|
newElement.hidden = false;
|
||||||
return newElement;
|
return newElement;
|
||||||
}
|
}
|
||||||
return this.oldCreate(type);
|
return this._createElement(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lazy way to hide touch controls through CSS.
|
// Lazy way to hide touch controls through CSS.
|
||||||
|
@ -294,7 +331,7 @@ function insertCanvasElements() {
|
||||||
let crouchButton = createTouchButton("crouchButton", "inGame");
|
let crouchButton = createTouchButton("crouchButton", "inGame");
|
||||||
crouchButton.style.cssText = "left:10vh;bottom:10vh;"
|
crouchButton.style.cssText = "left:10vh;bottom:10vh;"
|
||||||
crouchButton.addEventListener("touchstart", function(e){
|
crouchButton.addEventListener("touchstart", function(e){
|
||||||
shiftKeyEvent("keydown");
|
keyEvent("shift", "keydown")
|
||||||
window.crouchLock = window.crouchLock ? null : false
|
window.crouchLock = window.crouchLock ? null : false
|
||||||
crouchTimer = setTimeout(function(e) {
|
crouchTimer = setTimeout(function(e) {
|
||||||
window.crouchLock = (window.crouchLock != null);
|
window.crouchLock = (window.crouchLock != null);
|
||||||
|
@ -304,7 +341,7 @@ function insertCanvasElements() {
|
||||||
|
|
||||||
crouchButton.addEventListener("touchend", function(e) {
|
crouchButton.addEventListener("touchend", function(e) {
|
||||||
if(!window.crouchLock) {
|
if(!window.crouchLock) {
|
||||||
shiftKeyEvent("keyup");
|
keyEvent("shift", "keyup")
|
||||||
crouchButton.classList.remove('active');
|
crouchButton.classList.remove('active');
|
||||||
window.crouchLock = false
|
window.crouchLock = false
|
||||||
}
|
}
|
||||||
|
@ -318,13 +355,48 @@ function insertCanvasElements() {
|
||||||
document.body.appendChild(inventoryButton);
|
document.body.appendChild(inventoryButton);
|
||||||
let exitButton = createTouchButton("exitButton", "inMenu");
|
let exitButton = createTouchButton("exitButton", "inMenu");
|
||||||
exitButton.style.cssText = "top: 0vh; margin: auto; left: 0vh; right:8vh; width: 8vh; height: 8vh;"
|
exitButton.style.cssText = "top: 0vh; margin: auto; left: 0vh; right:8vh; width: 8vh; height: 8vh;"
|
||||||
exitButton.addEventListener("touchstart", function(e){keyEvent("À", "keydown")}, false);
|
exitButton.addEventListener("touchstart", function(e){keyEvent("`", "keydown")}, false);
|
||||||
exitButton.addEventListener("touchend", function(e){keyEvent("À", "keyup")}, false);
|
exitButton.addEventListener("touchend", function(e){keyEvent("`", "keyup")}, false);
|
||||||
document.body.appendChild(exitButton);
|
document.body.appendChild(exitButton);
|
||||||
// input for keyboard button
|
// input for keyboard button
|
||||||
let hiddenInput = document.createElement('input', true);
|
let hiddenInput = document.createElement('input', true);
|
||||||
hiddenInput.id = "hiddenInput"
|
hiddenInput.id = "hiddenInput"
|
||||||
hiddenInput.style.cssText = "opacity:0;z-index:-99999";
|
hiddenInput.classList.add("inMenu")
|
||||||
|
// We are hiding the text input behind button because opacity was causing problems.
|
||||||
|
hiddenInput.style.cssText = "position:absolute;top: 0vh; margin: auto; left: 8vh; right:0vh; width: 8vh; height: 8vh;font-size:20px;z-index:-10;color: transparent;text-shadow: 0 0 0 black;";
|
||||||
|
hiddenInput.value = " " //Allows delete to be detected before input is changed
|
||||||
|
hiddenInput.addEventListener("input", function hiddenInputHandler(e) {
|
||||||
|
let inputData = e.data ?? "delete"; // backspace makes null
|
||||||
|
window.lastKey = inputData
|
||||||
|
hiddenInput.value = " "; // We need a character to detect deleting
|
||||||
|
if(window.keyboardFix) {
|
||||||
|
if(e.inputType == 'insertText') {
|
||||||
|
let isShift = (inputData.toLowerCase() != inputData);
|
||||||
|
if(isShift) {
|
||||||
|
keyEvent("shift", "keydown")
|
||||||
|
keyEvent(inputData, "keydown");
|
||||||
|
keyEvent(inputData, "keyup");
|
||||||
|
keyEvent("shift", "keyup")
|
||||||
|
} else {
|
||||||
|
keyEvent(inputData, "keydown");
|
||||||
|
keyEvent(inputData, "keyup");
|
||||||
|
}
|
||||||
|
} else if (e.inputType == 'deleteContentForward' || e.inputType == 'deleteContentBackward') {
|
||||||
|
keyEvent("backspace", "keydown")
|
||||||
|
keyEvent("backspace", "keyup")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
hiddenInput.addEventListener("keydown", function(e) {
|
||||||
|
if(!(e.key && e.keyCode && e.which) && !window.keyboardFix) {
|
||||||
|
console.warn("Switching from keydown to input events due to invalid KeyboardEvent. Some functionality will be lost.")
|
||||||
|
window.keyboardFix = true;
|
||||||
|
if(window.lastKey) {
|
||||||
|
keyEvent(window.lastKey, "keydown")
|
||||||
|
keyEvent(window.lastKey, "keyup")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
document.body.appendChild(hiddenInput);
|
document.body.appendChild(hiddenInput);
|
||||||
let keyboardButton = createTouchButton("keyboardButton", "inMenu");
|
let keyboardButton = createTouchButton("keyboardButton", "inMenu");
|
||||||
keyboardButton.style.cssText = "top: 0vh; margin: auto; left: 8vh; right:0vh; width: 8vh; height: 8vh;"
|
keyboardButton.style.cssText = "top: 0vh; margin: auto; left: 8vh; right:0vh; width: 8vh; height: 8vh;"
|
||||||
|
@ -381,8 +453,8 @@ function insertCanvasElements() {
|
||||||
document.body.appendChild(sprintButton);
|
document.body.appendChild(sprintButton);
|
||||||
let pauseButton = createTouchButton("pauseButton", "inGame");
|
let pauseButton = createTouchButton("pauseButton", "inGame");
|
||||||
pauseButton.style.cssText = "top: 0vh; margin: auto; left: 0vh; right: 32vh; width: 8vh; height: 8vh;"
|
pauseButton.style.cssText = "top: 0vh; margin: auto; left: 0vh; right: 32vh; width: 8vh; height: 8vh;"
|
||||||
pauseButton.addEventListener("touchstart", function(e){keyEvent("À", "keydown")}, false);
|
pauseButton.addEventListener("touchstart", function(e){keyEvent("`", "keydown")}, false);
|
||||||
pauseButton.addEventListener("touchend", function(e){keyEvent("À", "keyup")}, false);
|
pauseButton.addEventListener("touchend", function(e){keyEvent("`", "keyup")}, false);
|
||||||
document.body.appendChild(pauseButton);
|
document.body.appendChild(pauseButton);
|
||||||
let chatButton = createTouchButton("chatButton", "inGame");
|
let chatButton = createTouchButton("chatButton", "inGame");
|
||||||
chatButton.style.cssText = "top: 0vh; margin: auto; left: 0vh; right: 16vh; width: 8vh; height: 8vh;"
|
chatButton.style.cssText = "top: 0vh; margin: auto; left: 0vh; right: 16vh; width: 8vh; height: 8vh;"
|
||||||
|
@ -395,8 +467,8 @@ function insertCanvasElements() {
|
||||||
keyEvent("5", "keydown");
|
keyEvent("5", "keydown");
|
||||||
}, false);
|
}, false);
|
||||||
perspectiveButton.addEventListener("touchend", function(e) {
|
perspectiveButton.addEventListener("touchend", function(e) {
|
||||||
keyEvent("5", "keyup");
|
|
||||||
keyEvent("f", "keyup");
|
keyEvent("f", "keyup");
|
||||||
|
keyEvent("5", "keyup");
|
||||||
}, false);
|
}, false);
|
||||||
document.body.appendChild(perspectiveButton);
|
document.body.appendChild(perspectiveButton);
|
||||||
let screenshotButton = createTouchButton("screenshotButton", "inGame");
|
let screenshotButton = createTouchButton("screenshotButton", "inGame");
|
||||||
|
|
BIN
images/attack.png
Normal file
After Width: | Height: | Size: 696 B |
BIN
images/attack_pressed.png
Normal file
After Width: | Height: | Size: 696 B |
BIN
images/backButton.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
images/backPressed.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
images/button.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
images/buttonPressed.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
images/chat.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
images/chatPressed.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
images/compass.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
images/compassPressed.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
images/crouch.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
images/crouchLock.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
images/crouchPressed.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
images/down.png
Normal file
After Width: | Height: | Size: 289 B |
BIN
images/down_pressed.png
Normal file
After Width: | Height: | Size: 318 B |
BIN
images/drop.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
images/dropPressed.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
images/interact_pressed.png
Normal file
After Width: | Height: | Size: 273 B |
BIN
images/inventory.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
images/inventoryPressed.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
images/jumpButton.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
images/jumpButtonPressed.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
images/keyboard.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
images/keyboardPressed.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
images/left.png
Normal file
After Width: | Height: | Size: 287 B |
BIN
images/left_pressed.png
Normal file
After Width: | Height: | Size: 317 B |
BIN
images/logo.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
images/logo128.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
images/pauseButton.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
images/pausePressed.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
images/perspective5.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
images/perspectivePressed.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
images/place.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
images/preview.png
Normal file
After Width: | Height: | Size: 1.9 MiB |
BIN
images/right.png
Normal file
After Width: | Height: | Size: 291 B |
BIN
images/right_pressed.png
Normal file
After Width: | Height: | Size: 316 B |
BIN
images/screenshot.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
images/screenshotPressed.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
images/scrollLeft.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
images/scrollLeftPressed.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
images/scrollRight.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
images/scrollRightPressed.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
images/select.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
images/selectPressed.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
images/sprint.png
Normal file
After Width: | Height: | Size: 696 B |
BIN
images/sprint_pressed.png
Normal file
After Width: | Height: | Size: 696 B |
BIN
images/strafeLeft.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
images/strafeLeftPressed.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
images/strafeRight.png
Normal file
After Width: | Height: | Size: 993 B |
BIN
images/strafeRightPressed.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
images/up.png
Normal file
After Width: | Height: | Size: 284 B |
BIN
images/up_pressed.png
Normal file
After Width: | Height: | Size: 314 B |