now use custom emotes with tweens instead of transistions
@ -68,6 +68,7 @@ export class SocketManager {
|
|||||||
private rooms: Map<string, GameRoom> = new Map<string, GameRoom>();
|
private rooms: Map<string, GameRoom> = new Map<string, GameRoom>();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
||||||
clientEventsEmitter.registerToClientJoin((clientUUid: string, roomId: string) => {
|
clientEventsEmitter.registerToClientJoin((clientUUid: string, roomId: string) => {
|
||||||
gaugeManager.incNbClientPerRoomGauge(roomId);
|
gaugeManager.incNbClientPerRoomGauge(roomId);
|
||||||
});
|
});
|
||||||
|
BIN
front/dist/resources/emotes/clap-emote.png
vendored
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
front/dist/resources/emotes/hand-emote.png
vendored
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
front/dist/resources/emotes/heart-emote.png
vendored
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
front/dist/resources/emotes/pipo-popupemotes001.png
vendored
Before Width: | Height: | Size: 747 B |
BIN
front/dist/resources/emotes/pipo-popupemotes002.png
vendored
Before Width: | Height: | Size: 920 B |
BIN
front/dist/resources/emotes/pipo-popupemotes021.png
vendored
Before Width: | Height: | Size: 810 B |
BIN
front/dist/resources/emotes/taba-clap-emote.png
vendored
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.9 KiB |
BIN
front/dist/resources/emotes/taba-thumbsup-emote.png
vendored
Before Width: | Height: | Size: 1.9 KiB |
BIN
front/dist/resources/emotes/thanks-emote.png
vendored
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
front/dist/resources/emotes/thumb-down-emote.png
vendored
Normal file
After Width: | Height: | Size: 8.6 KiB |
BIN
front/dist/resources/emotes/thumb-up-emote.png
vendored
Normal file
After Width: | Height: | Size: 8.6 KiB |
@ -11,7 +11,7 @@ class EmoteEventStream {
|
|||||||
public stream = this._stream.asObservable();
|
public stream = this._stream.asObservable();
|
||||||
|
|
||||||
|
|
||||||
onMessage(userId: number, emoteName:string) {
|
fire(userId: number, emoteName:string) {
|
||||||
this._stream.next({userId, emoteName});
|
this._stream.next({userId, emoteName});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,7 +149,7 @@ export class RoomConnection implements RoomConnection {
|
|||||||
payload = subMessage.getItemeventmessage();
|
payload = subMessage.getItemeventmessage();
|
||||||
} else if (subMessage.hasEmoteeventmessage()) {
|
} else if (subMessage.hasEmoteeventmessage()) {
|
||||||
const emoteMessage = subMessage.getEmoteeventmessage() as EmoteEventMessage;
|
const emoteMessage = subMessage.getEmoteeventmessage() as EmoteEventMessage;
|
||||||
emoteEventStream.onMessage(emoteMessage.getActoruserid(), emoteMessage.getEmote());
|
emoteEventStream.fire(emoteMessage.getActoruserid(), emoteMessage.getEmote());
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unexpected batch message type');
|
throw new Error('Unexpected batch message type');
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,10 @@ import {DEPTH_UI_INDEX} from "../Game/DepthIndexes";
|
|||||||
import {waScaleManager} from "../Services/WaScaleManager";
|
import {waScaleManager} from "../Services/WaScaleManager";
|
||||||
|
|
||||||
export interface RadialMenuItem {
|
export interface RadialMenuItem {
|
||||||
sprite: string,
|
image: string,
|
||||||
frame: number,
|
|
||||||
name: string,
|
name: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const menuRadius = 60;
|
|
||||||
export const RadialMenuClickEvent = 'radialClick';
|
export const RadialMenuClickEvent = 'radialClick';
|
||||||
|
|
||||||
export class RadialMenu extends Phaser.GameObjects.Container {
|
export class RadialMenu extends Phaser.GameObjects.Container {
|
||||||
@ -27,24 +25,26 @@ export class RadialMenu extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
private initItems() {
|
private initItems() {
|
||||||
const itemsNumber = this.items.length;
|
const itemsNumber = this.items.length;
|
||||||
this.items.forEach((item, index) => this.createRadialElement(item, index, itemsNumber))
|
const menuRadius = 70 + (waScaleManager.uiScalingFactor - 1) * 20;
|
||||||
|
this.items.forEach((item, index) => this.createRadialElement(item, index, itemsNumber, menuRadius))
|
||||||
}
|
}
|
||||||
|
|
||||||
private createRadialElement(item: RadialMenuItem, index: number, itemsNumber: number) {
|
private createRadialElement(item: RadialMenuItem, index: number, itemsNumber: number, menuRadius: number) {
|
||||||
const image = new Sprite(this.scene, 0, menuRadius, item.sprite, item.frame);
|
const image = new Sprite(this.scene, 0, menuRadius, item.image);
|
||||||
this.add(image);
|
this.add(image);
|
||||||
this.scene.sys.updateList.add(image);
|
this.scene.sys.updateList.add(image);
|
||||||
image.setDepth(DEPTH_UI_INDEX)
|
const scalingFactor = waScaleManager.uiScalingFactor * 0.075;
|
||||||
|
image.setScale(scalingFactor)
|
||||||
image.setInteractive({
|
image.setInteractive({
|
||||||
hitArea: new Phaser.Geom.Circle(0, 0, 25),
|
|
||||||
hitAreaCallback: Phaser.Geom.Circle.Contains, //eslint-disable-line @typescript-eslint/unbound-method
|
|
||||||
useHandCursor: true,
|
useHandCursor: true,
|
||||||
});
|
});
|
||||||
image.on('pointerdown', () => this.emit(RadialMenuClickEvent, item));
|
image.on('pointerdown', () => this.emit(RadialMenuClickEvent, item));
|
||||||
image.on('pointerover', () => {
|
image.on('pointerover', () => {
|
||||||
this.scene.tweens.add({
|
this.scene.tweens.add({
|
||||||
targets: image,
|
targets: image,
|
||||||
scale: 2,
|
props: {
|
||||||
|
scale: 2 * scalingFactor,
|
||||||
|
},
|
||||||
duration: 500,
|
duration: 500,
|
||||||
ease: 'Power3',
|
ease: 'Power3',
|
||||||
})
|
})
|
||||||
@ -52,7 +52,9 @@ export class RadialMenu extends Phaser.GameObjects.Container {
|
|||||||
image.on('pointerout', () => {
|
image.on('pointerout', () => {
|
||||||
this.scene.tweens.add({
|
this.scene.tweens.add({
|
||||||
targets: image,
|
targets: image,
|
||||||
scale: 1,
|
props: {
|
||||||
|
scale: scalingFactor,
|
||||||
|
},
|
||||||
duration: 500,
|
duration: 500,
|
||||||
ease: 'Power3',
|
ease: 'Power3',
|
||||||
})
|
})
|
||||||
|
@ -5,7 +5,6 @@ import Container = Phaser.GameObjects.Container;
|
|||||||
import Sprite = Phaser.GameObjects.Sprite;
|
import Sprite = Phaser.GameObjects.Sprite;
|
||||||
import {TextureError} from "../../Exception/TextureError";
|
import {TextureError} from "../../Exception/TextureError";
|
||||||
import {Companion} from "../Companion/Companion";
|
import {Companion} from "../Companion/Companion";
|
||||||
import {getEmoteAnimName} from "../Game/EmoteManager";
|
|
||||||
import type {GameScene} from "../Game/GameScene";
|
import type {GameScene} from "../Game/GameScene";
|
||||||
import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes";
|
import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes";
|
||||||
import {waScaleManager} from "../Services/WaScaleManager";
|
import {waScaleManager} from "../Services/WaScaleManager";
|
||||||
@ -32,6 +31,7 @@ export abstract class Character extends Container {
|
|||||||
private invisible: boolean;
|
private invisible: boolean;
|
||||||
public companion?: Companion;
|
public companion?: Companion;
|
||||||
private emote: Phaser.GameObjects.Sprite | null = null;
|
private emote: Phaser.GameObjects.Sprite | null = null;
|
||||||
|
private emoteTween: Phaser.Tweens.Tween|null = null;
|
||||||
|
|
||||||
constructor(scene: GameScene,
|
constructor(scene: GameScene,
|
||||||
x: number,
|
x: number,
|
||||||
@ -246,24 +246,76 @@ export abstract class Character extends Container {
|
|||||||
|
|
||||||
playEmote(emoteKey: string) {
|
playEmote(emoteKey: string) {
|
||||||
this.cancelPreviousEmote();
|
this.cancelPreviousEmote();
|
||||||
|
|
||||||
|
const scalingFactor = waScaleManager.uiScalingFactor * 0.05;
|
||||||
|
const emoteY = -30 - scalingFactor * 10;
|
||||||
|
|
||||||
this.playerName.setVisible(false);
|
this.playerName.setVisible(false);
|
||||||
this.emote = new Sprite(this.scene, 0, -30 - waScaleManager.uiScalingFactor * 10, emoteKey, 1);
|
this.emote = new Sprite(this.scene, 0, 0, emoteKey);
|
||||||
this.emote.setDepth(DEPTH_INGAME_TEXT_INDEX);
|
this.emote.setAlpha(0);
|
||||||
this.emote.setScale(waScaleManager.uiScalingFactor)
|
this.emote.setScale(0.1 * scalingFactor);
|
||||||
this.add(this.emote);
|
this.add(this.emote);
|
||||||
this.scene.sys.updateList.add(this.emote);
|
this.scene.sys.updateList.add(this.emote);
|
||||||
this.emote.play(getEmoteAnimName(emoteKey));
|
|
||||||
this.emote.on(Phaser.Animations.Events.ANIMATION_COMPLETE, () => {
|
this.createStartTransition(scalingFactor, emoteY);
|
||||||
this.emote?.destroy();
|
}
|
||||||
this.emote = null;
|
|
||||||
this.playerName.setVisible(true);
|
private createStartTransition(scalingFactor: number, emoteY: number) {
|
||||||
|
this.emoteTween = this.scene.tweens.add({
|
||||||
|
targets: this.emote,
|
||||||
|
props: {
|
||||||
|
scale: scalingFactor,
|
||||||
|
alpha: 1,
|
||||||
|
y: emoteY,
|
||||||
|
},
|
||||||
|
ease: 'Power2',
|
||||||
|
duration: 500,
|
||||||
|
onComplete: () => {
|
||||||
|
this.startPulseTransition(emoteY, scalingFactor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private startPulseTransition(emoteY: number, scalingFactor: number) {
|
||||||
|
this.emoteTween = this.scene.tweens.add({
|
||||||
|
targets: this.emote,
|
||||||
|
props: {
|
||||||
|
y: emoteY * 1.3,
|
||||||
|
scale: scalingFactor * 1.1
|
||||||
|
},
|
||||||
|
duration: 250,
|
||||||
|
yoyo: true,
|
||||||
|
repeat: 1,
|
||||||
|
completeDelay: 200,
|
||||||
|
onComplete: () => {
|
||||||
|
this.startExitTransition(emoteY);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private startExitTransition(emoteY: number) {
|
||||||
|
this.emoteTween = this.scene.tweens.add({
|
||||||
|
targets: this.emote,
|
||||||
|
props: {
|
||||||
|
alpha: 0,
|
||||||
|
y: 2 * emoteY,
|
||||||
|
},
|
||||||
|
ease: 'Power2',
|
||||||
|
duration: 500,
|
||||||
|
onComplete: () => {
|
||||||
|
this.destroyEmote();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelPreviousEmote() {
|
cancelPreviousEmote() {
|
||||||
if (!this.emote) return;
|
if (!this.emote) return;
|
||||||
|
|
||||||
|
this.emoteTween?.remove();
|
||||||
|
this.destroyEmote()
|
||||||
|
}
|
||||||
|
|
||||||
|
private destroyEmote() {
|
||||||
this.emote?.destroy();
|
this.emote?.destroy();
|
||||||
this.emote = null;
|
this.emote = null;
|
||||||
this.playerName.setVisible(true);
|
this.playerName.setVisible(true);
|
||||||
|
@ -1,38 +1,30 @@
|
|||||||
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||||
import {createLoadingPromise} from "../Entity/PlayerTexturesLoadingManager";
|
|
||||||
import {emoteEventStream} from "../../Connexion/EmoteEventStream";
|
import {emoteEventStream} from "../../Connexion/EmoteEventStream";
|
||||||
import type {GameScene} from "./GameScene";
|
import type {GameScene} from "./GameScene";
|
||||||
import type {RadialMenuItem} from "../Components/RadialMenu";
|
import type {RadialMenuItem} from "../Components/RadialMenu";
|
||||||
|
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
||||||
|
import type {Subscription} from "rxjs";
|
||||||
|
|
||||||
enum RegisteredEmoteTypes {
|
|
||||||
short = 1,
|
|
||||||
long = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RegisteredEmote extends BodyResourceDescriptionInterface {
|
interface RegisteredEmote extends BodyResourceDescriptionInterface {
|
||||||
name: string;
|
name: string;
|
||||||
img: string;
|
img: string;
|
||||||
type: RegisteredEmoteTypes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//the last 3 emotes are courtesy of @tabascoeye
|
|
||||||
export const emotes: {[key: string]: RegisteredEmote} = {
|
export const emotes: {[key: string]: RegisteredEmote} = {
|
||||||
'emote-exclamation': {name: 'emote-exclamation', img: 'resources/emotes/pipo-popupemotes001.png', type: RegisteredEmoteTypes.short, },
|
'emote-heart': {name: 'emote-heart', img: 'resources/emotes/heart-emote.png'},
|
||||||
'emote-interrogation': {name: 'emote-interrogation', img: 'resources/emotes/pipo-popupemotes002.png', type: RegisteredEmoteTypes.short},
|
'emote-clap': {name: 'emote-clap', img: 'resources/emotes/clap-emote.png'},
|
||||||
'emote-sleep': {name: 'emote-sleep', img: 'resources/emotes/pipo-popupemotes021.png', type: RegisteredEmoteTypes.short},
|
'emote-hand': {name: 'emote-hand', img: 'resources/emotes/hand-emote.png'},
|
||||||
'emote-clap': {name: 'emote-clap', img: 'resources/emotes/taba-clap-emote.png', type: RegisteredEmoteTypes.short},
|
'emote-thanks': {name: 'emote-thanks', img: 'resources/emotes/thanks-emote.png'},
|
||||||
'emote-thumbsdown': {name: 'emote-thumbsdown', img: 'resources/emotes/taba-thumbsdown-emote.png', type: RegisteredEmoteTypes.long},
|
'emote-thumb-up': {name: 'emote-thumb-up', img: 'resources/emotes/thumb-up-emote.png'},
|
||||||
'emote-thumbsup': {name: 'emote-thumbsup', img: 'resources/emotes/taba-thumbsup-emote.png', type: RegisteredEmoteTypes.long},
|
'emote-thumb-down': {name: 'emote-thumb-down', img: 'resources/emotes/thumb-down-emote.png'},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getEmoteAnimName = (emoteKey: string): string => {
|
|
||||||
return 'anim-'+emoteKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class EmoteManager {
|
export class EmoteManager {
|
||||||
|
private subscription: Subscription;
|
||||||
|
|
||||||
constructor(private scene: GameScene) {
|
constructor(private scene: GameScene) {
|
||||||
emoteEventStream.stream.subscribe((event) => {
|
this.subscription = emoteEventStream.stream.subscribe((event) => {
|
||||||
const actor = this.scene.MapPlayersByKey.get(event.userId);
|
const actor = this.scene.MapPlayersByKey.get(event.userId);
|
||||||
if (actor) {
|
if (actor) {
|
||||||
this.lazyLoadEmoteTexture(event.emoteName).then(emoteKey => {
|
this.lazyLoadEmoteTexture(event.emoteName).then(emoteKey => {
|
||||||
@ -41,45 +33,41 @@ export class EmoteManager {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
createLoadingPromise(loadPlugin: LoaderPlugin, playerResourceDescriptor: BodyResourceDescriptionInterface) {
|
||||||
|
return new Promise<string>((res) => {
|
||||||
|
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
||||||
|
return res(playerResourceDescriptor.name);
|
||||||
|
}
|
||||||
|
loadPlugin.image(playerResourceDescriptor.name, playerResourceDescriptor.img);
|
||||||
|
loadPlugin.once('filecomplete-image-' + playerResourceDescriptor.name, () => res(playerResourceDescriptor.name));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
lazyLoadEmoteTexture(textureKey: string): Promise<string> {
|
lazyLoadEmoteTexture(textureKey: string): Promise<string> {
|
||||||
const emoteDescriptor = emotes[textureKey];
|
const emoteDescriptor = emotes[textureKey];
|
||||||
if (emoteDescriptor === undefined) {
|
if (emoteDescriptor === undefined) {
|
||||||
throw 'Emote not found!';
|
throw 'Emote not found!';
|
||||||
}
|
}
|
||||||
const loadPromise = createLoadingPromise(this.scene.load, emoteDescriptor, {
|
const loadPromise = this.createLoadingPromise(this.scene.load, emoteDescriptor);
|
||||||
frameWidth: 32,
|
|
||||||
frameHeight: 32,
|
|
||||||
});
|
|
||||||
this.scene.load.start();
|
this.scene.load.start();
|
||||||
return loadPromise.then(() => {
|
return loadPromise
|
||||||
if (this.scene.anims.exists(getEmoteAnimName(textureKey))) {
|
|
||||||
return Promise.resolve(textureKey);
|
|
||||||
}
|
|
||||||
const frameConfig = emoteDescriptor.type === RegisteredEmoteTypes.short ? {frames: [0,1,2,2]} : {frames : [0,1,2,3,4,]};
|
|
||||||
this.scene.anims.create({
|
|
||||||
key: getEmoteAnimName(textureKey),
|
|
||||||
frames: this.scene.anims.generateFrameNumbers(textureKey, frameConfig),
|
|
||||||
frameRate: 5,
|
|
||||||
repeat: 2,
|
|
||||||
});
|
|
||||||
return textureKey;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getMenuImages(): Promise<RadialMenuItem[]> {
|
getMenuImages(): Promise<RadialMenuItem[]> {
|
||||||
const promises = [];
|
const promises = [];
|
||||||
for (const key in emotes) {
|
for (const key in emotes) {
|
||||||
const promise = this.lazyLoadEmoteTexture(key).then((textureKey) => {
|
const promise = this.lazyLoadEmoteTexture(key).then((textureKey) => {
|
||||||
const emoteDescriptor = emotes[textureKey];
|
|
||||||
return {
|
return {
|
||||||
sprite: textureKey,
|
image: textureKey,
|
||||||
name: textureKey,
|
name: textureKey,
|
||||||
frame: emoteDescriptor.type === RegisteredEmoteTypes.short ? 1 : 4,
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
promises.push(promise);
|
promises.push(promise);
|
||||||
}
|
}
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.subscription.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
@ -940,6 +940,7 @@ ${escapedMessage}
|
|||||||
this.messageSubscription?.unsubscribe();
|
this.messageSubscription?.unsubscribe();
|
||||||
this.userInputManager.destroy();
|
this.userInputManager.destroy();
|
||||||
this.pinchManager?.destroy();
|
this.pinchManager?.destroy();
|
||||||
|
this.emoteManager.destroy();
|
||||||
|
|
||||||
for(const iframeEvents of this.iframeSubscriptionList){
|
for(const iframeEvents of this.iframeSubscriptionList){
|
||||||
iframeEvents.unsubscribe();
|
iframeEvents.unsubscribe();
|
||||||
|
@ -54,7 +54,7 @@ class WaScaleManager {
|
|||||||
* This is used to scale back the ui components to counter-act the zoom.
|
* This is used to scale back the ui components to counter-act the zoom.
|
||||||
*/
|
*/
|
||||||
public get uiScalingFactor(): number {
|
public get uiScalingFactor(): number {
|
||||||
return this.actualZoom > 1 ? 1 : 2;
|
return this.actualZoom > 1 ? 1 : 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -183,7 +183,7 @@ export class IoSocketController {
|
|||||||
// If we get an HTTP 404, the token is invalid. Let's perform an anonymous login!
|
// If we get an HTTP 404, the token is invalid. Let's perform an anonymous login!
|
||||||
console.warn('Cannot find user with uuid "'+userUuid+'". Performing an anonymous login instead.');
|
console.warn('Cannot find user with uuid "'+userUuid+'". Performing an anonymous login instead.');
|
||||||
} else if(err?.response?.status == 403) {
|
} else if(err?.response?.status == 403) {
|
||||||
// If we get an HTTP 404, the world is full. We need to broadcast a special error to the client.
|
// If we get an HTTP 403, the world is full. We need to broadcast a special error to the client.
|
||||||
// we finish immediately the upgrade then we will close the socket as soon as it starts opening.
|
// we finish immediately the upgrade then we will close the socket as soon as it starts opening.
|
||||||
return res.upgrade({
|
return res.upgrade({
|
||||||
rejected: true,
|
rejected: true,
|
||||||
|