feat: implement player skin display based on texture properties from server packets eg your custom plugin skins!

This commit is contained in:
Vitaly Turovsky 2025-01-27 03:48:42 +03:00
commit 491b5d66c5
3 changed files with 46 additions and 35 deletions

View file

@ -276,8 +276,20 @@ export class Entities extends EventEmitter {
// fixme workaround
defaultSteveTexture
usernamePerSkinUrlsCache = {} as Record<string, { skinUrl?: string, capeUrl?: string }>
// true means use default skin url
updatePlayerSkin (entityId: string | number, username: string | undefined, skinUrl: string | true, capeUrl: string | true | undefined = undefined) {
if (username) {
if (typeof skinUrl === 'string' || typeof capeUrl === 'string') this.usernamePerSkinUrlsCache[username] = {}
if (typeof skinUrl === 'string') this.usernamePerSkinUrlsCache[username].skinUrl = skinUrl
if (typeof capeUrl === 'string') this.usernamePerSkinUrlsCache[username].capeUrl = capeUrl
if (skinUrl === true) {
skinUrl = this.usernamePerSkinUrlsCache[username]?.skinUrl ?? skinUrl
}
capeUrl ??= this.usernamePerSkinUrlsCache[username]?.capeUrl
}
let playerObject = this.getPlayerObject(entityId)
if (!playerObject) return
// const username = this.entities[entityId].username

View file

@ -153,4 +153,32 @@ customEvents.on('gameLoaded', () => {
watchValue(options, o => {
viewer.entities.setDebugMode(o.showChunkBorders ? 'basic' : 'none')
})
// Texture override from packet properties
bot._client.on('player_info', (packet) => {
for (const player of packet.data) {
const textureProperty = player.properties?.find(prop => prop.name === 'textures')
if (textureProperty) {
try {
const textureData = JSON.parse(Buffer.from(textureProperty.value, 'base64').toString())
const skinUrl = textureData.textures?.SKIN?.url
const capeUrl = textureData.textures?.CAPE?.url
// Find entity with matching UUID and update skin
let entityId = ''
for (const [entId, entity] of Object.entries(bot.entities)) {
if (entity.uuid === player.UUID) {
entityId = entId
break
}
}
// even if not found, still record to cache
viewer.entities.updatePlayerSkin(entityId, player.name, skinUrl, capeUrl)
} catch (err) {
console.error('Error decoding player texture:', err)
}
}
}
})
})

View file

@ -6,14 +6,7 @@ export interface ClientOnMap {
} | /** 1.12.2 */ {
keepAliveId: bigint;
};
login: /** 1.7 */ {
entityId: number;
gameMode: number;
dimension: number;
difficulty: number;
maxPlayers: number;
levelType: string;
} | /** 1.8 */ {
login:/** 1.8 */ {
entityId: number;
gameMode: number;
dimension: number;
@ -148,9 +141,7 @@ export interface ClientOnMap {
entityId: number;
equipments: any;
};
spawn_position: /** 1.7 */ {
location: any;
} | /** 1.8 */ {
spawn_position:/** 1.8 */ {
location: { x: number, y: number, z: number };
} | /** 1.17 */ {
location: { x: number, y: number, z: number };
@ -215,14 +206,7 @@ export interface ClientOnMap {
death: any;
portalCooldown: number;
};
position: /** 1.7 */ {
x: number;
y: number;
z: number;
yaw: number;
pitch: number;
onGround: boolean;
} | /** 1.8 */ {
position: /** 1.8 */ {
x: number;
y: number;
z: number;
@ -905,11 +889,7 @@ export interface ClientOnMap {
statistics: /** 1.7 */ {
entries: any;
};
player_info: /** 1.7 */ {
playerName: string;
online: boolean;
ping: number;
} | /** 1.8 */ {
player_info: /** 1.8 */ {
action: number;
data: any;
};
@ -926,22 +906,13 @@ export interface ClientOnMap {
length: number;
matches: any;
};
scoreboard_objective: /** 1.7 */ {
name: string;
displayText: string;
action: number;
} | /** 1.8 */ {
scoreboard_objective:/** 1.8 */ {
name: string;
action: number;
displayText: any;
type: any;
};
scoreboard_score: /** 1.7 */ {
itemName: string;
action: number;
scoreName: any;
value: any;
} | /** 1.8 */ {
scoreboard_score:/** 1.8 */ {
itemName: string;
action: number;
scoreName: string;