2020-05-09 14:45:23 +00:00
|
|
|
/* typehints:start */
|
|
|
|
import { GameRoot } from "./root";
|
|
|
|
import { InputReceiver } from "../core/input_receiver";
|
|
|
|
import { Application } from "../application";
|
|
|
|
/* typehints:end */
|
|
|
|
|
|
|
|
import { Signal, STOP_PROPAGATION } from "../core/signal";
|
|
|
|
import { IS_MOBILE } from "../core/config";
|
2020-05-17 10:12:13 +00:00
|
|
|
import { T } from "../translations";
|
2020-05-09 14:45:23 +00:00
|
|
|
function key(str) {
|
|
|
|
return str.toUpperCase().charCodeAt(0);
|
|
|
|
}
|
|
|
|
|
2020-05-19 07:14:40 +00:00
|
|
|
export const KEYMAPPINGS = {
|
2020-05-09 14:45:23 +00:00
|
|
|
general: {
|
|
|
|
confirm: { keyCode: 13 }, // enter
|
|
|
|
back: { keyCode: 27, builtin: true }, // escape
|
|
|
|
},
|
|
|
|
|
|
|
|
ingame: {
|
2020-05-19 07:14:40 +00:00
|
|
|
menuOpenShop: { keyCode: key("F") },
|
|
|
|
menuOpenStats: { keyCode: key("G") },
|
2020-07-03 08:31:19 +00:00
|
|
|
menuClose: { keyCode: key("Q") },
|
2020-05-10 16:24:50 +00:00
|
|
|
|
2020-05-19 07:14:40 +00:00
|
|
|
toggleHud: { keyCode: 113 }, // F2
|
2020-06-21 19:44:53 +00:00
|
|
|
exportScreenshot: { keyCode: 114 }, // F3PS
|
2020-06-13 08:57:29 +00:00
|
|
|
toggleFPSInfo: { keyCode: 115 }, // F4
|
2020-06-24 20:23:10 +00:00
|
|
|
|
2020-06-28 09:44:30 +00:00
|
|
|
switchLayers: { keyCode: key("Y") },
|
2020-05-30 17:11:18 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
navigation: {
|
|
|
|
mapMoveUp: { keyCode: key("W") },
|
|
|
|
mapMoveRight: { keyCode: key("D") },
|
|
|
|
mapMoveDown: { keyCode: key("S") },
|
|
|
|
mapMoveLeft: { keyCode: key("A") },
|
2020-06-13 13:14:35 +00:00
|
|
|
mapMoveFaster: { keyCode: 16 }, //shift
|
2020-05-23 09:03:58 +00:00
|
|
|
|
2020-05-30 17:11:18 +00:00
|
|
|
centerMap: { keyCode: 32 }, // SPACE
|
2020-05-23 09:03:58 +00:00
|
|
|
mapZoomIn: { keyCode: 187, repeated: true }, // "+"
|
|
|
|
mapZoomOut: { keyCode: 189, repeated: true }, // "-"
|
2020-05-28 17:40:48 +00:00
|
|
|
|
|
|
|
createMarker: { keyCode: key("M") },
|
2020-05-09 14:45:23 +00:00
|
|
|
},
|
|
|
|
|
2020-05-19 07:14:40 +00:00
|
|
|
buildings: {
|
|
|
|
belt: { keyCode: key("1") },
|
|
|
|
splitter: { keyCode: key("2") },
|
|
|
|
underground_belt: { keyCode: key("3") },
|
|
|
|
miner: { keyCode: key("4") },
|
|
|
|
cutter: { keyCode: key("5") },
|
|
|
|
rotater: { keyCode: key("6") },
|
|
|
|
stacker: { keyCode: key("7") },
|
|
|
|
mixer: { keyCode: key("8") },
|
|
|
|
painter: { keyCode: key("9") },
|
|
|
|
trash: { keyCode: key("0") },
|
|
|
|
},
|
2020-05-09 14:45:23 +00:00
|
|
|
|
2020-05-19 07:14:40 +00:00
|
|
|
placement: {
|
2020-06-21 19:44:53 +00:00
|
|
|
pipette: { keyCode: key("Q") },
|
2020-05-19 07:14:40 +00:00
|
|
|
rotateWhilePlacing: { keyCode: key("R") },
|
2020-05-27 13:03:36 +00:00
|
|
|
rotateInverseModifier: { keyCode: 16 }, // SHIFT
|
2020-05-19 07:14:40 +00:00
|
|
|
cycleBuildingVariants: { keyCode: key("T") },
|
|
|
|
cycleBuildings: { keyCode: 9 }, // TAB
|
2020-06-17 12:39:12 +00:00
|
|
|
switchDirectionLockSide: { keyCode: key("R") },
|
2020-05-19 07:14:40 +00:00
|
|
|
},
|
2020-05-16 20:45:40 +00:00
|
|
|
|
2020-05-19 07:14:40 +00:00
|
|
|
massSelect: {
|
2020-05-27 12:25:30 +00:00
|
|
|
massSelectStart: { keyCode: 17 }, // CTRL
|
|
|
|
massSelectSelectMultiple: { keyCode: 16 }, // SHIFT
|
2020-05-27 12:30:59 +00:00
|
|
|
massSelectCopy: { keyCode: key("C") },
|
2020-06-11 06:19:44 +00:00
|
|
|
massSelectCut: { keyCode: key("X") },
|
2020-05-30 17:11:18 +00:00
|
|
|
confirmMassDelete: { keyCode: 46 }, // DEL
|
2020-06-11 21:56:13 +00:00
|
|
|
pasteLastBlueprint: { keyCode: key("V") },
|
2020-05-19 07:14:40 +00:00
|
|
|
},
|
2020-05-17 08:07:20 +00:00
|
|
|
|
2020-05-19 07:14:40 +00:00
|
|
|
placementModifiers: {
|
2020-06-17 12:29:43 +00:00
|
|
|
lockBeltDirection: { keyCode: 16 }, // SHIFT
|
2020-05-27 12:25:30 +00:00
|
|
|
placementDisableAutoOrientation: { keyCode: 17 }, // CTRL
|
|
|
|
placeMultiple: { keyCode: 16 }, // SHIFT
|
|
|
|
placeInverse: { keyCode: 18 }, // ALT
|
2020-05-09 14:45:23 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2020-05-19 07:14:40 +00:00
|
|
|
// Assign ids
|
|
|
|
for (const categoryId in KEYMAPPINGS) {
|
|
|
|
for (const mappingId in KEYMAPPINGS[categoryId]) {
|
|
|
|
KEYMAPPINGS[categoryId][mappingId].id = mappingId;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-21 17:30:44 +00:00
|
|
|
export const KEYCODE_LMB = 1;
|
|
|
|
export const KEYCODE_MMB = 2;
|
|
|
|
export const KEYCODE_RMB = 3;
|
|
|
|
|
2020-05-09 14:45:23 +00:00
|
|
|
/**
|
|
|
|
* Returns a keycode -> string
|
|
|
|
* @param {number} code
|
|
|
|
* @returns {string}
|
|
|
|
*/
|
|
|
|
export function getStringForKeyCode(code) {
|
|
|
|
switch (code) {
|
2020-06-21 17:30:44 +00:00
|
|
|
case KEYCODE_LMB:
|
2020-05-30 08:11:26 +00:00
|
|
|
return "LMB";
|
2020-06-21 17:30:44 +00:00
|
|
|
case KEYCODE_MMB:
|
2020-05-30 08:11:26 +00:00
|
|
|
return "MMB";
|
2020-06-21 17:30:44 +00:00
|
|
|
case KEYCODE_RMB:
|
2020-05-30 08:11:26 +00:00
|
|
|
return "RMB";
|
2020-05-30 07:57:02 +00:00
|
|
|
case 4:
|
2020-05-30 08:11:26 +00:00
|
|
|
return "MB4";
|
|
|
|
case 5:
|
2020-05-30 07:57:02 +00:00
|
|
|
return "MB5";
|
2020-05-09 14:45:23 +00:00
|
|
|
case 8:
|
|
|
|
return "⌫";
|
|
|
|
case 9:
|
2020-05-17 10:12:13 +00:00
|
|
|
return T.global.keys.tab;
|
2020-05-09 14:45:23 +00:00
|
|
|
case 13:
|
|
|
|
return "⏎";
|
|
|
|
case 16:
|
|
|
|
return "⇪";
|
|
|
|
case 17:
|
2020-05-17 10:12:13 +00:00
|
|
|
return T.global.keys.control;
|
2020-05-09 14:45:23 +00:00
|
|
|
case 18:
|
2020-05-17 10:12:13 +00:00
|
|
|
return T.global.keys.alt;
|
2020-05-09 14:45:23 +00:00
|
|
|
case 19:
|
|
|
|
return "PAUSE";
|
|
|
|
case 20:
|
|
|
|
return "CAPS";
|
|
|
|
case 27:
|
2020-05-17 10:12:13 +00:00
|
|
|
return T.global.keys.escape;
|
2020-05-09 14:45:23 +00:00
|
|
|
case 32:
|
2020-05-17 10:12:13 +00:00
|
|
|
return T.global.keys.space;
|
2020-05-09 14:45:23 +00:00
|
|
|
case 33:
|
|
|
|
return "PGUP";
|
|
|
|
case 34:
|
|
|
|
return "PGDOWN";
|
|
|
|
case 35:
|
|
|
|
return "END";
|
|
|
|
case 36:
|
|
|
|
return "HOME";
|
|
|
|
case 37:
|
|
|
|
return "⬅";
|
|
|
|
case 38:
|
|
|
|
return "⬆";
|
|
|
|
case 39:
|
|
|
|
return "➡";
|
|
|
|
case 40:
|
|
|
|
return "⬇";
|
|
|
|
case 44:
|
|
|
|
return "PRNT";
|
|
|
|
case 45:
|
|
|
|
return "INS";
|
|
|
|
case 46:
|
|
|
|
return "DEL";
|
|
|
|
case 93:
|
|
|
|
return "SEL";
|
|
|
|
case 96:
|
|
|
|
return "NUM 0";
|
|
|
|
case 97:
|
|
|
|
return "NUM 1";
|
|
|
|
case 98:
|
|
|
|
return "NUM 2";
|
|
|
|
case 99:
|
|
|
|
return "NUM 3";
|
|
|
|
case 100:
|
|
|
|
return "NUM 4";
|
|
|
|
case 101:
|
|
|
|
return "NUM 5";
|
|
|
|
case 102:
|
|
|
|
return "NUM 6";
|
|
|
|
case 103:
|
|
|
|
return "NUM 7";
|
|
|
|
case 104:
|
|
|
|
return "NUM 8";
|
|
|
|
case 105:
|
|
|
|
return "NUM 9";
|
|
|
|
case 106:
|
|
|
|
return "*";
|
|
|
|
case 107:
|
|
|
|
return "+";
|
|
|
|
case 109:
|
|
|
|
return "-";
|
|
|
|
case 110:
|
|
|
|
return ".";
|
|
|
|
case 111:
|
|
|
|
return "/";
|
|
|
|
case 112:
|
|
|
|
return "F1";
|
|
|
|
case 113:
|
|
|
|
return "F2";
|
|
|
|
case 114:
|
|
|
|
return "F3";
|
|
|
|
case 115:
|
|
|
|
return "F4";
|
|
|
|
case 116:
|
|
|
|
return "F4";
|
|
|
|
case 117:
|
|
|
|
return "F5";
|
|
|
|
case 118:
|
|
|
|
return "F6";
|
|
|
|
case 119:
|
|
|
|
return "F7";
|
|
|
|
case 120:
|
|
|
|
return "F8";
|
|
|
|
case 121:
|
|
|
|
return "F9";
|
|
|
|
case 122:
|
|
|
|
return "F10";
|
|
|
|
case 123:
|
|
|
|
return "F11";
|
|
|
|
case 124:
|
|
|
|
return "F12";
|
|
|
|
|
|
|
|
case 144:
|
|
|
|
return "NUMLOCK";
|
|
|
|
case 145:
|
|
|
|
return "SCRLOCK";
|
|
|
|
case 182:
|
|
|
|
return "COMP";
|
|
|
|
case 183:
|
|
|
|
return "CALC";
|
|
|
|
case 186:
|
|
|
|
return ";";
|
|
|
|
case 187:
|
2020-05-23 09:03:58 +00:00
|
|
|
return "+";
|
2020-05-09 14:45:23 +00:00
|
|
|
case 188:
|
|
|
|
return ",";
|
|
|
|
case 189:
|
|
|
|
return "-";
|
|
|
|
case 191:
|
|
|
|
return "/";
|
|
|
|
case 219:
|
|
|
|
return "[";
|
|
|
|
case 220:
|
|
|
|
return "\\";
|
|
|
|
case 221:
|
|
|
|
return "]";
|
|
|
|
case 222:
|
|
|
|
return "'";
|
|
|
|
}
|
|
|
|
|
|
|
|
return String.fromCharCode(code);
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Keybinding {
|
|
|
|
/**
|
|
|
|
*
|
2020-05-28 17:40:48 +00:00
|
|
|
* @param {KeyActionMapper} keyMapper
|
2020-05-09 14:45:23 +00:00
|
|
|
* @param {Application} app
|
|
|
|
* @param {object} param0
|
|
|
|
* @param {number} param0.keyCode
|
|
|
|
* @param {boolean=} param0.builtin
|
2020-05-23 09:03:58 +00:00
|
|
|
* @param {boolean=} param0.repeated
|
2020-05-09 14:45:23 +00:00
|
|
|
*/
|
2020-05-28 17:40:48 +00:00
|
|
|
constructor(keyMapper, app, { keyCode, builtin = false, repeated = false }) {
|
2020-05-09 14:45:23 +00:00
|
|
|
assert(keyCode && Number.isInteger(keyCode), "Invalid key code: " + keyCode);
|
2020-05-28 17:40:48 +00:00
|
|
|
this.keyMapper = keyMapper;
|
2020-05-09 14:45:23 +00:00
|
|
|
this.app = app;
|
|
|
|
this.keyCode = keyCode;
|
|
|
|
this.builtin = builtin;
|
2020-05-23 09:03:58 +00:00
|
|
|
this.repeated = repeated;
|
2020-05-09 14:45:23 +00:00
|
|
|
|
|
|
|
this.signal = new Signal();
|
|
|
|
this.toggled = new Signal();
|
|
|
|
}
|
|
|
|
|
2020-05-28 12:53:11 +00:00
|
|
|
/**
|
|
|
|
* Returns whether this binding is currently pressed
|
2020-06-17 11:12:39 +00:00
|
|
|
* @returns {boolean}
|
2020-05-28 12:53:11 +00:00
|
|
|
*/
|
2020-06-17 11:12:39 +00:00
|
|
|
get pressed() {
|
2020-05-28 17:40:48 +00:00
|
|
|
// Check if the key is down
|
|
|
|
if (this.app.inputMgr.keysDown.has(this.keyCode)) {
|
|
|
|
// Check if it is the top reciever
|
|
|
|
const reciever = this.keyMapper.inputReceiver;
|
|
|
|
return this.app.inputMgr.getTopReciever() === reciever;
|
|
|
|
}
|
2020-06-17 11:12:39 +00:00
|
|
|
return false;
|
2020-05-28 12:53:11 +00:00
|
|
|
}
|
|
|
|
|
2020-05-09 14:45:23 +00:00
|
|
|
/**
|
|
|
|
* Adds an event listener
|
|
|
|
* @param {function() : void} receiver
|
|
|
|
* @param {object=} scope
|
|
|
|
*/
|
|
|
|
add(receiver, scope = null) {
|
|
|
|
this.signal.add(receiver, scope);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {Element} elem
|
|
|
|
* @returns {HTMLElement} the created element, or null if the keybindings are not shown
|
|
|
|
* */
|
|
|
|
appendLabelToElement(elem) {
|
|
|
|
if (IS_MOBILE) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
const spacer = document.createElement("code");
|
|
|
|
spacer.classList.add("keybinding");
|
|
|
|
spacer.innerHTML = getStringForKeyCode(this.keyCode);
|
|
|
|
elem.appendChild(spacer);
|
|
|
|
return spacer;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the key code as a nice string
|
|
|
|
*/
|
|
|
|
getKeyCodeString() {
|
|
|
|
return getStringForKeyCode(this.keyCode);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remvoes all signal receivers
|
|
|
|
*/
|
|
|
|
clearSignalReceivers() {
|
|
|
|
this.signal.removeAll();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class KeyActionMapper {
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {GameRoot} root
|
|
|
|
* @param {InputReceiver} inputReciever
|
|
|
|
*/
|
|
|
|
constructor(root, inputReciever) {
|
|
|
|
this.root = root;
|
2020-05-28 17:40:48 +00:00
|
|
|
this.inputReceiver = inputReciever;
|
|
|
|
|
2020-05-09 14:45:23 +00:00
|
|
|
inputReciever.keydown.add(this.handleKeydown, this);
|
|
|
|
inputReciever.keyup.add(this.handleKeyup, this);
|
|
|
|
|
|
|
|
/** @type {Object.<string, Keybinding>} */
|
|
|
|
this.keybindings = {};
|
|
|
|
|
2020-05-19 07:14:40 +00:00
|
|
|
const overrides = root.app.settings.getKeybindingOverrides();
|
2020-05-09 14:45:23 +00:00
|
|
|
|
2020-05-19 07:14:40 +00:00
|
|
|
for (const category in KEYMAPPINGS) {
|
|
|
|
for (const key in KEYMAPPINGS[category]) {
|
|
|
|
let payload = Object.assign({}, KEYMAPPINGS[category][key]);
|
|
|
|
if (overrides[key]) {
|
|
|
|
payload.keyCode = overrides[key];
|
|
|
|
}
|
2020-05-09 14:45:23 +00:00
|
|
|
|
2020-05-28 17:40:48 +00:00
|
|
|
this.keybindings[key] = new Keybinding(this, this.root.app, payload);
|
2020-05-09 14:45:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
inputReciever.pageBlur.add(this.onPageBlur, this);
|
|
|
|
inputReciever.destroyed.add(this.cleanup, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns all keybindings starting with the given id
|
|
|
|
* @param {string} pattern
|
|
|
|
* @returns {Array<Keybinding>}
|
|
|
|
*/
|
|
|
|
getKeybindingsStartingWith(pattern) {
|
|
|
|
let result = [];
|
|
|
|
for (const key in this.keybindings) {
|
|
|
|
if (key.startsWith(pattern)) {
|
|
|
|
result.push(this.keybindings[key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Forwards the given events to the other mapper (used in tooltips)
|
|
|
|
* @param {KeyActionMapper} receiver
|
|
|
|
* @param {Array<string>} bindings
|
|
|
|
*/
|
|
|
|
forward(receiver, bindings) {
|
|
|
|
for (let i = 0; i < bindings.length; ++i) {
|
|
|
|
const key = bindings[i];
|
|
|
|
this.keybindings[key].signal.add((...args) => receiver.keybindings[key].signal.dispatch(...args));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cleanup() {
|
|
|
|
for (const key in this.keybindings) {
|
|
|
|
this.keybindings[key].signal.removeAll();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
onPageBlur() {
|
|
|
|
// Reset all down states
|
|
|
|
// Find mapping
|
|
|
|
for (const key in this.keybindings) {
|
|
|
|
/** @type {Keybinding} */
|
|
|
|
const binding = this.keybindings[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal keydown handler
|
|
|
|
* @param {object} param0
|
|
|
|
* @param {number} param0.keyCode
|
|
|
|
* @param {boolean} param0.shift
|
|
|
|
* @param {boolean} param0.alt
|
2020-05-28 12:53:11 +00:00
|
|
|
* @param {boolean=} param0.initial
|
2020-05-09 14:45:23 +00:00
|
|
|
*/
|
2020-05-28 12:53:11 +00:00
|
|
|
handleKeydown({ keyCode, shift, alt, initial }) {
|
2020-05-09 14:45:23 +00:00
|
|
|
let stop = false;
|
|
|
|
|
|
|
|
// Find mapping
|
|
|
|
for (const key in this.keybindings) {
|
|
|
|
/** @type {Keybinding} */
|
|
|
|
const binding = this.keybindings[key];
|
2020-05-28 12:53:11 +00:00
|
|
|
if (binding.keyCode === keyCode && (initial || binding.repeated)) {
|
2020-05-09 14:45:23 +00:00
|
|
|
/** @type {Signal} */
|
|
|
|
const signal = this.keybindings[key].signal;
|
|
|
|
if (signal.dispatch() === STOP_PROPAGATION) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stop) {
|
|
|
|
return STOP_PROPAGATION;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal keyup handler
|
|
|
|
* @param {object} param0
|
|
|
|
* @param {number} param0.keyCode
|
|
|
|
* @param {boolean} param0.shift
|
|
|
|
* @param {boolean} param0.alt
|
|
|
|
*/
|
|
|
|
handleKeyup({ keyCode, shift, alt }) {
|
2020-05-28 12:53:11 +00:00
|
|
|
// Empty
|
2020-05-09 14:45:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a given keybinding
|
2020-05-19 07:14:40 +00:00
|
|
|
* @param {{ keyCode: number }} binding
|
2020-05-09 14:45:23 +00:00
|
|
|
* @returns {Keybinding}
|
|
|
|
*/
|
2020-05-19 07:14:40 +00:00
|
|
|
getBinding(binding) {
|
|
|
|
// @ts-ignore
|
|
|
|
const id = binding.id;
|
2020-06-27 08:51:52 +00:00
|
|
|
assert(id, "Not a valid keybinding: " + JSON.stringify(binding));
|
2020-05-09 14:45:23 +00:00
|
|
|
assert(this.keybindings[id], "Keybinding " + id + " not known!");
|
|
|
|
return this.keybindings[id];
|
|
|
|
}
|
|
|
|
}
|