mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 17:36:44 +00:00
f7598369ed
## Description: This PR consolidates ad hoc platform/environment/viewport detection into a single shared utility. It is scoped to this refactor only, and serves as groundwork for the mobile-focused feature work planned for the v31 milestone. ### What changed - Introduced a shared `Platform` utility centralising: - OS detection (with `userAgentData` + UA fallback) - Electron environment detection - Viewport breakpoint helpers (`isMobileWidth`, `isTabletWidth`, `isDesktopWidth`) - Replaced duplicated inline checks across client files with the shared API. - Normalised Mac detection to derive from the consolidated OS logic rather than a separate regex. ### Why - Multiple client files each independently ran `navigator.userAgent` regexes or copy-pasted `isElectron` logic — this unifies all of that. - Puts a stable, tested abstraction in place before v31 mobile work lands, so mobile feature branches have a consistent surface to build against. ## Please complete the following: - [x] I have added screenshots for all UI updates (N/A: refactor only, no visible UI changes) - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file (N/A: no new user-facing strings) - [x] I have added relevant tests to the test directory (N/A: refactor only) - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: skigim
717 lines
20 KiB
TypeScript
717 lines
20 KiB
TypeScript
import { EventBus, GameEvent } from "../core/EventBus";
|
|
import { UnitType } from "../core/game/Game";
|
|
import { UnitView } from "../core/game/GameView";
|
|
import { UserSettings } from "../core/game/UserSettings";
|
|
import { UIState } from "./graphics/UIState";
|
|
import { Platform } from "./Platform";
|
|
import { ReplaySpeedMultiplier } from "./utilities/ReplaySpeedMultiplier";
|
|
|
|
export class MouseUpEvent implements GameEvent {
|
|
constructor(
|
|
public readonly x: number,
|
|
public readonly y: number,
|
|
) {}
|
|
}
|
|
|
|
export class MouseOverEvent implements GameEvent {
|
|
constructor(
|
|
public readonly x: number,
|
|
public readonly y: number,
|
|
) {}
|
|
}
|
|
export class TouchEvent implements GameEvent {
|
|
constructor(
|
|
public readonly x: number,
|
|
public readonly y: number,
|
|
) {}
|
|
}
|
|
|
|
/**
|
|
* Event emitted when a unit is selected or deselected
|
|
*/
|
|
export class UnitSelectionEvent implements GameEvent {
|
|
constructor(
|
|
public readonly unit: UnitView | null,
|
|
public readonly isSelected: boolean,
|
|
) {}
|
|
}
|
|
|
|
export class MouseDownEvent implements GameEvent {
|
|
constructor(
|
|
public readonly x: number,
|
|
public readonly y: number,
|
|
) {}
|
|
}
|
|
|
|
export class MouseMoveEvent implements GameEvent {
|
|
constructor(
|
|
public readonly x: number,
|
|
public readonly y: number,
|
|
) {}
|
|
}
|
|
|
|
export class ContextMenuEvent implements GameEvent {
|
|
constructor(
|
|
public readonly x: number,
|
|
public readonly y: number,
|
|
) {}
|
|
}
|
|
|
|
export class ZoomEvent implements GameEvent {
|
|
constructor(
|
|
public readonly x: number,
|
|
public readonly y: number,
|
|
public readonly delta: number,
|
|
) {}
|
|
}
|
|
|
|
export class DragEvent implements GameEvent {
|
|
constructor(
|
|
public readonly deltaX: number,
|
|
public readonly deltaY: number,
|
|
) {}
|
|
}
|
|
|
|
export class AlternateViewEvent implements GameEvent {
|
|
constructor(public readonly alternateView: boolean) {}
|
|
}
|
|
|
|
export class CloseViewEvent implements GameEvent {}
|
|
|
|
export class RefreshGraphicsEvent implements GameEvent {}
|
|
|
|
export class TogglePerformanceOverlayEvent implements GameEvent {}
|
|
|
|
export class ToggleStructureEvent implements GameEvent {
|
|
constructor(public readonly structureTypes: UnitType[] | null) {}
|
|
}
|
|
|
|
export class GhostStructureChangedEvent implements GameEvent {
|
|
constructor(public readonly ghostStructure: UnitType | null) {}
|
|
}
|
|
|
|
export class SwapRocketDirectionEvent implements GameEvent {
|
|
constructor(public readonly rocketDirectionUp: boolean) {}
|
|
}
|
|
|
|
export class ShowBuildMenuEvent implements GameEvent {
|
|
constructor(
|
|
public readonly x: number,
|
|
public readonly y: number,
|
|
) {}
|
|
}
|
|
export class ShowEmojiMenuEvent implements GameEvent {
|
|
constructor(
|
|
public readonly x: number,
|
|
public readonly y: number,
|
|
) {}
|
|
}
|
|
|
|
export class DoBoatAttackEvent implements GameEvent {}
|
|
|
|
export class DoGroundAttackEvent implements GameEvent {}
|
|
|
|
export class AttackRatioEvent implements GameEvent {
|
|
constructor(public readonly attackRatio: number) {}
|
|
}
|
|
|
|
export class ReplaySpeedChangeEvent implements GameEvent {
|
|
constructor(public readonly replaySpeedMultiplier: ReplaySpeedMultiplier) {}
|
|
}
|
|
|
|
export class CenterCameraEvent implements GameEvent {
|
|
constructor() {}
|
|
}
|
|
|
|
export class AutoUpgradeEvent implements GameEvent {
|
|
constructor(
|
|
public readonly x: number,
|
|
public readonly y: number,
|
|
) {}
|
|
}
|
|
|
|
export class ToggleCoordinateGridEvent implements GameEvent {
|
|
constructor(public readonly enabled: boolean) {}
|
|
}
|
|
|
|
export class TickMetricsEvent implements GameEvent {
|
|
constructor(
|
|
public readonly tickExecutionDuration?: number,
|
|
public readonly tickDelay?: number,
|
|
) {}
|
|
}
|
|
|
|
export class InputHandler {
|
|
private lastPointerX: number = 0;
|
|
private lastPointerY: number = 0;
|
|
|
|
private lastPointerDownX: number = 0;
|
|
private lastPointerDownY: number = 0;
|
|
|
|
private pointers: Map<number, PointerEvent> = new Map();
|
|
|
|
private lastPinchDistance: number = 0;
|
|
|
|
private pointerDown: boolean = false;
|
|
|
|
private alternateView = false;
|
|
|
|
private moveInterval: NodeJS.Timeout | null = null;
|
|
private activeKeys = new Set<string>();
|
|
private keybinds: Record<string, string> = {};
|
|
private coordinateGridEnabled = false;
|
|
|
|
private readonly PAN_SPEED = 5;
|
|
private readonly ZOOM_SPEED = 10;
|
|
|
|
private readonly userSettings: UserSettings = new UserSettings();
|
|
|
|
constructor(
|
|
public uiState: UIState,
|
|
private canvas: HTMLCanvasElement,
|
|
private eventBus: EventBus,
|
|
) {}
|
|
|
|
initialize() {
|
|
let saved: Record<string, string> = {};
|
|
try {
|
|
const parsed = JSON.parse(
|
|
localStorage.getItem("settings.keybinds") ?? "{}",
|
|
);
|
|
// flatten { key: {key, value} } → { key: value } and accept legacy string values
|
|
saved = Object.fromEntries(
|
|
Object.entries(parsed)
|
|
.map(([k, v]) => {
|
|
// Extract value from nested object or plain string
|
|
let val: unknown;
|
|
if (v && typeof v === "object" && "value" in v) {
|
|
val = (v as { value: unknown }).value;
|
|
} else {
|
|
val = v;
|
|
}
|
|
|
|
// Map invalid values to undefined (filtered later)
|
|
if (typeof val !== "string") {
|
|
return [k, undefined];
|
|
}
|
|
return [k, val];
|
|
})
|
|
.filter(([, v]) => typeof v === "string"),
|
|
) as Record<string, string>;
|
|
} catch (e) {
|
|
console.warn("Invalid keybinds JSON:", e);
|
|
}
|
|
|
|
// Mac users might have different keybinds
|
|
const isMac = Platform.isMac;
|
|
|
|
this.keybinds = {
|
|
toggleView: "Space",
|
|
coordinateGrid: "KeyM",
|
|
centerCamera: "KeyC",
|
|
moveUp: "KeyW",
|
|
moveDown: "KeyS",
|
|
moveLeft: "KeyA",
|
|
moveRight: "KeyD",
|
|
zoomOut: "KeyQ",
|
|
zoomIn: "KeyE",
|
|
attackRatioDown: "KeyT",
|
|
attackRatioUp: "KeyY",
|
|
boatAttack: "KeyB",
|
|
groundAttack: "KeyG",
|
|
swapDirection: "KeyU",
|
|
modifierKey: isMac ? "MetaLeft" : "ControlLeft",
|
|
altKey: "AltLeft",
|
|
buildCity: "Digit1",
|
|
buildFactory: "Digit2",
|
|
buildPort: "Digit3",
|
|
buildDefensePost: "Digit4",
|
|
buildMissileSilo: "Digit5",
|
|
buildSamLauncher: "Digit6",
|
|
buildWarship: "Digit7",
|
|
buildAtomBomb: "Digit8",
|
|
buildHydrogenBomb: "Digit9",
|
|
buildMIRV: "Digit0",
|
|
...saved,
|
|
};
|
|
|
|
this.canvas.addEventListener("pointerdown", (e) => this.onPointerDown(e));
|
|
window.addEventListener("pointerup", (e) => this.onPointerUp(e));
|
|
this.canvas.addEventListener(
|
|
"wheel",
|
|
(e) => {
|
|
this.onScroll(e);
|
|
this.onShiftScroll(e);
|
|
e.preventDefault();
|
|
},
|
|
{ passive: false },
|
|
);
|
|
window.addEventListener("pointermove", this.onPointerMove.bind(this));
|
|
this.canvas.addEventListener("contextmenu", (e) => this.onContextMenu(e));
|
|
window.addEventListener("mousemove", (e) => {
|
|
if (e.movementX || e.movementY) {
|
|
this.eventBus.emit(new MouseMoveEvent(e.clientX, e.clientY));
|
|
}
|
|
});
|
|
this.pointers.clear();
|
|
|
|
this.moveInterval = setInterval(() => {
|
|
let deltaX = 0;
|
|
let deltaY = 0;
|
|
|
|
// Skip if shift is held down
|
|
if (
|
|
this.activeKeys.has("ShiftLeft") ||
|
|
this.activeKeys.has("ShiftRight")
|
|
) {
|
|
return;
|
|
}
|
|
|
|
if (
|
|
this.activeKeys.has(this.keybinds.moveUp) ||
|
|
this.activeKeys.has("ArrowUp")
|
|
)
|
|
deltaY += this.PAN_SPEED;
|
|
if (
|
|
this.activeKeys.has(this.keybinds.moveDown) ||
|
|
this.activeKeys.has("ArrowDown")
|
|
)
|
|
deltaY -= this.PAN_SPEED;
|
|
if (
|
|
this.activeKeys.has(this.keybinds.moveLeft) ||
|
|
this.activeKeys.has("ArrowLeft")
|
|
)
|
|
deltaX += this.PAN_SPEED;
|
|
if (
|
|
this.activeKeys.has(this.keybinds.moveRight) ||
|
|
this.activeKeys.has("ArrowRight")
|
|
)
|
|
deltaX -= this.PAN_SPEED;
|
|
|
|
if (deltaX || deltaY) {
|
|
this.eventBus.emit(new DragEvent(deltaX, deltaY));
|
|
}
|
|
|
|
const cx = window.innerWidth / 2;
|
|
const cy = window.innerHeight / 2;
|
|
|
|
if (
|
|
this.activeKeys.has(this.keybinds.zoomOut) ||
|
|
this.activeKeys.has("Minus")
|
|
) {
|
|
this.eventBus.emit(new ZoomEvent(cx, cy, this.ZOOM_SPEED));
|
|
}
|
|
if (
|
|
this.activeKeys.has(this.keybinds.zoomIn) ||
|
|
this.activeKeys.has("Equal")
|
|
) {
|
|
this.eventBus.emit(new ZoomEvent(cx, cy, -this.ZOOM_SPEED));
|
|
}
|
|
}, 1);
|
|
|
|
window.addEventListener("keydown", (e) => {
|
|
const isTextInput = this.isTextInputTarget(e.target);
|
|
if (isTextInput && e.code !== "Escape") {
|
|
return;
|
|
}
|
|
|
|
if (e.code === this.keybinds.toggleView) {
|
|
e.preventDefault();
|
|
if (!this.alternateView) {
|
|
this.alternateView = true;
|
|
this.eventBus.emit(new AlternateViewEvent(true));
|
|
}
|
|
}
|
|
|
|
if (e.code === this.keybinds.coordinateGrid && !e.repeat) {
|
|
e.preventDefault();
|
|
this.coordinateGridEnabled = !this.coordinateGridEnabled;
|
|
this.eventBus.emit(
|
|
new ToggleCoordinateGridEvent(this.coordinateGridEnabled),
|
|
);
|
|
}
|
|
|
|
if (e.code === "Escape") {
|
|
e.preventDefault();
|
|
this.eventBus.emit(new CloseViewEvent());
|
|
this.setGhostStructure(null);
|
|
}
|
|
|
|
if (
|
|
[
|
|
this.keybinds.moveUp,
|
|
this.keybinds.moveDown,
|
|
this.keybinds.moveLeft,
|
|
this.keybinds.moveRight,
|
|
this.keybinds.zoomOut,
|
|
this.keybinds.zoomIn,
|
|
"ArrowUp",
|
|
"ArrowLeft",
|
|
"ArrowDown",
|
|
"ArrowRight",
|
|
"Minus",
|
|
"Equal",
|
|
this.keybinds.attackRatioDown,
|
|
this.keybinds.attackRatioUp,
|
|
this.keybinds.centerCamera,
|
|
"ControlLeft",
|
|
"ControlRight",
|
|
"ShiftLeft",
|
|
"ShiftRight",
|
|
].includes(e.code)
|
|
) {
|
|
this.activeKeys.add(e.code);
|
|
}
|
|
});
|
|
window.addEventListener("keyup", (e) => {
|
|
const isTextInput = this.isTextInputTarget(e.target);
|
|
if (isTextInput && !this.activeKeys.has(e.code)) {
|
|
return;
|
|
}
|
|
|
|
if (e.code === this.keybinds.toggleView) {
|
|
e.preventDefault();
|
|
this.alternateView = false;
|
|
this.eventBus.emit(new AlternateViewEvent(false));
|
|
}
|
|
|
|
const resetKey = this.keybinds.resetGfx ?? "KeyR";
|
|
if (e.code === resetKey && this.isAltKeyHeld(e)) {
|
|
e.preventDefault();
|
|
this.eventBus.emit(new RefreshGraphicsEvent());
|
|
}
|
|
|
|
if (e.code === this.keybinds.boatAttack) {
|
|
e.preventDefault();
|
|
this.eventBus.emit(new DoBoatAttackEvent());
|
|
}
|
|
|
|
if (e.code === this.keybinds.groundAttack) {
|
|
e.preventDefault();
|
|
this.eventBus.emit(new DoGroundAttackEvent());
|
|
}
|
|
|
|
if (e.code === this.keybinds.attackRatioDown) {
|
|
e.preventDefault();
|
|
const increment = this.userSettings.attackRatioIncrement();
|
|
this.eventBus.emit(new AttackRatioEvent(-increment));
|
|
}
|
|
|
|
if (e.code === this.keybinds.attackRatioUp) {
|
|
e.preventDefault();
|
|
const increment = this.userSettings.attackRatioIncrement();
|
|
this.eventBus.emit(new AttackRatioEvent(increment));
|
|
}
|
|
|
|
if (e.code === this.keybinds.centerCamera) {
|
|
e.preventDefault();
|
|
this.eventBus.emit(new CenterCameraEvent());
|
|
}
|
|
|
|
if (e.code === this.keybinds.buildCity) {
|
|
e.preventDefault();
|
|
this.setGhostStructure(UnitType.City);
|
|
}
|
|
|
|
if (e.code === this.keybinds.buildFactory) {
|
|
e.preventDefault();
|
|
this.setGhostStructure(UnitType.Factory);
|
|
}
|
|
|
|
if (e.code === this.keybinds.buildPort) {
|
|
e.preventDefault();
|
|
this.setGhostStructure(UnitType.Port);
|
|
}
|
|
|
|
if (e.code === this.keybinds.buildDefensePost) {
|
|
e.preventDefault();
|
|
this.setGhostStructure(UnitType.DefensePost);
|
|
}
|
|
|
|
if (e.code === this.keybinds.buildMissileSilo) {
|
|
e.preventDefault();
|
|
this.setGhostStructure(UnitType.MissileSilo);
|
|
}
|
|
|
|
if (e.code === this.keybinds.buildSamLauncher) {
|
|
e.preventDefault();
|
|
this.setGhostStructure(UnitType.SAMLauncher);
|
|
}
|
|
|
|
if (e.code === this.keybinds.buildAtomBomb) {
|
|
e.preventDefault();
|
|
this.setGhostStructure(UnitType.AtomBomb);
|
|
}
|
|
|
|
if (e.code === this.keybinds.buildHydrogenBomb) {
|
|
e.preventDefault();
|
|
this.setGhostStructure(UnitType.HydrogenBomb);
|
|
}
|
|
|
|
if (e.code === this.keybinds.buildWarship) {
|
|
e.preventDefault();
|
|
this.setGhostStructure(UnitType.Warship);
|
|
}
|
|
|
|
if (e.code === this.keybinds.buildMIRV) {
|
|
e.preventDefault();
|
|
this.setGhostStructure(UnitType.MIRV);
|
|
}
|
|
|
|
if (e.code === this.keybinds.swapDirection) {
|
|
e.preventDefault();
|
|
const nextDirection = !this.uiState.rocketDirectionUp;
|
|
this.eventBus.emit(new SwapRocketDirectionEvent(nextDirection));
|
|
}
|
|
|
|
// Shift-D to toggle performance overlay
|
|
console.log(e.code, e.shiftKey, e.ctrlKey, e.altKey, e.metaKey);
|
|
if (e.code === "KeyD" && e.shiftKey) {
|
|
e.preventDefault();
|
|
console.log("TogglePerformanceOverlayEvent");
|
|
this.eventBus.emit(new TogglePerformanceOverlayEvent());
|
|
}
|
|
|
|
this.activeKeys.delete(e.code);
|
|
});
|
|
}
|
|
|
|
private onPointerDown(event: PointerEvent) {
|
|
if (event.button === 1) {
|
|
event.preventDefault();
|
|
this.eventBus.emit(new AutoUpgradeEvent(event.clientX, event.clientY));
|
|
return;
|
|
}
|
|
|
|
if (event.button > 0) {
|
|
return;
|
|
}
|
|
|
|
this.pointerDown = true;
|
|
this.pointers.set(event.pointerId, event);
|
|
|
|
if (this.pointers.size === 1) {
|
|
this.lastPointerX = event.clientX;
|
|
this.lastPointerY = event.clientY;
|
|
|
|
this.lastPointerDownX = event.clientX;
|
|
this.lastPointerDownY = event.clientY;
|
|
|
|
this.eventBus.emit(new MouseDownEvent(event.clientX, event.clientY));
|
|
} else if (this.pointers.size === 2) {
|
|
this.lastPinchDistance = this.getPinchDistance();
|
|
}
|
|
}
|
|
|
|
onPointerUp(event: PointerEvent) {
|
|
if (event.button === 1) {
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
|
|
if (event.button > 0) {
|
|
return;
|
|
}
|
|
this.pointerDown = false;
|
|
this.pointers.clear();
|
|
|
|
if (this.isModifierKeyPressed(event)) {
|
|
this.eventBus.emit(new ShowBuildMenuEvent(event.clientX, event.clientY));
|
|
return;
|
|
}
|
|
if (this.isAltKeyPressed(event)) {
|
|
this.eventBus.emit(new ShowEmojiMenuEvent(event.clientX, event.clientY));
|
|
return;
|
|
}
|
|
|
|
const dist =
|
|
Math.abs(event.x - this.lastPointerDownX) +
|
|
Math.abs(event.y - this.lastPointerDownY);
|
|
if (dist < 10) {
|
|
if (event.pointerType === "touch") {
|
|
this.eventBus.emit(new TouchEvent(event.x, event.y));
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
|
|
if (!this.userSettings.leftClickOpensMenu() || event.shiftKey) {
|
|
this.eventBus.emit(new MouseUpEvent(event.x, event.y));
|
|
} else {
|
|
this.eventBus.emit(new ContextMenuEvent(event.clientX, event.clientY));
|
|
}
|
|
}
|
|
}
|
|
|
|
private onScroll(event: WheelEvent) {
|
|
if (!event.shiftKey) {
|
|
const realCtrl =
|
|
this.activeKeys.has("ControlLeft") ||
|
|
this.activeKeys.has("ControlRight");
|
|
const ratio = event.ctrlKey && !realCtrl ? 10 : 1; // Compensate pinch-zoom low sensitivity
|
|
this.eventBus.emit(new ZoomEvent(event.x, event.y, event.deltaY * ratio));
|
|
}
|
|
}
|
|
|
|
private onShiftScroll(event: WheelEvent) {
|
|
if (event.shiftKey) {
|
|
const scrollValue = event.deltaY === 0 ? event.deltaX : event.deltaY;
|
|
const increment = this.userSettings.attackRatioIncrement();
|
|
const ratio = scrollValue > 0 ? -increment : increment;
|
|
this.eventBus.emit(new AttackRatioEvent(ratio));
|
|
}
|
|
}
|
|
|
|
private onPointerMove(event: PointerEvent) {
|
|
if (event.button === 1) {
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
|
|
if (event.button > 0) {
|
|
return;
|
|
}
|
|
|
|
this.pointers.set(event.pointerId, event);
|
|
|
|
if (!this.pointerDown) {
|
|
this.eventBus.emit(new MouseOverEvent(event.clientX, event.clientY));
|
|
return;
|
|
}
|
|
|
|
if (this.pointers.size === 1) {
|
|
const deltaX = event.clientX - this.lastPointerX;
|
|
const deltaY = event.clientY - this.lastPointerY;
|
|
|
|
this.eventBus.emit(new DragEvent(deltaX, deltaY));
|
|
|
|
this.lastPointerX = event.clientX;
|
|
this.lastPointerY = event.clientY;
|
|
} else if (this.pointers.size === 2) {
|
|
const currentPinchDistance = this.getPinchDistance();
|
|
const pinchDelta = currentPinchDistance - this.lastPinchDistance;
|
|
|
|
if (Math.abs(pinchDelta) > 1) {
|
|
const zoomCenter = this.getPinchCenter();
|
|
this.eventBus.emit(
|
|
new ZoomEvent(zoomCenter.x, zoomCenter.y, -pinchDelta * 2),
|
|
);
|
|
this.lastPinchDistance = currentPinchDistance;
|
|
}
|
|
}
|
|
}
|
|
|
|
private onContextMenu(event: MouseEvent) {
|
|
event.preventDefault();
|
|
if (this.uiState.ghostStructure !== null) {
|
|
this.setGhostStructure(null);
|
|
return;
|
|
}
|
|
this.eventBus.emit(new ContextMenuEvent(event.clientX, event.clientY));
|
|
}
|
|
|
|
private setGhostStructure(ghostStructure: UnitType | null) {
|
|
this.uiState.ghostStructure = ghostStructure;
|
|
this.eventBus.emit(new GhostStructureChangedEvent(ghostStructure));
|
|
}
|
|
|
|
private getPinchDistance(): number {
|
|
const pointerEvents = Array.from(this.pointers.values());
|
|
const dx = pointerEvents[0].clientX - pointerEvents[1].clientX;
|
|
const dy = pointerEvents[0].clientY - pointerEvents[1].clientY;
|
|
return Math.sqrt(dx * dx + dy * dy);
|
|
}
|
|
|
|
private getPinchCenter(): { x: number; y: number } {
|
|
const pointerEvents = Array.from(this.pointers.values());
|
|
return {
|
|
x: (pointerEvents[0].clientX + pointerEvents[1].clientX) / 2,
|
|
y: (pointerEvents[0].clientY + pointerEvents[1].clientY) / 2,
|
|
};
|
|
}
|
|
|
|
private isTextInputTarget(target: EventTarget | null): boolean {
|
|
const element = target as HTMLElement | null;
|
|
if (!element) return false;
|
|
if (element.tagName === "TEXTAREA" || element.isContentEditable) {
|
|
return true;
|
|
}
|
|
if (element.tagName === "INPUT") {
|
|
const input = element as HTMLInputElement;
|
|
if (input.id === "attack-ratio" && input.type === "range") {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
destroy() {
|
|
if (this.moveInterval !== null) {
|
|
clearInterval(this.moveInterval);
|
|
}
|
|
this.activeKeys.clear();
|
|
}
|
|
|
|
isModifierKeyPressed(event: PointerEvent): boolean {
|
|
return (
|
|
((this.keybinds.modifierKey === "AltLeft" ||
|
|
this.keybinds.modifierKey === "AltRight") &&
|
|
event.altKey) ||
|
|
((this.keybinds.modifierKey === "ControlLeft" ||
|
|
this.keybinds.modifierKey === "ControlRight") &&
|
|
event.ctrlKey) ||
|
|
((this.keybinds.modifierKey === "ShiftLeft" ||
|
|
this.keybinds.modifierKey === "ShiftRight") &&
|
|
event.shiftKey) ||
|
|
((this.keybinds.modifierKey === "MetaLeft" ||
|
|
this.keybinds.modifierKey === "MetaRight") &&
|
|
event.metaKey)
|
|
);
|
|
}
|
|
|
|
private isAltKeyHeld(event: KeyboardEvent): boolean {
|
|
if (
|
|
this.keybinds.altKey === "AltLeft" ||
|
|
this.keybinds.altKey === "AltRight"
|
|
) {
|
|
return event.altKey && !event.ctrlKey;
|
|
}
|
|
if (
|
|
this.keybinds.altKey === "ControlLeft" ||
|
|
this.keybinds.altKey === "ControlRight"
|
|
) {
|
|
return event.ctrlKey;
|
|
}
|
|
if (
|
|
this.keybinds.altKey === "ShiftLeft" ||
|
|
this.keybinds.altKey === "ShiftRight"
|
|
) {
|
|
return event.shiftKey;
|
|
}
|
|
if (
|
|
this.keybinds.altKey === "MetaLeft" ||
|
|
this.keybinds.altKey === "MetaRight"
|
|
) {
|
|
return event.metaKey;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
isAltKeyPressed(event: PointerEvent): boolean {
|
|
return (
|
|
((this.keybinds.altKey === "AltLeft" ||
|
|
this.keybinds.altKey === "AltRight") &&
|
|
event.altKey) ||
|
|
((this.keybinds.altKey === "ControlLeft" ||
|
|
this.keybinds.altKey === "ControlRight") &&
|
|
event.ctrlKey) ||
|
|
((this.keybinds.altKey === "ShiftLeft" ||
|
|
this.keybinds.altKey === "ShiftRight") &&
|
|
event.shiftKey) ||
|
|
((this.keybinds.altKey === "MetaLeft" ||
|
|
this.keybinds.altKey === "MetaRight") &&
|
|
event.metaKey)
|
|
);
|
|
}
|
|
}
|