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:
parent
106ee755a8
commit
02f06a913b
@ -150,6 +150,7 @@ export class GameRoom {
|
||||
position,
|
||||
false,
|
||||
this.positionNotifier,
|
||||
joinRoomMessage.getAway(),
|
||||
socket,
|
||||
joinRoomMessage.getTagList(),
|
||||
joinRoomMessage.getVisitcardurl(),
|
||||
|
@ -32,6 +32,7 @@ export class User implements Movable {
|
||||
private position: PointInterface,
|
||||
public silent: boolean,
|
||||
private positionNotifier: PositionNotifier,
|
||||
private away: boolean,
|
||||
public readonly socket: UserSocket,
|
||||
public readonly tags: string[],
|
||||
public readonly visitCardUrl: string | null,
|
||||
@ -89,6 +90,10 @@ export class User implements Movable {
|
||||
return this.outlineColor;
|
||||
}
|
||||
|
||||
public isAway(): boolean {
|
||||
return this.away;
|
||||
}
|
||||
|
||||
get following(): User | undefined {
|
||||
return this._following;
|
||||
}
|
||||
@ -129,6 +134,11 @@ export class User implements Movable {
|
||||
}
|
||||
this.voiceIndicatorShown = details.getShowvoiceindicator()?.getValue();
|
||||
|
||||
const away = details.getAway();
|
||||
if (away) {
|
||||
this.away = away.getValue();
|
||||
}
|
||||
|
||||
const playerDetails = new SetPlayerDetailsMessage();
|
||||
|
||||
if (this.outlineColor !== undefined) {
|
||||
@ -137,6 +147,9 @@ export class User implements Movable {
|
||||
if (this.voiceIndicatorShown !== undefined) {
|
||||
playerDetails.setShowvoiceindicator(new BoolValue().setValue(this.voiceIndicatorShown));
|
||||
}
|
||||
if (details.getAway() !== undefined) {
|
||||
playerDetails.setAway(new BoolValue().setValue(this.away));
|
||||
}
|
||||
|
||||
this.positionNotifier.updatePlayerDetails(this, playerDetails);
|
||||
}
|
||||
|
@ -331,6 +331,7 @@ export class SocketManager {
|
||||
userJoinedZoneMessage.setUserid(thing.id);
|
||||
userJoinedZoneMessage.setUseruuid(thing.uuid);
|
||||
userJoinedZoneMessage.setName(thing.name);
|
||||
userJoinedZoneMessage.setAway(thing.isAway());
|
||||
userJoinedZoneMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
|
||||
userJoinedZoneMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
|
||||
userJoinedZoneMessage.setFromzone(this.toProtoZone(fromZone));
|
||||
@ -658,6 +659,7 @@ export class SocketManager {
|
||||
userJoinedMessage.setUserid(thing.id);
|
||||
userJoinedMessage.setUseruuid(thing.uuid);
|
||||
userJoinedMessage.setName(thing.name);
|
||||
userJoinedMessage.setAway(thing.isAway());
|
||||
userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
|
||||
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
|
||||
if (thing.visitCardUrl) {
|
||||
|
@ -41,6 +41,7 @@ describe("PositionNotifier", () => {
|
||||
},
|
||||
false,
|
||||
positionNotifier,
|
||||
false,
|
||||
{} as UserSocket,
|
||||
[],
|
||||
null,
|
||||
@ -60,6 +61,7 @@ describe("PositionNotifier", () => {
|
||||
},
|
||||
false,
|
||||
positionNotifier,
|
||||
false,
|
||||
{} as UserSocket,
|
||||
[],
|
||||
null,
|
||||
@ -149,6 +151,7 @@ describe("PositionNotifier", () => {
|
||||
},
|
||||
false,
|
||||
positionNotifier,
|
||||
false,
|
||||
{} as UserSocket,
|
||||
[],
|
||||
null,
|
||||
@ -168,6 +171,7 @@ describe("PositionNotifier", () => {
|
||||
},
|
||||
false,
|
||||
positionNotifier,
|
||||
false,
|
||||
{} as UserSocket,
|
||||
[],
|
||||
null,
|
||||
|
@ -14,6 +14,7 @@ export interface MessageUserPositionInterface {
|
||||
name: string;
|
||||
characterLayers: BodyResourceDescriptionInterface[];
|
||||
position: PointInterface;
|
||||
away: boolean;
|
||||
visitCardUrl: string | null;
|
||||
companion: string | null;
|
||||
userUuid: string;
|
||||
@ -29,6 +30,7 @@ export interface MessageUserJoined {
|
||||
name: string;
|
||||
characterLayers: BodyResourceDescriptionInterface[];
|
||||
position: PointInterface;
|
||||
away: boolean;
|
||||
visitCardUrl: string | null;
|
||||
companion: string | null;
|
||||
userUuid: string;
|
||||
|
@ -518,6 +518,20 @@ export class RoomConnection implements RoomConnection {
|
||||
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) {
|
||||
let message: SetPlayerDetailsMessageTsProto;
|
||||
if (color === null) {
|
||||
@ -654,6 +668,7 @@ export class RoomConnection implements RoomConnection {
|
||||
characterLayers,
|
||||
visitCardUrl: message.visitCardUrl,
|
||||
position: ProtobufClientUtils.toPointInterface(position),
|
||||
away: message.away,
|
||||
companion: companion ? companion.name : null,
|
||||
userUuid: message.userUuid,
|
||||
outlineColor: message.hasOutline ? message.outlineColor : undefined,
|
||||
|
65
front/src/Phaser/Components/PlayerStatusDot.ts
Normal file
65
front/src/Phaser/Components/PlayerStatusDot.ts
Normal 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);
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ import type { OutlineableInterface } from "../Game/OutlineableInterface";
|
||||
import type CancelablePromise from "cancelable-promise";
|
||||
import { TalkIcon } from "../Components/TalkIcon";
|
||||
import { Deferred } from "ts-deferred";
|
||||
import { PlayerStatusDot } from "../Components/PlayerStatusDot";
|
||||
|
||||
const playerNameY = -25;
|
||||
const interactiveRadius = 35;
|
||||
@ -32,6 +33,7 @@ export abstract class Character extends Container implements OutlineableInterfac
|
||||
private bubble: SpeechBubble | null = null;
|
||||
private readonly playerNameText: Text;
|
||||
private readonly talkIcon: TalkIcon;
|
||||
protected readonly statusDot: PlayerStatusDot;
|
||||
public playerName: string;
|
||||
public sprites: Map<string, Sprite>;
|
||||
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.add(this.playerNameText);
|
||||
this.statusDot = new PlayerStatusDot(scene, this.playerNameText.getLeftCenter().x - 6, playerNameY - 1);
|
||||
this.add([this.playerNameText, this.statusDot]);
|
||||
|
||||
this.setClickable(isClickable);
|
||||
|
||||
@ -238,6 +241,10 @@ export abstract class Character extends Container implements OutlineableInterfac
|
||||
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 {
|
||||
if (typeof texturePromise !== "undefined") {
|
||||
this.companion = new Companion(this.scene, this.x, this.y, name, texturePromise);
|
||||
|
@ -202,6 +202,8 @@ export class GameMap {
|
||||
|
||||
/**
|
||||
* Registers a callback called when the user moves inside another zone.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public onEnterZone(callback: zoneChangeCallback) {
|
||||
this.enterZoneCallbacks.push(callback);
|
||||
@ -209,6 +211,8 @@ export class GameMap {
|
||||
|
||||
/**
|
||||
* Registers a callback called when the user moves outside another zone.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public onLeaveZone(callback: zoneChangeCallback) {
|
||||
this.leaveZoneCallbacks.push(callback);
|
||||
|
@ -102,6 +102,7 @@ import CancelablePromise from "cancelable-promise";
|
||||
import { Deferred } from "ts-deferred";
|
||||
import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin";
|
||||
import { PlayerDetailsUpdatedMessage } from "../../Messages/ts-proto-generated/protos/messages";
|
||||
import { privacyShutdownStore } from "../../Stores/PrivacyShutdownStore";
|
||||
export interface GameSceneInitInterface {
|
||||
initPosition: PointInterface | null;
|
||||
reconnecting: boolean;
|
||||
@ -177,6 +178,7 @@ export class GameScene extends DirtyScene {
|
||||
|
||||
private localVolumeStoreUnsubscriber: Unsubscriber | undefined;
|
||||
private followUsersColorStoreUnsubscribe!: Unsubscriber;
|
||||
private privacyShutdownStoreUnsubscribe!: Unsubscriber;
|
||||
private userIsJitsiDominantSpeakerStoreUnsubscriber!: Unsubscriber;
|
||||
private jitsiParticipantsCountStoreUnsubscriber!: Unsubscriber;
|
||||
|
||||
@ -705,6 +707,10 @@ export class GameScene extends DirtyScene {
|
||||
}
|
||||
});
|
||||
|
||||
this.privacyShutdownStoreUnsubscribe = privacyShutdownStore.subscribe((away) => {
|
||||
this.connection?.emitPlayerAway(away);
|
||||
});
|
||||
|
||||
Promise.all([
|
||||
this.connectionAnswerPromiseDeferred.promise as Promise<unknown>,
|
||||
...scriptPromises,
|
||||
@ -763,6 +769,7 @@ export class GameScene extends DirtyScene {
|
||||
characterLayers: message.characterLayers,
|
||||
name: message.name,
|
||||
position: message.position,
|
||||
away: message.away,
|
||||
visitCardUrl: message.visitCardUrl,
|
||||
companion: message.companion,
|
||||
userUuid: message.userUuid,
|
||||
@ -1568,6 +1575,7 @@ ${escapedMessage}
|
||||
this.emoteUnsubscribe();
|
||||
this.emoteMenuUnsubscribe();
|
||||
this.followUsersColorStoreUnsubscribe();
|
||||
this.privacyShutdownStoreUnsubscribe();
|
||||
this.biggestAvailableAreaStoreUnsubscribe();
|
||||
this.userIsJitsiDominantSpeakerStoreUnsubscriber();
|
||||
this.jitsiParticipantsCountStoreUnsubscriber();
|
||||
@ -1940,6 +1948,9 @@ ${escapedMessage}
|
||||
if (addPlayerData.outlineColor !== undefined) {
|
||||
player.setApiOutlineColor(addPlayerData.outlineColor);
|
||||
}
|
||||
if (addPlayerData.away !== undefined) {
|
||||
player.setAwayStatus(addPlayerData.away, true);
|
||||
}
|
||||
this.MapPlayers.add(player);
|
||||
this.MapPlayersByKey.set(player.userId, player);
|
||||
player.updatePosition(addPlayerData.position);
|
||||
@ -2091,6 +2102,9 @@ ${escapedMessage}
|
||||
if (message.details?.showVoiceIndicator !== undefined) {
|
||||
character.showTalkIcon(message.details?.showVoiceIndicator);
|
||||
}
|
||||
if (message.details?.away !== undefined) {
|
||||
character.setAwayStatus(message.details?.away);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7,6 +7,7 @@ export interface PlayerInterface {
|
||||
visitCardUrl: string | null;
|
||||
companion: string | null;
|
||||
userUuid: string;
|
||||
away: boolean;
|
||||
color?: string;
|
||||
outlineColor?: number;
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ export class Player extends Character {
|
||||
companionTexturePromise?: CancelablePromise<string>
|
||||
) {
|
||||
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
|
||||
this.getBody().setImmovable(false);
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ function createPlayersStore() {
|
||||
visitCardUrl: message.visitCardUrl,
|
||||
companion: message.companion,
|
||||
userUuid: message.userUuid,
|
||||
away: message.away,
|
||||
color: getRandomColor(),
|
||||
});
|
||||
return users;
|
||||
@ -59,6 +60,7 @@ function createPlayersStore() {
|
||||
characterLayers: [],
|
||||
visitCardUrl: null,
|
||||
companion: null,
|
||||
away: false,
|
||||
userUuid: "dummy",
|
||||
color: getRandomColor(),
|
||||
});
|
||||
|
@ -53,6 +53,7 @@ message SetPlayerDetailsMessage {
|
||||
google.protobuf.UInt32Value outlineColor = 3;
|
||||
google.protobuf.BoolValue removeOutlineColor = 4;
|
||||
google.protobuf.BoolValue showVoiceIndicator = 5;
|
||||
google.protobuf.BoolValue away = 6;
|
||||
}
|
||||
|
||||
message UserMovesMessage {
|
||||
@ -206,6 +207,7 @@ message UserJoinedMessage {
|
||||
string userUuid = 7;
|
||||
uint32 outlineColor = 8;
|
||||
bool hasOutline = 9;
|
||||
bool away = 10;
|
||||
}
|
||||
|
||||
message UserLeftMessage {
|
||||
@ -344,6 +346,7 @@ message JoinRoomMessage {
|
||||
CompanionMessage companion = 8;
|
||||
string visitCardUrl = 9;
|
||||
string userRoomToken = 10;
|
||||
bool away = 11;
|
||||
}
|
||||
|
||||
message UserJoinedZoneMessage {
|
||||
@ -357,6 +360,7 @@ message UserJoinedZoneMessage {
|
||||
string userUuid = 8;
|
||||
uint32 outlineColor = 9;
|
||||
bool hasOutline = 10;
|
||||
bool away = 11;
|
||||
}
|
||||
|
||||
message UserLeftZoneMessage {
|
||||
|
@ -49,6 +49,7 @@ export class UserDescriptor {
|
||||
private name: string,
|
||||
private characterLayers: CharacterLayerMessage[],
|
||||
private position: PositionMessage,
|
||||
private away: boolean,
|
||||
private visitCardUrl: string | null,
|
||||
private companion?: CompanionMessage,
|
||||
private outlineColor?: number
|
||||
@ -69,6 +70,7 @@ export class UserDescriptor {
|
||||
message.getName(),
|
||||
message.getCharacterlayersList(),
|
||||
position,
|
||||
message.getAway(),
|
||||
message.getVisitcardurl(),
|
||||
message.getCompanion(),
|
||||
message.getHasoutline() ? message.getOutlinecolor() : undefined
|
||||
@ -89,6 +91,10 @@ export class UserDescriptor {
|
||||
} else {
|
||||
this.outlineColor = playerDetails.getOutlinecolor()?.getValue();
|
||||
}
|
||||
const away = playerDetails.getAway();
|
||||
if (away) {
|
||||
this.away = away.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
public toUserJoinedMessage(): UserJoinedMessage {
|
||||
@ -98,6 +104,7 @@ export class UserDescriptor {
|
||||
userJoinedMessage.setName(this.name);
|
||||
userJoinedMessage.setCharacterlayersList(this.characterLayers);
|
||||
userJoinedMessage.setPosition(this.position);
|
||||
userJoinedMessage.setAway(this.away);
|
||||
if (this.visitCardUrl) {
|
||||
userJoinedMessage.setVisitcardurl(this.visitCardUrl);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user