1
0
forked from MTSR/mapserver
mapserver/public/js/map/overlays/PlayerOverlay.js
Kenneth Watson 7e8dcdc77b
Add support for player skins via SkinsDB (#284)
* Add support for player skins via SkinsDB

* Fix jshint complaints
2022-12-16 08:07:47 +01:00

210 lines
6.1 KiB
JavaScript

import wsChannel from '../../WebSocketChannel.js';
import layerMgr from '../../LayerManager.js';
const defaultSkin = "pics/sam.png";
let players = [];
let playerSkins = {};
//update players all the time
wsChannel.addListener("minetest-info", function(info){
players = info.players || [];
});
export default L.LayerGroup.extend({
initialize: function() {
L.LayerGroup.prototype.initialize.call(this);
this.currentObjects = {}; // name => marker
this.reDraw = this.reDraw.bind(this);
this.onMinetestUpdate = this.onMinetestUpdate.bind(this);
},
createPopup: function(player) {
// moderators get a small crown icon
let moderator = player.moderator ? `<img src="pics/crown.png" alt="moderator" title="moderator">` : "";
let info = `<b>${moderator} ${player.name}</b>`;
info += "<hr>";
for (let i = 0; i < Math.floor(player.hp / 2); i++)
info += "<img src='pics/heart.png' alt='health'>";
if (player.hp % 2 === 1)
info += "<img src='pics/heart_half.png' alt='health'>";
info += "<br>";
for (let i = 0; i < Math.floor(player.breath / 2); i++)
info += "<img src='pics/bubble.png' alt='breath'>";
if (player.breath % 2 === 1)
info += "<img src='pics/bubble_half.png' alt='breath'>";
info += `
<br>
<b>RTT:</b> ${Math.floor(player.rtt*1000)} ms
<br>
<b>Protocol version:</b> ${player.protocol_version}
`;
info = `<div class="info">${info}</div>`;
let portrait = `<img class="portrait" src="${this.getSkin(player)}" alt="${player.name}">`;
return `<div class="player-popup">${portrait}${info}</div>`;
},
createMarker: function(player) {
const marker = L.marker([player.pos.z, player.pos.x], {icon: this.getIcon(player)});
marker.bindPopup(this.createPopup(player), {minWidth: 220});
return marker;
},
getIcon: function(player) {
const icon = this.getSkin(player);
// compatibility with mapserver_mod without `yaw` attribute - value will be 0.
const indicator = player.yaw === 0 ? false : player.velocity.x !== 0 || player.velocity.z !== 0 ? 'pics/sam_dir_move.png' : 'pics/sam_dir.png';
return L.divIcon({
html: `<div style="display:inline-block;width:48px;height:48px">
<img src="${icon}" style="position:absolute;top:8px;left:16px;width:16px;height:32px;" alt="${player.name}">
${indicator ? `<img src="${indicator}" style="position:absolute;top:0;left:0;width:48px;height:48px;transform:rotate(${player.yaw*-1}rad)" alt="${player.name}">` : ''}
</div>`,
className: '', // don't use leaflet default of a white block
iconSize: [48, 48],
iconAnchor: [24, 24],
popupAnchor: [0, -24]
});
},
getSkin: function(player) {
if (!player.skin || player.skin === "" || player.skin === "character.png") return defaultSkin;
let skin = `api/skins/${player.skin}`;
if (playerSkins[skin]) return playerSkins[skin];
// no cached skin, we need to build the image
let img = new Image();
img.onload = function() {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = 16;
canvas.height = 32;
// head
ctx.drawImage(img, 8, 8, 8, 8, 4, 0, 8, 8);
// chest
ctx.drawImage(img, 20, 20, 8, 12, 4, 8, 8, 12);
// leg left
ctx.drawImage(img, 4, 20, 4, 12, 4, 20, 4, 12);
// leg right
if (img.height === 64) {
ctx.drawImage(img, 20, 52, 4, 12, 8, 20, 4, 12);
} else {
ctx.drawImage(img, 4, 20, 4, 12, 8, 20, 4, 12);
}
// arm left
ctx.drawImage(img, 44, 20, 4, 12, 0, 8, 4, 12);
// arm right
if (img.height === 64) {
ctx.drawImage(img, 36, 52, 4, 12, 12, 8, 4, 12);
} else {
ctx.drawImage(img, 44, 20, 4, 12, 12, 8, 4, 12);
}
// store the skin, so it gets used on next update
playerSkins[skin] = canvas.toDataURL("image/png");
};
// trigger source image load
img.src = skin;
// return the default skin while the replacement loads
return defaultSkin;
},
isPlayerInCurrentLayer: function(player){
const mapLayer = layerMgr.getCurrentLayer();
return (
player.pos.y >= (mapLayer.from*16) &&
player.pos.y <= ((mapLayer.to*16) + 15)
);
},
onMinetestUpdate: function(/*info*/){
players.forEach(player => {
const isInLayer = this.isPlayerInCurrentLayer(player);
if (!isInLayer){
if (this.currentObjects[player.name]){
//player is displayed and not on the layer anymore
//Remove the marker and reference
this.currentObjects[player.name].remove();
delete this.currentObjects[player.name];
}
return;
}
if (this.currentObjects[player.name]){
//marker exists
const marker = this.currentObjects[player.name];
marker.setLatLng([player.pos.z, player.pos.x]);
marker.setIcon(this.getIcon(player));
marker.setPopupContent(this.createPopup(player));
} else {
//marker does not exist
const marker = this.createMarker(player);
marker.addTo(this);
this.currentObjects[player.name] = marker;
}
});
Object.keys(this.currentObjects).forEach(existingName => {
const playerIsActive = players.find(function(p){
return p.name == existingName;
});
if (!playerIsActive){
//player
this.currentObjects[existingName].remove();
delete this.currentObjects[existingName];
}
});
},
reDraw: function(){
this.currentObjects = {};
this.clearLayers();
players.forEach(player => {
if (!this.isPlayerInCurrentLayer(player)){
//not in current layer
return;
}
const marker = this.createMarker(player);
marker.addTo(this);
this.currentObjects[player.name] = marker;
});
},
onAdd: function(/*map*/) {
wsChannel.addListener("minetest-info", this.onMinetestUpdate);
this.reDraw();
},
onRemove: function(/*map*/) {
this.clearLayers();
wsChannel.removeListener("minetest-info", this.onMinetestUpdate);
}
});