From 524339a3a005d23386c0e49e9922c189973c48f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= <d.negrier@thecodingmachine.com> Date: Thu, 23 Dec 2021 18:07:51 +0100 Subject: [PATCH] Adding hightlight to player names when they follow each others --- .../Components/FollowMenu/FollowMenu.svelte | 4 +- front/src/Connexion/RoomConnection.ts | 19 +--- front/src/Phaser/Game/GameScene.ts | 23 ++++- front/src/Stores/FollowStore.ts | 87 ++++++++++++++++++- front/src/WebRtc/ColorGenerator.ts | 26 +++++- 5 files changed, 132 insertions(+), 27 deletions(-) diff --git a/front/src/Components/FollowMenu/FollowMenu.svelte b/front/src/Components/FollowMenu/FollowMenu.svelte index 72f55613..c38571bd 100644 --- a/front/src/Components/FollowMenu/FollowMenu.svelte +++ b/front/src/Components/FollowMenu/FollowMenu.svelte @@ -72,9 +72,7 @@ vim: ft=typescript function reset() { gameScene.connection?.emitFollowAbort(); - followStateStore.set(followStates.off); - followRoleStore.set(followRoles.leader); - followUsersStore.set([]); + followUsersStore.stopFollowing(); } function request() { diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index b9a3ca12..6e88d3d8 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -271,28 +271,17 @@ export class RoomConnection implements RoomConnection { } else if (message.hasFollowrequestmessage()) { const requestMessage = message.getFollowrequestmessage() as FollowRequestMessage; if (!localUserStore.getIgnoreFollowRequests()) { - followStateStore.set(followStates.requesting); - followRoleStore.set(followRoles.follower); - followUsersStore.set([requestMessage.getLeader()]); + followUsersStore.addFollowRequest(requestMessage.getLeader()); } } else if (message.hasFollowconfirmationmessage()) { const responseMessage = message.getFollowconfirmationmessage() as FollowConfirmationMessage; - followUsersStore.set([...get(followUsersStore), responseMessage.getFollower()]); + followUsersStore.addFollower(responseMessage.getFollower()); } else if (message.hasFollowabortmessage()) { const abortMessage = message.getFollowabortmessage() as FollowAbortMessage; if (get(followRoleStore) === followRoles.follower) { - followStateStore.set(followStates.off); - followRoleStore.set(followRoles.leader); - followUsersStore.set([]); + followUsersStore.stopFollowing(); } else { - let followers = get(followUsersStore); - const oldFollowerCount = followers.length; - followers = followers.filter((name) => name !== abortMessage.getFollower()); - followUsersStore.set(followers); - if (followers.length === 0 && oldFollowerCount > 0) { - followStateStore.set(followStates.off); - followRoleStore.set(followRoles.leader); - } + followUsersStore.removeFollower(abortMessage.getFollower()); } } else if (message.hasErrormessage()) { const errorMessage = message.getErrormessage() as ErrorMessage; diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index b31ed83b..4800e259 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1,7 +1,7 @@ import type { Subscription } from "rxjs"; import AnimatedTiles from "phaser-animated-tiles"; import { Queue } from "queue-typescript"; -import { get } from "svelte/store"; +import { get, Unsubscriber } from "svelte/store"; import { userMessageManager } from "../../Administration/UserMessageManager"; import { connectionManager } from "../../Connexion/ConnectionManager"; @@ -91,6 +91,8 @@ import { deepCopy } from "deep-copy-ts"; import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR; import { MapStore } from "../../Stores/Utils/MapStore"; import { SetPlayerDetailsMessage } from "../../Messages/generated/messages_pb"; +import { followUsersColorStore, followUsersStore } from "../../Stores/FollowStore"; +import { getColorRgbFromHue } from "../../WebRtc/ColorGenerator"; export interface GameSceneInitInterface { initPosition: PointInterface | null; @@ -165,9 +167,11 @@ export class GameScene extends DirtyScene { private createPromise: Promise<void>; private createPromiseResolve!: (value?: void | PromiseLike<void>) => void; private iframeSubscriptionList!: Array<Subscription>; - private peerStoreUnsubscribe!: () => void; - private emoteUnsubscribe!: () => void; - private emoteMenuUnsubscribe!: () => void; + private peerStoreUnsubscribe!: Unsubscriber; + private emoteUnsubscribe!: Unsubscriber; + private emoteMenuUnsubscribe!: Unsubscriber; + private followUsersColorStoreUnsubscribe!: Unsubscriber; + private biggestAvailableAreaStoreUnsubscribe!: () => void; MapUrlFile: string; roomUrl: string; @@ -646,6 +650,16 @@ export class GameScene extends DirtyScene { } }); + this.followUsersColorStoreUnsubscribe = followUsersColorStore.subscribe((color) => { + if (color !== undefined) { + this.CurrentPlayer.setOutlineColor(color); + this.connection?.emitPlayerOutlineColor(color); + } else { + this.CurrentPlayer.removeOutlineColor(); + this.connection?.emitPlayerOutlineColor(null); + } + }); + Promise.all([this.connectionAnswerPromise as Promise<unknown>, ...scriptPromises]).then(() => { this.scene.wake(); }); @@ -1443,6 +1457,7 @@ ${escapedMessage} this.peerStoreUnsubscribe(); this.emoteUnsubscribe(); this.emoteMenuUnsubscribe(); + this.followUsersColorStoreUnsubscribe(); this.biggestAvailableAreaStoreUnsubscribe(); iframeListener.unregisterAnswerer("getState"); iframeListener.unregisterAnswerer("loadTileset"); diff --git a/front/src/Stores/FollowStore.ts b/front/src/Stores/FollowStore.ts index 6c85ab17..856d02fe 100644 --- a/front/src/Stores/FollowStore.ts +++ b/front/src/Stores/FollowStore.ts @@ -1,4 +1,6 @@ -import { writable } from "svelte/store"; +import { derived, writable } from "svelte/store"; +import { getColorRgbFromHue } from "../WebRtc/ColorGenerator"; +import { gameManager } from "../Phaser/Game/GameManager"; export const followStates = { off: "off", @@ -14,4 +16,85 @@ export const followRoles = { export const followStateStore = writable(followStates.off); export const followRoleStore = writable(followRoles.leader); -export const followUsersStore = writable<number[]>([]); +//export const followUsersStore = writable<number[]>([]); + +function createFollowUsersStore() { + const { subscribe, update, set } = writable<number[]>([]); + + return { + subscribe, + addFollowRequest(leader: number): void { + followStateStore.set(followStates.requesting); + followRoleStore.set(followRoles.follower); + set([leader]); + }, + addFollower(user: number): void { + update((followers) => { + followers.push(user); + return followers; + }); + }, + /** + * Removes the follower from the store. + * Will update followStateStore and followRoleStore if nobody is following anymore. + * @param user + */ + removeFollower(user: number): void { + update((followers) => { + const oldFollowerCount = followers.length; + followers = followers.filter((id) => id !== user); + + if (followers.length === 0 && oldFollowerCount > 0) { + followStateStore.set(followStates.off); + followRoleStore.set(followRoles.leader); + } + + return followers; + }); + }, + stopFollowing(): void { + set([]); + followStateStore.set(followStates.off); + followRoleStore.set(followRoles.leader); + }, + }; +} + +export const followUsersStore = createFollowUsersStore(); + +/** + * This store contains the color of the follow group. It is derived from the ID of the leader. + */ +export const followUsersColorStore = derived( + [followStateStore, followRoleStore, followUsersStore], + ([$followStateStore, $followRoleStore, $followUsersStore]) => { + console.log($followStateStore); + if ($followStateStore !== followStates.active) { + return undefined; + } + + if ($followUsersStore.length === 0) { + return undefined; + } + + let leaderId: number; + if ($followRoleStore === followRoles.leader) { + // Let's get my ID by a quite complicated way.... + leaderId = gameManager.getCurrentGameScene().connection?.getUserId() ?? 0; + } else { + leaderId = $followUsersStore[0]; + } + + // Let's compute a random hue between 0 and 1 that varies enough to be interesting + const hue = ((leaderId * 197) % 255) / 255; + + let { r, g, b } = getColorRgbFromHue(hue); + if ($followRoleStore === followRoles.follower) { + // Let's make the followers very slightly darker + r *= 0.9; + g *= 0.9; + b *= 0.9; + } + return (Math.round(r * 255) << 16) | (Math.round(g * 255) << 8) | Math.round(b * 255); + } +); diff --git a/front/src/WebRtc/ColorGenerator.ts b/front/src/WebRtc/ColorGenerator.ts index be192f9f..f78671e6 100644 --- a/front/src/WebRtc/ColorGenerator.ts +++ b/front/src/WebRtc/ColorGenerator.ts @@ -1,13 +1,29 @@ export function getRandomColor(): string { + const { r, g, b } = getColorRgbFromHue(Math.random()); + return toHexa(r, g, b); +} + +function toHexa(r: number, g: number, b: number): string { + return "#" + Math.floor(r * 256).toString(16) + Math.floor(g * 256).toString(16) + Math.floor(b * 256).toString(16); +} + +export function getColorRgbFromHue(hue: number): { r: number; g: number; b: number } { const golden_ratio_conjugate = 0.618033988749895; - let hue = Math.random(); hue += golden_ratio_conjugate; hue %= 1; return hsv_to_rgb(hue, 0.5, 0.95); } +function stringToDouble(string: string): number { + let num = 1; + for (const char of string.split("")) { + num *= char.charCodeAt(0); + } + return (num % 255) / 255; +} + //todo: test this. -function hsv_to_rgb(hue: number, saturation: number, brightness: number): string { +function hsv_to_rgb(hue: number, saturation: number, brightness: number): { r: number; g: number; b: number } { const h_i = Math.floor(hue * 6); const f = hue * 6 - h_i; const p = brightness * (1 - saturation); @@ -48,5 +64,9 @@ function hsv_to_rgb(hue: number, saturation: number, brightness: number): string default: throw "h_i cannot be " + h_i; } - return "#" + Math.floor(r * 256).toString(16) + Math.floor(g * 256).toString(16) + Math.floor(b * 256).toString(16); + return { + r, + g, + b, + }; }