From 770c9242a8ee5fac5ced27e8cbc34fda123461ef Mon Sep 17 00:00:00 2001 From: James Lyne Date: Wed, 29 Sep 2021 01:30:35 +0100 Subject: [PATCH] Player marker improvements - Use 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 --- src/components/map/marker/PlayerMarker.vue | 5 +- src/components/sidebar/FollowTarget.vue | 2 +- src/components/sidebar/PlayerListItem.vue | 2 +- src/index.d.ts | 7 ++- src/leaflet/icon/PlayerIcon.ts | 67 +++++++++------------- src/leaflet/marker/PlayerMarker.ts | 12 ++-- src/providers/Pl3xmapMapProvider.ts | 21 ++++--- src/scss/leaflet/_markers.scss | 64 +++++++++++++++++---- src/util/dynmap.ts | 21 +++++-- 9 files changed, 124 insertions(+), 77 deletions(-) diff --git a/src/components/map/marker/PlayerMarker.vue b/src/components/map/marker/PlayerMarker.vue index 5ec1371..451c024 100644 --- a/src/components/map/marker/PlayerMarker.vue +++ b/src/components/map/marker/PlayerMarker.vue @@ -48,9 +48,8 @@ export default defineComponent({ //The player marker marker = new PlayerMarker(props.player, { - smallFace: componentSettings.value!.smallFaces, - showSkinFace: componentSettings.value!.showSkinFaces, - showBody: componentSettings.value!.showBodies, + showSkin: componentSettings.value!.showSkins, + imageSize: componentSettings.value!.imageSize, showHealth: componentSettings.value!.showHealth, showArmor: componentSettings.value!.showArmor, pane: 'players', diff --git a/src/components/sidebar/FollowTarget.vue b/src/components/sidebar/FollowTarget.vue index d1550df..45c1ed7 100644 --- a/src/components/sidebar/FollowTarget.vue +++ b/src/components/sidebar/FollowTarget.vue @@ -67,7 +67,7 @@ export default defineComponent({ updatePlayerImage = async () => { image.value = defaultImage; - if(store.state.components.playerMarkers && store.state.components.playerMarkers.showSkinFaces) { + if(store.state.components.playerMarkers && store.state.components.playerMarkers.showSkins) { try { const result = await getMinecraftHead(props.target, '16'); image.value = result.src; diff --git a/src/components/sidebar/PlayerListItem.vue b/src/components/sidebar/PlayerListItem.vue index 9905abe..267ec66 100644 --- a/src/components/sidebar/PlayerListItem.vue +++ b/src/components/sidebar/PlayerListItem.vue @@ -91,7 +91,7 @@ export default defineComponent({ let image = ref(defaultImage); 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(() => {}); } }); diff --git a/src/index.d.ts b/src/index.d.ts index 8cc862b..a7f3d3b 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -320,13 +320,14 @@ interface LiveAtlasPlayerMarkerConfig { hideByDefault: boolean; layerName: string; layerPriority: number; - showBodies: boolean; - showSkinFaces: boolean; + imageSize: LiveAtlasPlayerImageSize; + showSkins: boolean; showHealth: boolean; showArmor: boolean; - smallFaces: boolean; } +export type LiveAtlasPlayerImageSize = 'none' | 'small' | 'large' | 'body'; + interface LiveAtlasChatBoxConfig { allowUrlName: boolean; showPlayerFaces: boolean; diff --git a/src/leaflet/icon/PlayerIcon.ts b/src/leaflet/icon/PlayerIcon.ts index 7c95b36..6546762 100644 --- a/src/leaflet/icon/PlayerIcon.ts +++ b/src/leaflet/icon/PlayerIcon.ts @@ -20,7 +20,7 @@ import {BaseIconOptions, DomUtil, Icon, Layer, LayerOptions, Util} from 'leaflet'; import {getMinecraftHead} from '@/util'; import playerImage from '@/assets/images/player_face.png'; -import {LiveAtlasPlayer} from "@/index"; +import {LiveAtlasPlayer, LiveAtlasPlayerImageSize} from "@/index"; const noSkinImage: HTMLImageElement = document.createElement('img'); 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'; export interface PlayerIconOptions extends BaseIconOptions { - smallFace: boolean, - showSkinFace: boolean, - showBody: boolean, + imageSize: LiveAtlasPlayerImageSize, + showSkin: boolean, showHealth: boolean, showArmor: boolean, } @@ -60,10 +59,8 @@ export class PlayerIcon extends Layer implements Icon { private _currentName?: string; - private _playerHealth?: HTMLDivElement; - private _playerHealthBar?: HTMLDivElement; - private _playerArmor?: HTMLDivElement; - private _playerArmorBar?: HTMLDivElement; + private _playerHealth?: HTMLMeterElement; + private _playerArmor?: HTMLMeterElement; constructor(player: LiveAtlasPlayer, options: PlayerIconOptions) { super(options as LayerOptions); @@ -77,7 +74,6 @@ export class PlayerIcon extends Layer implements Icon { } const player = this._player; - let offset = 8; this._container = document.createElement('div'); @@ -90,21 +86,23 @@ export class PlayerIcon extends Layer implements Icon { this._playerName.className = 'player__name'; this._playerName.innerHTML = this._currentName = player.displayName; - if (this.options.showSkinFace) { + if (this.options.showSkin) { let size; - if (this.options.smallFace) { - this._playerImage = smallImage.cloneNode() as HTMLImageElement; - size = '16'; - offset = 8; - } else if(this.options.showBody) { - this._playerImage = bodyImage.cloneNode() as HTMLImageElement; - size = 'body'; - offset = 16; - } else { - this._playerImage = largeImage.cloneNode() as HTMLImageElement; - size = '32'; - offset = 16; + switch(this.options.imageSize) { + case 'small': + this._playerImage = smallImage.cloneNode() as HTMLImageElement; + size = '16'; + break; + + case 'body': + this._playerImage = bodyImage.cloneNode() as HTMLImageElement; + size = 'body'; + break; + + default: + this._playerImage = largeImage.cloneNode() as HTMLImageElement; + size = '32'; } getMinecraftHead(player, size).then(head => { @@ -114,37 +112,28 @@ export class PlayerIcon extends Layer implements Icon { this._playerImage = noSkinImage.cloneNode(false) as HTMLImageElement; } - this._container.appendChild(this._playerImage); - this._container.appendChild(this._playerInfo); + this._playerInfo.appendChild(this._playerImage); this._playerInfo.appendChild(this._playerName); + this._container.appendChild(this._playerInfo); if (this.options.showHealth) { - this._playerHealth = document.createElement('div'); + this._playerHealth = document.createElement('meter'); this._playerHealth.className = 'player__health'; 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); } if (this.options.showArmor) { - this._playerArmor = document.createElement('div'); + this._playerArmor = document.createElement('meter'); this._playerArmor.className = 'player__armor'; 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._container.style.marginTop = `-${offset}px`; - this._container.style.marginLeft = `-${offset}px`; - return this._container; } @@ -165,7 +154,7 @@ export class PlayerIcon extends Layer implements Icon { if(this.options.showHealth) { if (this._player.health !== undefined) { this._playerHealth!.hidden = false; - this._playerHealthBar!.style.width = Math.ceil(this._player.health * 2.5) + 'px'; + this._playerHealth!.value = this._player.health * 5; } else { this._playerHealth!.hidden = true; } @@ -174,7 +163,7 @@ export class PlayerIcon extends Layer implements Icon { if(this.options.showArmor) { if(this._player.armor !== undefined) { this._playerArmor!.hidden = false; - this._playerArmorBar!.style.width = Math.ceil(this._player.armor * 2.5) + 'px'; + this._playerArmor!.value = this._player.armor * 5; } else { this._playerArmor!.hidden = true; } diff --git a/src/leaflet/marker/PlayerMarker.ts b/src/leaflet/marker/PlayerMarker.ts index 733b83a..ed790fd 100644 --- a/src/leaflet/marker/PlayerMarker.ts +++ b/src/leaflet/marker/PlayerMarker.ts @@ -16,14 +16,13 @@ import {LatLng, MarkerOptions, Marker, Map, Util} from 'leaflet'; import {PlayerIcon} from "@/leaflet/icon/PlayerIcon"; -import {LiveAtlasPlayer} from "@/index"; +import {LiveAtlasPlayer, LiveAtlasPlayerImageSize} from "@/index"; import {watch} from "@vue/runtime-core"; import {WatchStopHandle} from "vue"; export interface PlayerMarkerOptions extends MarkerOptions { - smallFace: boolean, - showSkinFace: boolean, - showBody: boolean, + showSkin: boolean, + imageSize: LiveAtlasPlayerImageSize, showHealth: boolean, showArmor: boolean, } @@ -40,9 +39,8 @@ export class PlayerMarker extends Marker { this._player = player; this._PlayerIcon = options.icon = new PlayerIcon(player, { - smallFace: options.smallFace, - showSkinFace: options.showSkinFace, - showBody: options.showBody, + imageSize: options.imageSize, + showSkin: options.showSkin, showHealth: options.showHealth, showArmor: options.showArmor, }); diff --git a/src/providers/Pl3xmapMapProvider.ts b/src/providers/Pl3xmapMapProvider.ts index 1d28080..363679e 100644 --- a/src/providers/Pl3xmapMapProvider.ts +++ b/src/providers/Pl3xmapMapProvider.ts @@ -15,10 +15,18 @@ */ import { - HeadQueueEntry, LiveAtlasArea, LiveAtlasCircle, LiveAtlasComponentConfig, - LiveAtlasDimension, LiveAtlasLine, LiveAtlasMarker, - LiveAtlasMarkerSet, LiveAtlasPartialComponentConfig, - LiveAtlasPlayer, LiveAtlasServerConfig, LiveAtlasServerDefinition, + HeadQueueEntry, + LiveAtlasArea, + LiveAtlasCircle, + LiveAtlasComponentConfig, + LiveAtlasDimension, + LiveAtlasLine, + LiveAtlasMarker, + LiveAtlasMarkerSet, + LiveAtlasPartialComponentConfig, + LiveAtlasPlayer, + LiveAtlasServerConfig, + LiveAtlasServerDefinition, LiveAtlasServerMessageConfig, LiveAtlasWorldDefinition } from "@/index"; @@ -90,11 +98,10 @@ export default class Pl3xmapMapProvider extends MapProvider { hideByDefault: !!worldResponse.player_tracker?.default_hidden, layerName: worldResponse.player_tracker?.label || '', layerPriority: worldResponse.player_tracker?.priority, - showBodies: false, - showSkinFaces: true, + imageSize: 'small', + showSkins: true, showHealth: !!worldResponse.player_tracker?.nameplates?.show_health, showArmor: !!worldResponse.player_tracker?.nameplates?.show_armor, - smallFaces: true, } } else { worldConfig.components.playerMarkers = undefined; diff --git a/src/scss/leaflet/_markers.scss b/src/scss/leaflet/_markers.scss index ed5e713..a8ae015 100644 --- a/src/scss/leaflet/_markers.scss +++ b/src/scss/leaflet/_markers.scss @@ -24,28 +24,68 @@ &.marker--player { 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 { - 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__armor { - width: 50px; - } + width: 5rem; + height: 0.7rem; + display: block; - .player__health, - .player__armor, - .player__health-bar, - .player__armor-bar { - height: 7px; + &::-webkit-meter-inner-element, + &::-webkit-meter-bar { + border-radius: 0; + background: none; + border: none; + box-shadow: none; + image-rendering: crisp-edges; + image-rendering: pixelated; + } } .player__health { 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; } @@ -53,7 +93,11 @@ 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; } } diff --git a/src/util/dynmap.ts b/src/util/dynmap.ts index fad7cce..a036e5d 100644 --- a/src/util/dynmap.ts +++ b/src/util/dynmap.ts @@ -16,11 +16,15 @@ import {DynmapMarkerSetUpdates, DynmapTileUpdate, DynmapUpdate} from "@/dynmap"; import { - LiveAtlasArea, LiveAtlasChat, + LiveAtlasArea, + LiveAtlasChat, LiveAtlasCircle, - LiveAtlasComponentConfig, LiveAtlasDimension, + LiveAtlasComponentConfig, + LiveAtlasDimension, LiveAtlasLine, - LiveAtlasMarker, LiveAtlasServerConfig, LiveAtlasServerMessageConfig, + LiveAtlasMarker, + LiveAtlasServerConfig, + LiveAtlasServerMessageConfig, LiveAtlasWorldDefinition } from "@/index"; import {getPoints} from "@/util/areas"; @@ -150,11 +154,16 @@ export function buildComponents(response: any): LiveAtlasComponentConfig { hideByDefault: component.hidebydefault || false, layerName: component.label || "Players", layerPriority: component.layerprio || 0, - showBodies: component.showplayerbody || false, - showSkinFaces: component.showplayerfaces || false, + showSkins: component.showplayerfaces || false, + imageSize: 'large', showHealth: 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;