implement player list overlay, version 1.1.7

This commit is contained in:
LabyStudio
2022-06-19 21:10:57 +02:00
parent b8408a9a8e
commit 4ac61adbb5
17 changed files with 434 additions and 9 deletions
@@ -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();
+1 -1
View File
@@ -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
+2 -2
View File
@@ -1,8 +1,8 @@
export default class GameProfile {
constructor(username, uuid) {
this.username = username;
constructor(uuid, username) {
this.uuid = uuid;
this.username = username;
}
getCompactUUID() {
+8
View File
@@ -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