Player marker improvements

- Use <meter> for health/armor
- Condense small/body image properties into single image size option
- Remove image-size dependant margins
- Add circle alongside label for indicating player position
This commit is contained in:
James Lyne 2021-09-29 01:30:35 +01:00
parent 146a5efc82
commit 770c9242a8
9 changed files with 124 additions and 77 deletions

View File

@ -48,9 +48,8 @@ export default defineComponent({
//The player marker //The player marker
marker = new PlayerMarker(props.player, { marker = new PlayerMarker(props.player, {
smallFace: componentSettings.value!.smallFaces, showSkin: componentSettings.value!.showSkins,
showSkinFace: componentSettings.value!.showSkinFaces, imageSize: componentSettings.value!.imageSize,
showBody: componentSettings.value!.showBodies,
showHealth: componentSettings.value!.showHealth, showHealth: componentSettings.value!.showHealth,
showArmor: componentSettings.value!.showArmor, showArmor: componentSettings.value!.showArmor,
pane: 'players', pane: 'players',

View File

@ -67,7 +67,7 @@ export default defineComponent({
updatePlayerImage = async () => { updatePlayerImage = async () => {
image.value = defaultImage; image.value = defaultImage;
if(store.state.components.playerMarkers && store.state.components.playerMarkers.showSkinFaces) { if(store.state.components.playerMarkers && store.state.components.playerMarkers.showSkins) {
try { try {
const result = await getMinecraftHead(props.target, '16'); const result = await getMinecraftHead(props.target, '16');
image.value = result.src; image.value = result.src;

View File

@ -91,7 +91,7 @@ export default defineComponent({
let image = ref(defaultImage); let image = ref(defaultImage);
onMounted(() => { onMounted(() => {
if(store.state.components.playerMarkers && store.state.components.playerMarkers.showSkinFaces) { if(store.state.components.playerMarkers && store.state.components.playerMarkers.showSkins) {
getMinecraftHead(props.player, '16').then((result) => image.value = result.src).catch(() => {}); getMinecraftHead(props.player, '16').then((result) => image.value = result.src).catch(() => {});
} }
}); });

7
src/index.d.ts vendored
View File

@ -320,13 +320,14 @@ interface LiveAtlasPlayerMarkerConfig {
hideByDefault: boolean; hideByDefault: boolean;
layerName: string; layerName: string;
layerPriority: number; layerPriority: number;
showBodies: boolean; imageSize: LiveAtlasPlayerImageSize;
showSkinFaces: boolean; showSkins: boolean;
showHealth: boolean; showHealth: boolean;
showArmor: boolean; showArmor: boolean;
smallFaces: boolean;
} }
export type LiveAtlasPlayerImageSize = 'none' | 'small' | 'large' | 'body';
interface LiveAtlasChatBoxConfig { interface LiveAtlasChatBoxConfig {
allowUrlName: boolean; allowUrlName: boolean;
showPlayerFaces: boolean; showPlayerFaces: boolean;

View File

@ -20,7 +20,7 @@
import {BaseIconOptions, DomUtil, Icon, Layer, LayerOptions, Util} from 'leaflet'; import {BaseIconOptions, DomUtil, Icon, Layer, LayerOptions, Util} from 'leaflet';
import {getMinecraftHead} from '@/util'; import {getMinecraftHead} from '@/util';
import playerImage from '@/assets/images/player_face.png'; import playerImage from '@/assets/images/player_face.png';
import {LiveAtlasPlayer} from "@/index"; import {LiveAtlasPlayer, LiveAtlasPlayerImageSize} from "@/index";
const noSkinImage: HTMLImageElement = document.createElement('img'); const noSkinImage: HTMLImageElement = document.createElement('img');
noSkinImage.height = 16; noSkinImage.height = 16;
@ -42,9 +42,8 @@ noSkinImage.src = smallImage.src = largeImage.src = bodyImage.src = playerImage;
noSkinImage.className = smallImage.className = largeImage.className = bodyImage.className = 'player__icon'; noSkinImage.className = smallImage.className = largeImage.className = bodyImage.className = 'player__icon';
export interface PlayerIconOptions extends BaseIconOptions { export interface PlayerIconOptions extends BaseIconOptions {
smallFace: boolean, imageSize: LiveAtlasPlayerImageSize,
showSkinFace: boolean, showSkin: boolean,
showBody: boolean,
showHealth: boolean, showHealth: boolean,
showArmor: boolean, showArmor: boolean,
} }
@ -60,10 +59,8 @@ export class PlayerIcon extends Layer implements Icon<PlayerIconOptions> {
private _currentName?: string; private _currentName?: string;
private _playerHealth?: HTMLDivElement; private _playerHealth?: HTMLMeterElement;
private _playerHealthBar?: HTMLDivElement; private _playerArmor?: HTMLMeterElement;
private _playerArmor?: HTMLDivElement;
private _playerArmorBar?: HTMLDivElement;
constructor(player: LiveAtlasPlayer, options: PlayerIconOptions) { constructor(player: LiveAtlasPlayer, options: PlayerIconOptions) {
super(options as LayerOptions); super(options as LayerOptions);
@ -77,7 +74,6 @@ export class PlayerIcon extends Layer implements Icon<PlayerIconOptions> {
} }
const player = this._player; const player = this._player;
let offset = 8;
this._container = document.createElement('div'); this._container = document.createElement('div');
@ -90,21 +86,23 @@ export class PlayerIcon extends Layer implements Icon<PlayerIconOptions> {
this._playerName.className = 'player__name'; this._playerName.className = 'player__name';
this._playerName.innerHTML = this._currentName = player.displayName; this._playerName.innerHTML = this._currentName = player.displayName;
if (this.options.showSkinFace) { if (this.options.showSkin) {
let size; let size;
if (this.options.smallFace) { switch(this.options.imageSize) {
this._playerImage = smallImage.cloneNode() as HTMLImageElement; case 'small':
size = '16'; this._playerImage = smallImage.cloneNode() as HTMLImageElement;
offset = 8; size = '16';
} else if(this.options.showBody) { break;
this._playerImage = bodyImage.cloneNode() as HTMLImageElement;
size = 'body'; case 'body':
offset = 16; this._playerImage = bodyImage.cloneNode() as HTMLImageElement;
} else { size = 'body';
this._playerImage = largeImage.cloneNode() as HTMLImageElement; break;
size = '32';
offset = 16; default:
this._playerImage = largeImage.cloneNode() as HTMLImageElement;
size = '32';
} }
getMinecraftHead(player, size).then(head => { getMinecraftHead(player, size).then(head => {
@ -114,37 +112,28 @@ export class PlayerIcon extends Layer implements Icon<PlayerIconOptions> {
this._playerImage = noSkinImage.cloneNode(false) as HTMLImageElement; this._playerImage = noSkinImage.cloneNode(false) as HTMLImageElement;
} }
this._container.appendChild(this._playerImage); this._playerInfo.appendChild(this._playerImage);
this._container.appendChild(this._playerInfo);
this._playerInfo.appendChild(this._playerName); this._playerInfo.appendChild(this._playerName);
this._container.appendChild(this._playerInfo);
if (this.options.showHealth) { if (this.options.showHealth) {
this._playerHealth = document.createElement('div'); this._playerHealth = document.createElement('meter');
this._playerHealth.className = 'player__health'; this._playerHealth.className = 'player__health';
this._playerHealth.hidden = true; this._playerHealth.hidden = true;
this._playerHealth.max = 100;
this._playerHealthBar = document.createElement('div');
this._playerHealthBar.className = 'player__health-bar';
this._playerHealth.appendChild(this._playerHealthBar);
this._playerInfo.appendChild(this._playerHealth); this._playerInfo.appendChild(this._playerHealth);
} }
if (this.options.showArmor) { if (this.options.showArmor) {
this._playerArmor = document.createElement('div'); this._playerArmor = document.createElement('meter');
this._playerArmor.className = 'player__armor'; this._playerArmor.className = 'player__armor';
this._playerArmor.hidden = true; this._playerArmor.hidden = true;
this._playerArmor.max = 100;
this._playerArmorBar = document.createElement('div');
this._playerArmorBar.className = 'player__armor-bar';
this._playerArmor.appendChild(this._playerArmorBar);
this._playerInfo.appendChild(this._playerArmor); this._playerInfo.appendChild(this._playerArmor);
} }
this._container.style.marginTop = `-${offset}px`;
this._container.style.marginLeft = `-${offset}px`;
return this._container; return this._container;
} }
@ -165,7 +154,7 @@ export class PlayerIcon extends Layer implements Icon<PlayerIconOptions> {
if(this.options.showHealth) { if(this.options.showHealth) {
if (this._player.health !== undefined) { if (this._player.health !== undefined) {
this._playerHealth!.hidden = false; this._playerHealth!.hidden = false;
this._playerHealthBar!.style.width = Math.ceil(this._player.health * 2.5) + 'px'; this._playerHealth!.value = this._player.health * 5;
} else { } else {
this._playerHealth!.hidden = true; this._playerHealth!.hidden = true;
} }
@ -174,7 +163,7 @@ export class PlayerIcon extends Layer implements Icon<PlayerIconOptions> {
if(this.options.showArmor) { if(this.options.showArmor) {
if(this._player.armor !== undefined) { if(this._player.armor !== undefined) {
this._playerArmor!.hidden = false; this._playerArmor!.hidden = false;
this._playerArmorBar!.style.width = Math.ceil(this._player.armor * 2.5) + 'px'; this._playerArmor!.value = this._player.armor * 5;
} else { } else {
this._playerArmor!.hidden = true; this._playerArmor!.hidden = true;
} }

View File

@ -16,14 +16,13 @@
import {LatLng, MarkerOptions, Marker, Map, Util} from 'leaflet'; import {LatLng, MarkerOptions, Marker, Map, Util} from 'leaflet';
import {PlayerIcon} from "@/leaflet/icon/PlayerIcon"; import {PlayerIcon} from "@/leaflet/icon/PlayerIcon";
import {LiveAtlasPlayer} from "@/index"; import {LiveAtlasPlayer, LiveAtlasPlayerImageSize} from "@/index";
import {watch} from "@vue/runtime-core"; import {watch} from "@vue/runtime-core";
import {WatchStopHandle} from "vue"; import {WatchStopHandle} from "vue";
export interface PlayerMarkerOptions extends MarkerOptions { export interface PlayerMarkerOptions extends MarkerOptions {
smallFace: boolean, showSkin: boolean,
showSkinFace: boolean, imageSize: LiveAtlasPlayerImageSize,
showBody: boolean,
showHealth: boolean, showHealth: boolean,
showArmor: boolean, showArmor: boolean,
} }
@ -40,9 +39,8 @@ export class PlayerMarker extends Marker {
this._player = player; this._player = player;
this._PlayerIcon = options.icon = new PlayerIcon(player, { this._PlayerIcon = options.icon = new PlayerIcon(player, {
smallFace: options.smallFace, imageSize: options.imageSize,
showSkinFace: options.showSkinFace, showSkin: options.showSkin,
showBody: options.showBody,
showHealth: options.showHealth, showHealth: options.showHealth,
showArmor: options.showArmor, showArmor: options.showArmor,
}); });

View File

@ -15,10 +15,18 @@
*/ */
import { import {
HeadQueueEntry, LiveAtlasArea, LiveAtlasCircle, LiveAtlasComponentConfig, HeadQueueEntry,
LiveAtlasDimension, LiveAtlasLine, LiveAtlasMarker, LiveAtlasArea,
LiveAtlasMarkerSet, LiveAtlasPartialComponentConfig, LiveAtlasCircle,
LiveAtlasPlayer, LiveAtlasServerConfig, LiveAtlasServerDefinition, LiveAtlasComponentConfig,
LiveAtlasDimension,
LiveAtlasLine,
LiveAtlasMarker,
LiveAtlasMarkerSet,
LiveAtlasPartialComponentConfig,
LiveAtlasPlayer,
LiveAtlasServerConfig,
LiveAtlasServerDefinition,
LiveAtlasServerMessageConfig, LiveAtlasServerMessageConfig,
LiveAtlasWorldDefinition LiveAtlasWorldDefinition
} from "@/index"; } from "@/index";
@ -90,11 +98,10 @@ export default class Pl3xmapMapProvider extends MapProvider {
hideByDefault: !!worldResponse.player_tracker?.default_hidden, hideByDefault: !!worldResponse.player_tracker?.default_hidden,
layerName: worldResponse.player_tracker?.label || '', layerName: worldResponse.player_tracker?.label || '',
layerPriority: worldResponse.player_tracker?.priority, layerPriority: worldResponse.player_tracker?.priority,
showBodies: false, imageSize: 'small',
showSkinFaces: true, showSkins: true,
showHealth: !!worldResponse.player_tracker?.nameplates?.show_health, showHealth: !!worldResponse.player_tracker?.nameplates?.show_health,
showArmor: !!worldResponse.player_tracker?.nameplates?.show_armor, showArmor: !!worldResponse.player_tracker?.nameplates?.show_armor,
smallFaces: true,
} }
} else { } else {
worldConfig.components.playerMarkers = undefined; worldConfig.components.playerMarkers = undefined;

View File

@ -24,28 +24,68 @@
&.marker--player { &.marker--player {
transition: transform 0.3s ease-in 0s; transition: transform 0.3s ease-in 0s;
height: 1.2rem;
margin-left: -0.6rem;
margin-top: -0.6rem;
&:before {
content: '';
display: block;
width: 0.8rem;
height: 0.8rem;
position: absolute;
left: 0;
background-color: var(--text-base);
border: 0.2rem solid var(--background-dark);
border-radius: 50%;
}
.marker__label { .marker__label {
display: block; display: grid !important;
grid-template-columns: min-content 1fr;
grid-template-rows: 1fr min-content min-content min-content 1fr;
grid-auto-flow: column;
column-gap: 0.5rem;
margin-left: 1.6rem;
background-clip: padding-box;
padding-right: 0.5rem;
align-items: center;
}
.player__icon {
grid-row: 1 / -1;
}
.player__name {
grid-row: 2;
} }
.player__health, .player__health,
.player__armor { .player__armor {
width: 50px; width: 5rem;
} height: 0.7rem;
display: block;
.player__health, &::-webkit-meter-inner-element,
.player__armor, &::-webkit-meter-bar {
.player__health-bar, border-radius: 0;
.player__armor-bar { background: none;
height: 7px; border: none;
box-shadow: none;
image-rendering: crisp-edges;
image-rendering: pixelated;
}
} }
.player__health { .player__health {
background: url(../assets/images/heart_depleted.png) repeat-x left center; background: url(../assets/images/heart_depleted.png) repeat-x left center;
} }
.player__health-bar { .player__health::-webkit-meter-optimum-value {
background: url(../assets/images/heart.png) repeat-x left center;
}
.player__health::-moz-meter-bar {
background: url(../assets/images/heart.png) repeat-x left center; background: url(../assets/images/heart.png) repeat-x left center;
} }
@ -53,7 +93,11 @@
background: url(../assets/images/armor_depleted.png) repeat-x left center; background: url(../assets/images/armor_depleted.png) repeat-x left center;
} }
.player__armor-bar { .player__armor::-webkit-meter-optimum-value {
background: url(../assets/images/armor.png) repeat-x left center;
}
.player__armor::-moz-meter-bar {
background: url(../assets/images/armor.png) repeat-x left center; background: url(../assets/images/armor.png) repeat-x left center;
} }
} }

View File

@ -16,11 +16,15 @@
import {DynmapMarkerSetUpdates, DynmapTileUpdate, DynmapUpdate} from "@/dynmap"; import {DynmapMarkerSetUpdates, DynmapTileUpdate, DynmapUpdate} from "@/dynmap";
import { import {
LiveAtlasArea, LiveAtlasChat, LiveAtlasArea,
LiveAtlasChat,
LiveAtlasCircle, LiveAtlasCircle,
LiveAtlasComponentConfig, LiveAtlasDimension, LiveAtlasComponentConfig,
LiveAtlasDimension,
LiveAtlasLine, LiveAtlasLine,
LiveAtlasMarker, LiveAtlasServerConfig, LiveAtlasServerMessageConfig, LiveAtlasMarker,
LiveAtlasServerConfig,
LiveAtlasServerMessageConfig,
LiveAtlasWorldDefinition LiveAtlasWorldDefinition
} from "@/index"; } from "@/index";
import {getPoints} from "@/util/areas"; import {getPoints} from "@/util/areas";
@ -150,11 +154,16 @@ export function buildComponents(response: any): LiveAtlasComponentConfig {
hideByDefault: component.hidebydefault || false, hideByDefault: component.hidebydefault || false,
layerName: component.label || "Players", layerName: component.label || "Players",
layerPriority: component.layerprio || 0, layerPriority: component.layerprio || 0,
showBodies: component.showplayerbody || false, showSkins: component.showplayerfaces || false,
showSkinFaces: component.showplayerfaces || false, imageSize: 'large',
showHealth: component.showplayerhealth || false, showHealth: component.showplayerhealth || false,
showArmor: component.showplayerhealth || false, showArmor: component.showplayerhealth || false,
smallFaces: component.smallplayerfaces || false, }
if(component.smallplayerfaces) {
components.playerMarkers.imageSize = 'small'
} else if(component.showplayerbody) {
components.playerMarkers.imageSize = 'body';
} }
break; break;