implement player list overlay, version 1.1.7
This commit is contained in:
@@ -6,6 +6,7 @@ export default class GameSettings {
|
||||
this.keyTogglePerspective = 'F5';
|
||||
this.keyOpenChat = 'KeyT';
|
||||
this.keyOpenInventory = 'KeyE';
|
||||
this.keyPlayerList = 'Tab';
|
||||
|
||||
this.thirdPersonView = 0;
|
||||
this.fov = 70;
|
||||
|
||||
@@ -52,6 +52,7 @@ export default class GameWindow {
|
||||
// Create render layers
|
||||
this.canvasWorld = document.createElement('canvas');
|
||||
this.canvasDebug = document.createElement('canvas');
|
||||
this.canvasPlayerList = document.createElement('canvas');
|
||||
this.canvasItems = document.createElement('canvas');
|
||||
|
||||
// Create canvas renderer
|
||||
@@ -380,6 +381,11 @@ export default class GameWindow {
|
||||
this.canvasDebug.height = this.canvas.height;
|
||||
}
|
||||
|
||||
if (this.canvasPlayerList.width !== this.canvas.width || this.canvasPlayerList.height !== this.canvas.height) {
|
||||
this.canvasPlayerList.width = this.canvas.width;
|
||||
this.canvasPlayerList.height = this.canvas.height;
|
||||
}
|
||||
|
||||
// Reinitialize gui
|
||||
this.minecraft.screenRenderer.initialize();
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ export default class Minecraft {
|
||||
this.maxFps = 0;
|
||||
|
||||
let username = "Player" + Math.floor(Math.random() * 100);
|
||||
let profile = new GameProfile(username, UUID.randomUUID());
|
||||
let profile = new GameProfile(UUID.randomUUID(), username);
|
||||
this.session = new Session(profile, "");
|
||||
|
||||
// Tick timer
|
||||
|
||||
@@ -6,6 +6,8 @@ import EnumBlockFace from "../../../util/EnumBlockFace.js";
|
||||
import MathHelper from "../../../util/MathHelper.js";
|
||||
import FontRenderer from "../../render/gui/FontRenderer.js";
|
||||
import EnumSkyBlock from "../../../util/EnumSkyBlock.js";
|
||||
import PlayerListOverlay from "./PlayerListOverlay.js";
|
||||
import Keyboard from "../../../util/Keyboard.js";
|
||||
|
||||
export default class IngameOverlay extends Gui {
|
||||
|
||||
@@ -15,6 +17,7 @@ export default class IngameOverlay extends Gui {
|
||||
this.window = window;
|
||||
|
||||
this.chatOverlay = new ChatOverlay(minecraft);
|
||||
this.playerListOverlay = new PlayerListOverlay(minecraft, this);
|
||||
|
||||
this.textureCrosshair = minecraft.resources["gui/icons.png"];
|
||||
this.textureHotbar = minecraft.resources["gui/gui.png"];
|
||||
@@ -38,6 +41,11 @@ export default class IngameOverlay extends Gui {
|
||||
if (this.minecraft.settings.debugOverlay) {
|
||||
stack.drawImage(this.window.canvasDebug, 0, 0);
|
||||
}
|
||||
|
||||
// Render player list
|
||||
if (Keyboard.isKeyDown(this.minecraft.settings.keyPlayerList) && !this.minecraft.isSingleplayer()) {
|
||||
this.playerListOverlay.renderPlayerList(stack, this.window.width);
|
||||
}
|
||||
}
|
||||
|
||||
onTick() {
|
||||
@@ -224,6 +232,7 @@ export default class IngameOverlay extends Gui {
|
||||
// Draw line
|
||||
this.drawString(stack, lines[i], 2, 2 + FontRenderer.FONT_HEIGHT * i, 0xffe0e0e0, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
renderRightDebugOverlay(stack) {
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
import Gui from "../Gui.js";
|
||||
import FontRenderer from "../../render/gui/FontRenderer.js";
|
||||
|
||||
export default class PlayerListOverlay extends Gui {
|
||||
|
||||
constructor(minecraft, ingameOverlay) {
|
||||
super();
|
||||
|
||||
this.minecraft = minecraft;
|
||||
this.window = minecraft.window;
|
||||
this.ingameOverlay = ingameOverlay;
|
||||
this.dirty = true;
|
||||
|
||||
this.header = null;
|
||||
this.footer = null;
|
||||
}
|
||||
|
||||
renderPlayerList(stack, width) {
|
||||
if (this.dirty) {
|
||||
let subStack = this.window.canvasPlayerList.getContext("2d");
|
||||
this.reinitialize(subStack, width);
|
||||
}
|
||||
|
||||
stack.drawImage(this.window.canvasPlayerList, 0, 0);
|
||||
}
|
||||
|
||||
reinitialize(stack, width) {
|
||||
this.dirty = false;
|
||||
|
||||
let playerInfoMap = this.minecraft.playerController.getNetworkHandler().getPlayerInfoMap();
|
||||
let maxPlayerNameWidth = 0;
|
||||
let maxScoreValueWidth = 0; // TODO
|
||||
|
||||
// Calculate max scoreboard width entry
|
||||
for (let [uuid, playerInfo] of playerInfoMap) {
|
||||
let displayName = playerInfo.displayName === null ? playerInfo.profile.getUsername() : playerInfo.displayName;
|
||||
let playerLength = this.getStringWidth(stack, displayName);
|
||||
|
||||
// Find max player length
|
||||
maxPlayerNameWidth = Math.max(maxPlayerNameWidth, playerLength);
|
||||
}
|
||||
|
||||
// Get the max width of the scoreboard
|
||||
let maxScoreWidth = 0; // TODO
|
||||
|
||||
let screenWidth = this.window.width;
|
||||
let playerAmount = playerInfoMap.size;
|
||||
let rows = playerAmount;
|
||||
|
||||
// Calculate max columns
|
||||
let columns;
|
||||
for (columns = 1; rows > 20; rows = Math.floor((playerAmount + columns - 1) / columns)) {
|
||||
columns++;
|
||||
}
|
||||
|
||||
let isOnlineMode = true; // TODO
|
||||
let columnWidth = Math.min(
|
||||
columns * ((isOnlineMode ? 9 : 0) + maxPlayerNameWidth + maxScoreWidth + 13),
|
||||
screenWidth - 50
|
||||
) / columns;
|
||||
|
||||
// Clear canvas
|
||||
stack.clearRect(0, 0, this.window.width, this.window.height);
|
||||
|
||||
let x = Math.floor(screenWidth / 2) - Math.floor((columnWidth * columns + (columns - 1) * 5) / 2);
|
||||
let y = 10;
|
||||
|
||||
// Calculate background with of columns
|
||||
let backgroundWidth = columnWidth * columns + (columns - 1) * 5;
|
||||
|
||||
// Calculate header
|
||||
let headerLines = null;
|
||||
if (this.header !== null) {
|
||||
headerLines = this.minecraft.fontRenderer.listFormattedStringToWidth(this.header, width - 50);
|
||||
|
||||
for (let line of headerLines) {
|
||||
backgroundWidth = Math.max(backgroundWidth, this.getStringWidth(stack, line));
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate footer
|
||||
let footerLines = null;
|
||||
if (this.footer !== null) {
|
||||
footerLines = this.minecraft.fontRenderer.listFormattedStringToWidth(this.footer, width - 50);
|
||||
|
||||
for (let line of footerLines) {
|
||||
backgroundWidth = Math.max(backgroundWidth, this.getStringWidth(stack, line));
|
||||
}
|
||||
}
|
||||
|
||||
if (headerLines !== null) {
|
||||
this.drawRect(
|
||||
stack,
|
||||
Math.floor(width / 2) - Math.floor(backgroundWidth / 2) - 1,
|
||||
y - 1,
|
||||
Math.floor(width / 2) + Math.floor(backgroundWidth / 2) + 1,
|
||||
y + headerLines.length * FontRenderer.FONT_HEIGHT,
|
||||
'rgba(0,0,0,0.5)'
|
||||
);
|
||||
|
||||
for (let i = 0; i < headerLines.length; i++) {
|
||||
this.drawCenteredString(
|
||||
stack,
|
||||
headerLines[i],
|
||||
Math.floor(width / 2) + 1,
|
||||
y
|
||||
);
|
||||
y += FontRenderer.FONT_HEIGHT;
|
||||
}
|
||||
y++;
|
||||
}
|
||||
|
||||
// Render player list background
|
||||
this.drawRect(
|
||||
stack,
|
||||
Math.floor(screenWidth / 2) - Math.floor(backgroundWidth / 2) - 1,
|
||||
y - 1,
|
||||
Math.floor(screenWidth / 2) + Math.floor(backgroundWidth / 2) + 1,
|
||||
y + rows * FontRenderer.FONT_HEIGHT,
|
||||
'rgba(0,0,0,0.5)'
|
||||
);
|
||||
|
||||
let i = 0;
|
||||
for (let [uuid, playerInfo] of playerInfoMap) {
|
||||
let indexX = Math.floor(i / rows);
|
||||
let indexY = i % rows;
|
||||
|
||||
let entryX = x + indexX * columnWidth + indexX * 5 - 1;
|
||||
let entryY = y + indexY * FontRenderer.FONT_HEIGHT;
|
||||
|
||||
let rightX = entryX + columnWidth - 1;
|
||||
|
||||
// Check if index is inside of range
|
||||
if (i < playerInfoMap.size) {
|
||||
let pingX = entryX + columnWidth - 1;
|
||||
let displayName = playerInfo.displayName === null ? playerInfo.profile.getUsername() : playerInfo.displayName;
|
||||
|
||||
// Render player entry background
|
||||
this.drawRect(
|
||||
stack,
|
||||
entryX,
|
||||
entryY,
|
||||
entryX + columnWidth,
|
||||
entryY + (FontRenderer.FONT_HEIGHT - 1),
|
||||
'rgba(255,255,255,0.13)'
|
||||
);
|
||||
|
||||
// Render player head
|
||||
if (isOnlineMode) {
|
||||
let size = FontRenderer.FONT_HEIGHT - 1;
|
||||
|
||||
this.drawRect(
|
||||
stack,
|
||||
entryX,
|
||||
entryY,
|
||||
entryX + size,
|
||||
entryY + size,
|
||||
'rgb(0,0,0)'
|
||||
);
|
||||
|
||||
entryX += size + 1;
|
||||
}
|
||||
|
||||
// Render player name
|
||||
this.drawString(stack, displayName, entryX + 1.0, entryY - (FontRenderer.FONT_HEIGHT > 9 ? 0.5 : 0.0))
|
||||
|
||||
// Render ping
|
||||
let ping = playerInfo.ping;
|
||||
let spriteOffset = ping < 0 ? 5 : ping < 150 ? 0 : ping < 300 ? 1 : ping < 600 ? 2 : ping < 1000 ? 3 : 4;
|
||||
this.drawSprite(
|
||||
stack,
|
||||
this.ingameOverlay.textureCrosshair,
|
||||
0,
|
||||
16 + spriteOffset * 8,
|
||||
10,
|
||||
8,
|
||||
pingX - FontRenderer.FONT_HEIGHT - 1,
|
||||
entryY + 1,
|
||||
10,
|
||||
8,
|
||||
'rgb(0,0,0)'
|
||||
);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
if (footerLines !== null) {
|
||||
y = y + rows * 9 + 1;
|
||||
|
||||
this.drawRect(
|
||||
stack,
|
||||
Math.floor(width / 2) - Math.floor(backgroundWidth / 2) - 1,
|
||||
y - 1,
|
||||
Math.floor(width / 2) + Math.floor(backgroundWidth / 2) + 1,
|
||||
y + footerLines.length * FontRenderer.FONT_HEIGHT,
|
||||
'rgba(0,0,0,0.5)'
|
||||
);
|
||||
|
||||
for (let i = 0; i < footerLines.length; i++) {
|
||||
this.drawCenteredString(
|
||||
stack,
|
||||
footerLines[i],
|
||||
Math.floor(width / 2) + 1,
|
||||
y
|
||||
);
|
||||
y += FontRenderer.FONT_HEIGHT;
|
||||
}
|
||||
y++;
|
||||
}
|
||||
}
|
||||
|
||||
setDirty() {
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
setHeader(header) {
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
setFooter(footer) {
|
||||
this.footer = footer;
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,8 @@ import ServerBlockChangePacket from "./packet/play/server/ServerBlockChangePacke
|
||||
import ServerChatPacket from "./packet/play/server/ServerChatPacket.js";
|
||||
import ServerDisconnectPacket from "./packet/play/server/ServerDisconnectPacket.js";
|
||||
import ServerPlayerPositionRotationPacket from "./packet/play/server/ServerPlayerPositionRotationPacket.js";
|
||||
import ServerPlayerListEntryPacket from "./packet/play/server/ServerPlayerListEntryPacket.js";
|
||||
import ServerPlayerListDataPacket from "./packet/play/server/ServerPlayerListDataPacket.js";
|
||||
|
||||
export default class PacketRegistry {
|
||||
|
||||
@@ -53,7 +55,9 @@ export default class PacketRegistry {
|
||||
this.registerServer(ProtocolState.PLAY, 0x21, ServerChunkDataPacket);
|
||||
this.registerServer(ProtocolState.PLAY, 0x23, ServerBlockChangePacket);
|
||||
this.registerServer(ProtocolState.PLAY, 0x26, ServerMultiChunkDataPacket);
|
||||
this.registerServer(ProtocolState.PLAY, 0x38, ServerPlayerListEntryPacket);
|
||||
this.registerServer(ProtocolState.PLAY, 0x40, ServerDisconnectPacket);
|
||||
this.registerServer(ProtocolState.PLAY, 0x47, ServerPlayerListDataPacket);
|
||||
|
||||
this.registerClient(ProtocolState.PLAY, 0x00, ClientKeepAlivePacket);
|
||||
this.registerClient(ProtocolState.PLAY, 0x01, ClientChatPacket);
|
||||
|
||||
@@ -13,6 +13,8 @@ export default class NetworkPlayHandler extends PacketHandler {
|
||||
this.minecraft = networkManager.minecraft;
|
||||
this.networkManager = networkManager;
|
||||
this.profile = profile;
|
||||
|
||||
this.playerInfoMap = new Map();
|
||||
}
|
||||
|
||||
handleKeepAlive(packet) {
|
||||
@@ -31,6 +33,46 @@ export default class NetworkPlayHandler extends PacketHandler {
|
||||
}
|
||||
}
|
||||
|
||||
handleServerPlayerListEntry(packet) {
|
||||
for (let entry of packet.getPlayers()) {
|
||||
let uuid = entry.profile.getId().toString();
|
||||
|
||||
if (packet.getAction() === 4) { // REMOVE_PLAYER
|
||||
this.playerInfoMap.delete(uuid);
|
||||
} else {
|
||||
if (packet.getAction() === 0) { // ADD_PLAYER
|
||||
this.playerInfoMap.set(uuid, entry);
|
||||
}
|
||||
|
||||
let playerInfo = this.playerInfoMap.get(uuid);
|
||||
if (playerInfo !== null && typeof playerInfo !== "undefined") {
|
||||
switch (packet.getAction()) {
|
||||
case 0: // ADD_PLAYER
|
||||
playerInfo.gameType = entry.gameType;
|
||||
playerInfo.ping = entry.ping;
|
||||
break;
|
||||
case 1: // UPDATE_GAMEMODE
|
||||
playerInfo.gameType = entry.gameType;
|
||||
break;
|
||||
case 2: // UPDATE_LATENCY
|
||||
playerInfo.ping = entry.ping;
|
||||
break;
|
||||
case 3: // UPDATE_DISPLAY_NAME
|
||||
playerInfo.displayName = entry.displayName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.minecraft.ingameOverlay.playerListOverlay.setDirty();
|
||||
}
|
||||
|
||||
handleServerPlayerListData(packet) {
|
||||
this.minecraft.ingameOverlay.playerListOverlay.setHeader(packet.getHeader());
|
||||
this.minecraft.ingameOverlay.playerListOverlay.setFooter(packet.getFooter());
|
||||
}
|
||||
|
||||
handleServerPlayerPositionRotation(packet) {
|
||||
let player = this.minecraft.player;
|
||||
|
||||
@@ -117,6 +159,10 @@ export default class NetworkPlayHandler extends PacketHandler {
|
||||
return this.networkManager;
|
||||
}
|
||||
|
||||
getPlayerInfoMap() {
|
||||
return this.playerInfoMap;
|
||||
}
|
||||
|
||||
sendPacket(packet) {
|
||||
this.networkManager.sendPacket(packet);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Packet from "../../../Packet.js";
|
||||
import {format} from "../../../../../../../../../libraries/chat.js";
|
||||
|
||||
export default class LoginDisconnectPacket extends Packet {
|
||||
|
||||
@@ -14,7 +13,7 @@ export default class LoginDisconnectPacket extends Packet {
|
||||
}
|
||||
|
||||
read(buffer) {
|
||||
this.message = format(JSON.parse(buffer.readString(32767)));
|
||||
this.message = buffer.readTextComponent();
|
||||
}
|
||||
|
||||
handle(handler) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Packet from "../../../Packet.js";
|
||||
import {format} from "../../../../../../../../../libraries/chat.js";
|
||||
|
||||
export default class ServerChatPacket extends Packet {
|
||||
|
||||
@@ -11,7 +10,7 @@ export default class ServerChatPacket extends Packet {
|
||||
}
|
||||
|
||||
read(buffer) {
|
||||
this.message = format(JSON.parse(buffer.readString(32767)));
|
||||
this.message = buffer.readTextComponent();
|
||||
this.type = buffer.readByte();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Packet from "../../../Packet.js";
|
||||
import {format} from "../../../../../../../../../libraries/chat.js";
|
||||
|
||||
export default class ServerDisconnectPacket extends Packet {
|
||||
|
||||
@@ -10,7 +9,7 @@ export default class ServerDisconnectPacket extends Packet {
|
||||
}
|
||||
|
||||
read(buffer) {
|
||||
this.reason = format(JSON.parse(buffer.readString(32767)));
|
||||
this.reason = buffer.readTextComponent();
|
||||
}
|
||||
|
||||
handle(handler) {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import Packet from "../../../Packet.js";
|
||||
|
||||
export default class ServerPlayerListDataPacket extends Packet {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.header = null;
|
||||
this.footer = null;
|
||||
}
|
||||
|
||||
read(buffer) {
|
||||
this.header = buffer.readTextComponent();
|
||||
this.footer = buffer.readTextComponent();
|
||||
}
|
||||
|
||||
handle(handler) {
|
||||
handler.handleServerPlayerListData(this);
|
||||
}
|
||||
|
||||
getHeader() {
|
||||
return this.header;
|
||||
}
|
||||
|
||||
getFooter() {
|
||||
return this.footer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import Packet from "../../../Packet.js";
|
||||
import GameProfile from "../../../../../util/GameProfile.js";
|
||||
|
||||
export default class ServerPlayerListEntryPacket extends Packet {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.players = [];
|
||||
}
|
||||
|
||||
read(buffer) {
|
||||
this.action = buffer.readVarInt();
|
||||
let amount = buffer.readVarInt();
|
||||
|
||||
for (let i = 0; i < amount; i++) {
|
||||
let profile = null;
|
||||
let gameType = 0;
|
||||
let ping = 0;
|
||||
let displayName = null;
|
||||
|
||||
switch (this.action) {
|
||||
case 0: // ADD_PLAYER
|
||||
profile = new GameProfile(buffer.readUUID(), buffer.readString(16));
|
||||
|
||||
let propertiesCount = buffer.readVarInt();
|
||||
for (let propIndex = 0; propIndex < propertiesCount; propIndex++) {
|
||||
let key = buffer.readString(32767);
|
||||
let value = buffer.readString(32767);
|
||||
|
||||
if (buffer.readBoolean()) {
|
||||
let signature = buffer.readString(32767);
|
||||
// TODO implement properties
|
||||
} else {
|
||||
// TODO implement properties
|
||||
}
|
||||
}
|
||||
|
||||
gameType = buffer.readVarInt();
|
||||
ping = buffer.readVarInt();
|
||||
|
||||
if (buffer.readBoolean()) {
|
||||
displayName = buffer.readTextComponent();
|
||||
}
|
||||
break;
|
||||
case 1: // UPDATE_GAME_MODE
|
||||
profile = new GameProfile(buffer.readUUID(), null);
|
||||
gameType = buffer.readVarInt();
|
||||
break;
|
||||
case 2: // UPDATE_LATENCY
|
||||
profile = new GameProfile(buffer.readUUID(), null);
|
||||
ping = buffer.readVarInt();
|
||||
break;
|
||||
case 3: // UPDATE_DISPLAY_NAME
|
||||
profile = new GameProfile(buffer.readUUID(), null);
|
||||
|
||||
if (buffer.readBoolean()) {
|
||||
displayName = buffer.readTextComponent();
|
||||
}
|
||||
break;
|
||||
case 4: // REMOVE_PLAYER
|
||||
profile = new GameProfile(buffer.readUUID(), null);
|
||||
break;
|
||||
}
|
||||
|
||||
this.players.push({
|
||||
profile: profile,
|
||||
ping: ping,
|
||||
gameType: gameType,
|
||||
displayName: displayName
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handle(handler) {
|
||||
handler.handleServerPlayerListEntry(this);
|
||||
}
|
||||
|
||||
getAction() {
|
||||
return this.action;
|
||||
}
|
||||
|
||||
getPlayers() {
|
||||
return this.players;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import Long from "../../../../../../../libraries/long.js";
|
||||
import BlockPosition from "../../../util/BlockPosition.js";
|
||||
import UUID from "../../../util/UUID.js";
|
||||
import {format} from "../../../../../../../libraries/chat.js";
|
||||
|
||||
export default class ByteBuf {
|
||||
|
||||
@@ -213,6 +215,19 @@ export default class ByteBuf {
|
||||
return BlockPosition.fromLong(this.readLong());
|
||||
}
|
||||
|
||||
readUUID() {
|
||||
return new UUID(this.readLong(), this.readLong());
|
||||
}
|
||||
|
||||
writeUUID(uuid) {
|
||||
this.writeLong(uuid.getMostSignificantBits());
|
||||
this.writeLong(uuid.getLeastSignificantBits());
|
||||
}
|
||||
|
||||
readTextComponent() {
|
||||
return format(JSON.parse(this.readString(32767)));
|
||||
}
|
||||
|
||||
readableBytes() {
|
||||
return this.array.length - this.pos;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ export default class ScreenRenderer {
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
console.log(e.stack);
|
||||
}
|
||||
|
||||
// Scale GUI back
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export default class GameProfile {
|
||||
|
||||
constructor(username, uuid) {
|
||||
this.username = username;
|
||||
constructor(uuid, username) {
|
||||
this.uuid = uuid;
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
getCompactUUID() {
|
||||
|
||||
@@ -16,6 +16,14 @@ export default class UUID {
|
||||
UUID.digits(this.leastSigBits, 12));
|
||||
}
|
||||
|
||||
getMostSignificantBits() {
|
||||
return this.mostSigBits;
|
||||
}
|
||||
|
||||
getLeastSignificantBits() {
|
||||
return this.leastSigBits;
|
||||
}
|
||||
|
||||
static randomUUID() {
|
||||
let random = new Random();
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 644 B After Width: | Height: | Size: 891 B |
Reference in New Issue
Block a user