Availability indicator (#2044)

* status indicators changing properly

* away status wip

* updating away status on connection

* remove obsolete logs

* fix typecheck

* minor adjustments

* darker outline

* Trying darker outline

* Apply suggestions from code review

* Update pusher/src/Model/Zone.ts

* Making the dot smaller

* Marking onleavezone as deprecated

Co-authored-by: Piotr 'pwh' Hanusiak <p.hanusiak@workadventu.re>
Co-authored-by: David Négrier <d.negrier@thecodingmachine.com>
This commit is contained in:
Piotr Hanusiak 2022-04-07 14:23:53 +02:00 committed by GitHub
parent 106ee755a8
commit 02f06a913b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 143 additions and 2 deletions

View File

@ -150,6 +150,7 @@ export class GameRoom {
position, position,
false, false,
this.positionNotifier, this.positionNotifier,
joinRoomMessage.getAway(),
socket, socket,
joinRoomMessage.getTagList(), joinRoomMessage.getTagList(),
joinRoomMessage.getVisitcardurl(), joinRoomMessage.getVisitcardurl(),

View File

@ -32,6 +32,7 @@ export class User implements Movable {
private position: PointInterface, private position: PointInterface,
public silent: boolean, public silent: boolean,
private positionNotifier: PositionNotifier, private positionNotifier: PositionNotifier,
private away: boolean,
public readonly socket: UserSocket, public readonly socket: UserSocket,
public readonly tags: string[], public readonly tags: string[],
public readonly visitCardUrl: string | null, public readonly visitCardUrl: string | null,
@ -89,6 +90,10 @@ export class User implements Movable {
return this.outlineColor; return this.outlineColor;
} }
public isAway(): boolean {
return this.away;
}
get following(): User | undefined { get following(): User | undefined {
return this._following; return this._following;
} }
@ -129,6 +134,11 @@ export class User implements Movable {
} }
this.voiceIndicatorShown = details.getShowvoiceindicator()?.getValue(); this.voiceIndicatorShown = details.getShowvoiceindicator()?.getValue();
const away = details.getAway();
if (away) {
this.away = away.getValue();
}
const playerDetails = new SetPlayerDetailsMessage(); const playerDetails = new SetPlayerDetailsMessage();
if (this.outlineColor !== undefined) { if (this.outlineColor !== undefined) {
@ -137,6 +147,9 @@ export class User implements Movable {
if (this.voiceIndicatorShown !== undefined) { if (this.voiceIndicatorShown !== undefined) {
playerDetails.setShowvoiceindicator(new BoolValue().setValue(this.voiceIndicatorShown)); playerDetails.setShowvoiceindicator(new BoolValue().setValue(this.voiceIndicatorShown));
} }
if (details.getAway() !== undefined) {
playerDetails.setAway(new BoolValue().setValue(this.away));
}
this.positionNotifier.updatePlayerDetails(this, playerDetails); this.positionNotifier.updatePlayerDetails(this, playerDetails);
} }

View File

@ -331,6 +331,7 @@ export class SocketManager {
userJoinedZoneMessage.setUserid(thing.id); userJoinedZoneMessage.setUserid(thing.id);
userJoinedZoneMessage.setUseruuid(thing.uuid); userJoinedZoneMessage.setUseruuid(thing.uuid);
userJoinedZoneMessage.setName(thing.name); userJoinedZoneMessage.setName(thing.name);
userJoinedZoneMessage.setAway(thing.isAway());
userJoinedZoneMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers)); userJoinedZoneMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
userJoinedZoneMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition())); userJoinedZoneMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
userJoinedZoneMessage.setFromzone(this.toProtoZone(fromZone)); userJoinedZoneMessage.setFromzone(this.toProtoZone(fromZone));
@ -658,6 +659,7 @@ export class SocketManager {
userJoinedMessage.setUserid(thing.id); userJoinedMessage.setUserid(thing.id);
userJoinedMessage.setUseruuid(thing.uuid); userJoinedMessage.setUseruuid(thing.uuid);
userJoinedMessage.setName(thing.name); userJoinedMessage.setName(thing.name);
userJoinedMessage.setAway(thing.isAway());
userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers)); userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition())); userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
if (thing.visitCardUrl) { if (thing.visitCardUrl) {

View File

@ -41,6 +41,7 @@ describe("PositionNotifier", () => {
}, },
false, false,
positionNotifier, positionNotifier,
false,
{} as UserSocket, {} as UserSocket,
[], [],
null, null,
@ -60,6 +61,7 @@ describe("PositionNotifier", () => {
}, },
false, false,
positionNotifier, positionNotifier,
false,
{} as UserSocket, {} as UserSocket,
[], [],
null, null,
@ -149,6 +151,7 @@ describe("PositionNotifier", () => {
}, },
false, false,
positionNotifier, positionNotifier,
false,
{} as UserSocket, {} as UserSocket,
[], [],
null, null,
@ -168,6 +171,7 @@ describe("PositionNotifier", () => {
}, },
false, false,
positionNotifier, positionNotifier,
false,
{} as UserSocket, {} as UserSocket,
[], [],
null, null,

View File

@ -14,6 +14,7 @@ export interface MessageUserPositionInterface {
name: string; name: string;
characterLayers: BodyResourceDescriptionInterface[]; characterLayers: BodyResourceDescriptionInterface[];
position: PointInterface; position: PointInterface;
away: boolean;
visitCardUrl: string | null; visitCardUrl: string | null;
companion: string | null; companion: string | null;
userUuid: string; userUuid: string;
@ -29,6 +30,7 @@ export interface MessageUserJoined {
name: string; name: string;
characterLayers: BodyResourceDescriptionInterface[]; characterLayers: BodyResourceDescriptionInterface[];
position: PointInterface; position: PointInterface;
away: boolean;
visitCardUrl: string | null; visitCardUrl: string | null;
companion: string | null; companion: string | null;
userUuid: string; userUuid: string;

View File

@ -518,6 +518,20 @@ export class RoomConnection implements RoomConnection {
this.socket.send(bytes); this.socket.send(bytes);
} }
public emitPlayerAway(away: boolean): void {
const message = SetPlayerDetailsMessageTsProto.fromPartial({
away,
});
const bytes = ClientToServerMessageTsProto.encode({
message: {
$case: "setPlayerDetailsMessage",
setPlayerDetailsMessage: message,
},
}).finish();
this.socket.send(bytes);
}
public emitPlayerOutlineColor(color: number | null) { public emitPlayerOutlineColor(color: number | null) {
let message: SetPlayerDetailsMessageTsProto; let message: SetPlayerDetailsMessageTsProto;
if (color === null) { if (color === null) {
@ -654,6 +668,7 @@ export class RoomConnection implements RoomConnection {
characterLayers, characterLayers,
visitCardUrl: message.visitCardUrl, visitCardUrl: message.visitCardUrl,
position: ProtobufClientUtils.toPointInterface(position), position: ProtobufClientUtils.toPointInterface(position),
away: message.away,
companion: companion ? companion.name : null, companion: companion ? companion.name : null,
userUuid: message.userUuid, userUuid: message.userUuid,
outlineColor: message.hasOutline ? message.outlineColor : undefined, outlineColor: message.hasOutline ? message.outlineColor : undefined,

View File

@ -0,0 +1,65 @@
import { Easing } from "../../types";
export class PlayerStatusDot extends Phaser.GameObjects.Container {
private graphics: Phaser.GameObjects.Graphics;
private away: boolean;
private readonly COLORS = {
// online: 0x00ff00,
// away: 0xffff00,
online: 0x8cc43f,
onlineOutline: 0x427a25,
away: 0xf5931e,
awayOutline: 0x875d13,
};
constructor(scene: Phaser.Scene, x: number, y: number) {
super(scene, x, y);
this.away = false;
this.graphics = this.scene.add.graphics();
this.add(this.graphics);
this.redraw();
this.scene.add.existing(this);
}
public setAway(away: boolean = true, instant: boolean = false): void {
if (this.away === away) {
return;
}
this.away = away;
if (instant) {
this.redraw();
} else {
this.playStatusChangeAnimation();
}
}
private playStatusChangeAnimation(): void {
this.scale = 1;
this.scene.tweens.add({
targets: [this],
duration: 200,
yoyo: true,
ease: Easing.BackEaseIn,
scale: 0,
onYoyo: () => {
this.redraw();
},
onComplete: () => {
this.scale = 1;
},
});
}
private redraw(): void {
this.graphics.clear();
this.graphics.fillStyle(this.away ? this.COLORS.away : this.COLORS.online);
this.graphics.lineStyle(1, this.away ? this.COLORS.awayOutline : this.COLORS.onlineOutline);
this.graphics.fillCircle(0, 0, 3);
this.graphics.strokeCircle(0, 0, 3);
}
}

View File

@ -24,6 +24,7 @@ import type { OutlineableInterface } from "../Game/OutlineableInterface";
import type CancelablePromise from "cancelable-promise"; import type CancelablePromise from "cancelable-promise";
import { TalkIcon } from "../Components/TalkIcon"; import { TalkIcon } from "../Components/TalkIcon";
import { Deferred } from "ts-deferred"; import { Deferred } from "ts-deferred";
import { PlayerStatusDot } from "../Components/PlayerStatusDot";
const playerNameY = -25; const playerNameY = -25;
const interactiveRadius = 35; const interactiveRadius = 35;
@ -32,6 +33,7 @@ export abstract class Character extends Container implements OutlineableInterfac
private bubble: SpeechBubble | null = null; private bubble: SpeechBubble | null = null;
private readonly playerNameText: Text; private readonly playerNameText: Text;
private readonly talkIcon: TalkIcon; private readonly talkIcon: TalkIcon;
protected readonly statusDot: PlayerStatusDot;
public playerName: string; public playerName: string;
public sprites: Map<string, Sprite>; public sprites: Map<string, Sprite>;
protected lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down; protected lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down;
@ -137,7 +139,8 @@ export abstract class Character extends Container implements OutlineableInterfac
}); });
} }
this.playerNameText.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX); this.playerNameText.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
this.add(this.playerNameText); this.statusDot = new PlayerStatusDot(scene, this.playerNameText.getLeftCenter().x - 6, playerNameY - 1);
this.add([this.playerNameText, this.statusDot]);
this.setClickable(isClickable); this.setClickable(isClickable);
@ -238,6 +241,10 @@ export abstract class Character extends Container implements OutlineableInterfac
this.talkIcon.show(show, forceClose); this.talkIcon.show(show, forceClose);
} }
public setAwayStatus(away: boolean = true, instant: boolean = false): void {
this.statusDot.setAway(away, instant);
}
public addCompanion(name: string, texturePromise?: CancelablePromise<string>): void { public addCompanion(name: string, texturePromise?: CancelablePromise<string>): void {
if (typeof texturePromise !== "undefined") { if (typeof texturePromise !== "undefined") {
this.companion = new Companion(this.scene, this.x, this.y, name, texturePromise); this.companion = new Companion(this.scene, this.x, this.y, name, texturePromise);

View File

@ -202,6 +202,8 @@ export class GameMap {
/** /**
* Registers a callback called when the user moves inside another zone. * Registers a callback called when the user moves inside another zone.
*
* @deprecated
*/ */
public onEnterZone(callback: zoneChangeCallback) { public onEnterZone(callback: zoneChangeCallback) {
this.enterZoneCallbacks.push(callback); this.enterZoneCallbacks.push(callback);
@ -209,6 +211,8 @@ export class GameMap {
/** /**
* Registers a callback called when the user moves outside another zone. * Registers a callback called when the user moves outside another zone.
*
* @deprecated
*/ */
public onLeaveZone(callback: zoneChangeCallback) { public onLeaveZone(callback: zoneChangeCallback) {
this.leaveZoneCallbacks.push(callback); this.leaveZoneCallbacks.push(callback);

View File

@ -102,6 +102,7 @@ import CancelablePromise from "cancelable-promise";
import { Deferred } from "ts-deferred"; import { Deferred } from "ts-deferred";
import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin"; import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin";
import { PlayerDetailsUpdatedMessage } from "../../Messages/ts-proto-generated/protos/messages"; import { PlayerDetailsUpdatedMessage } from "../../Messages/ts-proto-generated/protos/messages";
import { privacyShutdownStore } from "../../Stores/PrivacyShutdownStore";
export interface GameSceneInitInterface { export interface GameSceneInitInterface {
initPosition: PointInterface | null; initPosition: PointInterface | null;
reconnecting: boolean; reconnecting: boolean;
@ -177,6 +178,7 @@ export class GameScene extends DirtyScene {
private localVolumeStoreUnsubscriber: Unsubscriber | undefined; private localVolumeStoreUnsubscriber: Unsubscriber | undefined;
private followUsersColorStoreUnsubscribe!: Unsubscriber; private followUsersColorStoreUnsubscribe!: Unsubscriber;
private privacyShutdownStoreUnsubscribe!: Unsubscriber;
private userIsJitsiDominantSpeakerStoreUnsubscriber!: Unsubscriber; private userIsJitsiDominantSpeakerStoreUnsubscriber!: Unsubscriber;
private jitsiParticipantsCountStoreUnsubscriber!: Unsubscriber; private jitsiParticipantsCountStoreUnsubscriber!: Unsubscriber;
@ -705,6 +707,10 @@ export class GameScene extends DirtyScene {
} }
}); });
this.privacyShutdownStoreUnsubscribe = privacyShutdownStore.subscribe((away) => {
this.connection?.emitPlayerAway(away);
});
Promise.all([ Promise.all([
this.connectionAnswerPromiseDeferred.promise as Promise<unknown>, this.connectionAnswerPromiseDeferred.promise as Promise<unknown>,
...scriptPromises, ...scriptPromises,
@ -763,6 +769,7 @@ export class GameScene extends DirtyScene {
characterLayers: message.characterLayers, characterLayers: message.characterLayers,
name: message.name, name: message.name,
position: message.position, position: message.position,
away: message.away,
visitCardUrl: message.visitCardUrl, visitCardUrl: message.visitCardUrl,
companion: message.companion, companion: message.companion,
userUuid: message.userUuid, userUuid: message.userUuid,
@ -1568,6 +1575,7 @@ ${escapedMessage}
this.emoteUnsubscribe(); this.emoteUnsubscribe();
this.emoteMenuUnsubscribe(); this.emoteMenuUnsubscribe();
this.followUsersColorStoreUnsubscribe(); this.followUsersColorStoreUnsubscribe();
this.privacyShutdownStoreUnsubscribe();
this.biggestAvailableAreaStoreUnsubscribe(); this.biggestAvailableAreaStoreUnsubscribe();
this.userIsJitsiDominantSpeakerStoreUnsubscriber(); this.userIsJitsiDominantSpeakerStoreUnsubscriber();
this.jitsiParticipantsCountStoreUnsubscriber(); this.jitsiParticipantsCountStoreUnsubscriber();
@ -1940,6 +1948,9 @@ ${escapedMessage}
if (addPlayerData.outlineColor !== undefined) { if (addPlayerData.outlineColor !== undefined) {
player.setApiOutlineColor(addPlayerData.outlineColor); player.setApiOutlineColor(addPlayerData.outlineColor);
} }
if (addPlayerData.away !== undefined) {
player.setAwayStatus(addPlayerData.away, true);
}
this.MapPlayers.add(player); this.MapPlayers.add(player);
this.MapPlayersByKey.set(player.userId, player); this.MapPlayersByKey.set(player.userId, player);
player.updatePosition(addPlayerData.position); player.updatePosition(addPlayerData.position);
@ -2091,6 +2102,9 @@ ${escapedMessage}
if (message.details?.showVoiceIndicator !== undefined) { if (message.details?.showVoiceIndicator !== undefined) {
character.showTalkIcon(message.details?.showVoiceIndicator); character.showTalkIcon(message.details?.showVoiceIndicator);
} }
if (message.details?.away !== undefined) {
character.setAwayStatus(message.details?.away);
}
} }
/** /**

View File

@ -7,6 +7,7 @@ export interface PlayerInterface {
visitCardUrl: string | null; visitCardUrl: string | null;
companion: string | null; companion: string | null;
userUuid: string; userUuid: string;
away: boolean;
color?: string; color?: string;
outlineColor?: number; outlineColor?: number;
} }

View File

@ -28,7 +28,7 @@ export class Player extends Character {
companionTexturePromise?: CancelablePromise<string> companionTexturePromise?: CancelablePromise<string>
) { ) {
super(Scene, x, y, texturesPromise, name, direction, moving, 1, true, companion, companionTexturePromise); super(Scene, x, y, texturesPromise, name, direction, moving, 1, true, companion, companionTexturePromise);
this.statusDot.setVisible(false);
//the current player model should be push away by other players to prevent conflict //the current player model should be push away by other players to prevent conflict
this.getBody().setImmovable(false); this.getBody().setImmovable(false);
} }

View File

@ -30,6 +30,7 @@ function createPlayersStore() {
visitCardUrl: message.visitCardUrl, visitCardUrl: message.visitCardUrl,
companion: message.companion, companion: message.companion,
userUuid: message.userUuid, userUuid: message.userUuid,
away: message.away,
color: getRandomColor(), color: getRandomColor(),
}); });
return users; return users;
@ -59,6 +60,7 @@ function createPlayersStore() {
characterLayers: [], characterLayers: [],
visitCardUrl: null, visitCardUrl: null,
companion: null, companion: null,
away: false,
userUuid: "dummy", userUuid: "dummy",
color: getRandomColor(), color: getRandomColor(),
}); });

View File

@ -53,6 +53,7 @@ message SetPlayerDetailsMessage {
google.protobuf.UInt32Value outlineColor = 3; google.protobuf.UInt32Value outlineColor = 3;
google.protobuf.BoolValue removeOutlineColor = 4; google.protobuf.BoolValue removeOutlineColor = 4;
google.protobuf.BoolValue showVoiceIndicator = 5; google.protobuf.BoolValue showVoiceIndicator = 5;
google.protobuf.BoolValue away = 6;
} }
message UserMovesMessage { message UserMovesMessage {
@ -206,6 +207,7 @@ message UserJoinedMessage {
string userUuid = 7; string userUuid = 7;
uint32 outlineColor = 8; uint32 outlineColor = 8;
bool hasOutline = 9; bool hasOutline = 9;
bool away = 10;
} }
message UserLeftMessage { message UserLeftMessage {
@ -344,6 +346,7 @@ message JoinRoomMessage {
CompanionMessage companion = 8; CompanionMessage companion = 8;
string visitCardUrl = 9; string visitCardUrl = 9;
string userRoomToken = 10; string userRoomToken = 10;
bool away = 11;
} }
message UserJoinedZoneMessage { message UserJoinedZoneMessage {
@ -357,6 +360,7 @@ message UserJoinedZoneMessage {
string userUuid = 8; string userUuid = 8;
uint32 outlineColor = 9; uint32 outlineColor = 9;
bool hasOutline = 10; bool hasOutline = 10;
bool away = 11;
} }
message UserLeftZoneMessage { message UserLeftZoneMessage {

View File

@ -49,6 +49,7 @@ export class UserDescriptor {
private name: string, private name: string,
private characterLayers: CharacterLayerMessage[], private characterLayers: CharacterLayerMessage[],
private position: PositionMessage, private position: PositionMessage,
private away: boolean,
private visitCardUrl: string | null, private visitCardUrl: string | null,
private companion?: CompanionMessage, private companion?: CompanionMessage,
private outlineColor?: number private outlineColor?: number
@ -69,6 +70,7 @@ export class UserDescriptor {
message.getName(), message.getName(),
message.getCharacterlayersList(), message.getCharacterlayersList(),
position, position,
message.getAway(),
message.getVisitcardurl(), message.getVisitcardurl(),
message.getCompanion(), message.getCompanion(),
message.getHasoutline() ? message.getOutlinecolor() : undefined message.getHasoutline() ? message.getOutlinecolor() : undefined
@ -89,6 +91,10 @@ export class UserDescriptor {
} else { } else {
this.outlineColor = playerDetails.getOutlinecolor()?.getValue(); this.outlineColor = playerDetails.getOutlinecolor()?.getValue();
} }
const away = playerDetails.getAway();
if (away) {
this.away = away.getValue();
}
} }
public toUserJoinedMessage(): UserJoinedMessage { public toUserJoinedMessage(): UserJoinedMessage {
@ -98,6 +104,7 @@ export class UserDescriptor {
userJoinedMessage.setName(this.name); userJoinedMessage.setName(this.name);
userJoinedMessage.setCharacterlayersList(this.characterLayers); userJoinedMessage.setCharacterlayersList(this.characterLayers);
userJoinedMessage.setPosition(this.position); userJoinedMessage.setPosition(this.position);
userJoinedMessage.setAway(this.away);
if (this.visitCardUrl) { if (this.visitCardUrl) {
userJoinedMessage.setVisitcardurl(this.visitCardUrl); userJoinedMessage.setVisitcardurl(this.visitCardUrl);
} }