Handle hidden/offworld players

- Respect server config for greying out players in other worlds
- Always grey out players hidden due to other restrictions
- Allow following for hidden players, and show their hidden status on the follow popup
This commit is contained in:
James Lyne 2020-12-18 16:04:06 +00:00
parent 217028e044
commit b92120e801
7 changed files with 92 additions and 15 deletions

View File

@ -43,6 +43,7 @@ function buildServerConfig(response: any): DynmapServerConfig {
return { return {
version: response.dynmapversion || '', version: response.dynmapversion || '',
allowChat: response.allowwebchat || false, allowChat: response.allowwebchat || false,
grayHiddenPlayers: response.grayplayerswhenhidden || false,
chatRequiresLogin: response['webchat-requires-login'] || false, chatRequiresLogin: response['webchat-requires-login'] || false,
chatInterval: response['webchat-interval'] || 5, chatInterval: response['webchat-interval'] || 5,
defaultMap: response.defaultmap || undefined, defaultMap: response.defaultmap || undefined,
@ -488,17 +489,20 @@ export default {
const players: Set<DynmapPlayer> = new Set(); const players: Set<DynmapPlayer> = new Set();
(response.players || []).forEach((player: any) => { (response.players || []).forEach((player: any) => {
const world = player.world && player.world !== '-some-other-bogus-world-' ? player.world : undefined;
players.add({ players.add({
account: player.account || "", account: player.account || "",
health: player.health || 0, health: player.health || 0,
armor: player.armor || 0, armor: player.armor || 0,
name: player.name ? sanitizer.sanitize(player.name) : "Steve", name: player.name ? sanitizer.sanitize(player.name) : "Steve",
sort: player.sort || 0, sort: player.sort || 0,
hidden: !world,
location: { location: {
x: player.x || 0, x: player.x || 0,
y: player.y || 0, y: player.y || 0,
z: player.z || 0, z: player.z || 0,
world: player.world || undefined, world: world,
} }
}); });
}); });

View File

@ -232,6 +232,11 @@ export default defineComponent({
return; return;
} }
if(player.hidden) {
console.warn(`Cannot follow ${player.name}. Player is hidden from the map.`);
return;
}
if(!player.location.world) { if(!player.location.world) {
console.warn(`Cannot follow ${player.name}. Player isn't in a known world.`); console.warn(`Cannot follow ${player.name}. Player isn't in a known world.`);
return; return;

View File

@ -18,9 +18,12 @@
<section class="sidebar__section following"> <section class="sidebar__section following">
<h2>Following</h2> <h2>Following</h2>
<div class="following__target"> <div :class="{'following__target': true, 'following__target--hidden': target.hidden}">
<img width="32" height="32" class="target__icon" :src="playerImage" alt="" /> <img width="32" height="32" class="target__icon" :src="playerImage" alt="" />
<span class="target__name" v-html="target.name"></span> <span class="target__info">
<span class="target__name" v-html="target.name"></span>
<span class="target__status" v-show="target.hidden">Currently hidden</span>
</span>
<button class="target__unfollow" type="button" :title="`Stop following this player`" <button class="target__unfollow" type="button" :title="`Stop following this player`"
@click.prevent="unfollow" @click.prevent="unfollow"
@keydown="onKeydown">Unfollow</button> @keydown="onKeydown">Unfollow</button>
@ -80,8 +83,22 @@ export default defineComponent({
right: 1.5rem; right: 1.5rem;
} }
.target__name { .target__info {
margin-left: 2rem; margin-left: 2rem;
display: flex;
flex-direction: column;
justify-content: flex-start;
.target__status {
font-size: 1.3rem;
}
}
&.following__target--hidden {
.target__icon {
filter: grayscale(1);
opacity: 0.5;
}
} }
} }
} }

View File

@ -15,9 +15,10 @@
--> -->
<template> <template>
<li class="player"> <li :class="{'player': true, 'player--hidden' : !!player.hidden, 'player--other-world': otherWorld}">
<img width="16" height="16" class="player__icon" :src="playerImage" alt="" /> <img width="16" height="16" class="player__icon" :src="playerImage" alt="" />
<button class="player__name" type="button" title="Click to center on player&#10;Double-click to follow player" <button class="player__name" type="button" :title="title"
:disbled="player.hidden"
@click.prevent="pan" @click.prevent="pan"
@keydown="onKeydown" @keydown="onKeydown"
@dblclick.prevent="follow" v-html="player.name"></button> @dblclick.prevent="follow" v-html="player.name"></button>
@ -45,20 +46,44 @@ export default defineComponent({
playerImage: playerImage, playerImage: playerImage,
} }
}, },
computed: {
otherWorld(): boolean {
const store = useStore();
return store.state.configuration.grayHiddenPlayers
&& (!store.state.currentWorld || store.state.currentWorld.name !== this.player.location.world);
},
title(): string {
if(this.player.hidden) {
return 'This player is currently hidden from the map\nDouble-click to follow player when they become visible';
} else if(this.otherWorld) {
return 'This player is in another world.\nClick to center on player\nDouble-click to follow player';
} else {
return 'Click to center on player\nDouble-click to follow player';
}
}
},
methods: { methods: {
follow() { follow() {
useStore().commit(MutationTypes.SET_FOLLOW_TARGET, this.player); useStore().commit(MutationTypes.SET_FOLLOW_TARGET, this.player);
}, },
pan() { pan() {
useStore().commit(MutationTypes.CLEAR_FOLLOW_TARGET, undefined); if(!this.player.hidden) {
useStore().commit(MutationTypes.SET_PAN_TARGET, this.player); useStore().commit(MutationTypes.CLEAR_FOLLOW_TARGET, undefined);
useStore().commit(MutationTypes.SET_PAN_TARGET, this.player);
}
}, },
onKeydown(e: KeyboardEvent) { onKeydown(e: KeyboardEvent) {
if(e.key !== ' ') { if(e.key !== ' ' && e.key !== 'Enter') {
return; return;
} }
e.shiftKey ? this.follow() : this.pan(); if(e.shiftKey) {
this.follow();
} else {
if(!this.player.hidden) {
this.pan();
}
}
} }
} }
}); });
@ -88,5 +113,26 @@ export default defineComponent({
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
&.player--hidden {
.player__icon {
filter: grayscale(1);
opacity: 0.5;
}
.player__name {
cursor: not-allowed;
}
color: #999999;
}
&.player--other-world {
.player__icon {
opacity: 0.5;
}
color: #999999;
}
} }
</style> </style>

12
src/dynmap.d.ts vendored
View File

@ -57,6 +57,7 @@ interface DynmapServerConfig {
loginEnabled: boolean; loginEnabled: boolean;
loginRequired: boolean; loginRequired: boolean;
maxPlayers: number; maxPlayers: number;
grayHiddenPlayers: boolean;
hash: number; hash: number;
} }
@ -169,11 +170,12 @@ interface DynmapUpdateResponse {
} }
interface DynmapPlayer { interface DynmapPlayer {
account: string account: string;
armor: number armor: number;
health: number health: number;
name: string name: string;
sort: number sort: number;
hidden: boolean;
location: DynmapLocation; location: DynmapLocation;
} }

View File

@ -259,6 +259,7 @@ export const mutations: MutationTree<State> & Mutations = {
existing!.health = player.health; existing!.health = player.health;
existing!.armor = player.armor; existing!.armor = player.armor;
existing!.location = Object.assign(existing!.location, player.location); existing!.location = Object.assign(existing!.location, player.location);
existing!.hidden = player.hidden;
existing!.name = player.name; existing!.name = player.name;
existing!.sort = player.sort; existing!.sort = player.sort;
} else { } else {
@ -269,6 +270,7 @@ export const mutations: MutationTree<State> & Mutations = {
location: player.location, location: player.location,
name: player.name, name: player.name,
sort: player.sort, sort: player.sort,
hidden: player.hidden,
}); });
} }

View File

@ -74,6 +74,7 @@ export const state: State = {
loginEnabled: false, loginEnabled: false,
loginRequired: false, loginRequired: false,
maxPlayers: 0, maxPlayers: 0,
grayHiddenPlayers: false,
hash: 0, hash: 0,
}, },