import { LitElement, html } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { GameEnv } from "../../../core/configuration/Config"; import { GameType } from "../../../core/game/Game"; import { GameView } from "../../../core/game/GameView"; import { MultiTabDetector } from "../../MultiTabDetector"; import { translateText } from "../../Utils"; import { Layer } from "./Layer"; @customElement("multi-tab-modal") export class MultiTabModal extends LitElement implements Layer { public game: GameView; private detector: MultiTabDetector; @property({ type: Number }) duration: number = 5000; @state() private countdown: number = 5; @state() private isVisible: boolean = false; @state() private fakeIp: string = ""; @state() private deviceFingerprint: string = ""; @state() private reported: boolean = true; private intervalId?: number; // Disable shadow DOM to allow Tailwind classes to work createRenderRoot() { return this; } tick() { if ( this.game.inSpawnPhase() || this.game.config().gameConfig().gameType === GameType.Singleplayer || this.game.config().serverConfig().env() === GameEnv.Dev || this.game.config().isReplay() ) { return; } if (!this.detector) { this.detector = new MultiTabDetector(); this.detector.startMonitoring((duration: number) => { this.show(duration); }); } } init() { this.fakeIp = this.generateFakeIp(); this.deviceFingerprint = this.generateDeviceFingerprint(); this.reported = true; } // Generate fake IP in format xxx.xxx.xxx.xxx private generateFakeIp(): string { return Array.from({ length: 4 }, () => Math.floor(Math.random() * 255), ).join("."); } // Generate fake device fingerprint (32 character hex) private generateDeviceFingerprint(): string { return Array.from({ length: 32 }, () => Math.floor(Math.random() * 16).toString(16), ).join(""); } // Show the modal with penalty information public show(duration: number): void { if (!this.game.myPlayer()?.isAlive()) { return; } this.duration = duration; this.countdown = Math.ceil(duration / 1000); this.isVisible = true; // Start countdown timer this.intervalId = window.setInterval(() => { this.countdown--; if (this.countdown <= 0) { this.hide(); } }, 1000); this.requestUpdate(); } // Hide the modal public hide(): void { this.isVisible = false; if (this.intervalId) { window.clearInterval(this.intervalId); this.intervalId = undefined; } // Dispatch event when modal is closed this.dispatchEvent( new CustomEvent("penalty-complete", { bubbles: true, composed: true, }), ); this.requestUpdate(); } disconnectedCallback() { super.disconnectedCallback(); if (this.intervalId) { window.clearInterval(this.intervalId); } } render() { if (!this.isVisible) { return html``; } return html`

${translateText("multi_tab.warning")}

RECORDING

${translateText("multi_tab.detected")}

IP: ${this.fakeIp}
Device Fingerprint: ${this.deviceFingerprint}
Reported: ${this.reported ? "TRUE" : "FALSE"}

${translateText("multi_tab.please_wait")} ${this.countdown} ${translateText("multi_tab.seconds")}

${translateText("multi_tab.explanation")}

Repeated violations may result in permanent account suspension.

`; } }