Merge branch 'develop' of github.com:thecodingmachine/workadventure into metadataScriptingApi
This commit is contained in:
commit
17261dfab6
@ -8,6 +8,10 @@
|
|||||||
|
|
||||||
### Updates
|
### Updates
|
||||||
|
|
||||||
|
- Added the emote feature to Workadventure. (@Kharhamel, @Tabascoeye)
|
||||||
|
- The emote menu can be opened by clicking on your character.
|
||||||
|
- Clicking on one of its element will close the menu and play an emote above your character.
|
||||||
|
- This emote can be seen by other players.
|
||||||
- Mobile support has been improved
|
- Mobile support has been improved
|
||||||
- WorkAdventure automatically sets the zoom level based on the viewport size to ensure a sensible size of the map is visible, whatever the viewport used
|
- WorkAdventure automatically sets the zoom level based on the viewport size to ensure a sensible size of the map is visible, whatever the viewport used
|
||||||
- Mouse wheel support to zoom in / out
|
- Mouse wheel support to zoom in / out
|
||||||
|
@ -2,12 +2,12 @@ import {PointInterface} from "./Websocket/PointInterface";
|
|||||||
import {Group} from "./Group";
|
import {Group} from "./Group";
|
||||||
import {User, UserSocket} from "./User";
|
import {User, UserSocket} from "./User";
|
||||||
import {PositionInterface} from "_Model/PositionInterface";
|
import {PositionInterface} from "_Model/PositionInterface";
|
||||||
import {EntersCallback, LeavesCallback, MovesCallback} from "_Model/Zone";
|
import {EmoteCallback, EntersCallback, LeavesCallback, MovesCallback} from "_Model/Zone";
|
||||||
import {PositionNotifier} from "./PositionNotifier";
|
import {PositionNotifier} from "./PositionNotifier";
|
||||||
import {Movable} from "_Model/Movable";
|
import {Movable} from "_Model/Movable";
|
||||||
import {extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous} from "./RoomIdentifier";
|
import {extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous} from "./RoomIdentifier";
|
||||||
import {arrayIntersect} from "../Services/ArrayHelper";
|
import {arrayIntersect} from "../Services/ArrayHelper";
|
||||||
import {JoinRoomMessage} from "../Messages/generated/messages_pb";
|
import {EmoteEventMessage, JoinRoomMessage} from "../Messages/generated/messages_pb";
|
||||||
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
||||||
import {ZoneSocket} from "src/RoomManager";
|
import {ZoneSocket} from "src/RoomManager";
|
||||||
import {Admin} from "../Model/Admin";
|
import {Admin} from "../Model/Admin";
|
||||||
@ -51,8 +51,9 @@ export class GameRoom {
|
|||||||
groupRadius: number,
|
groupRadius: number,
|
||||||
onEnters: EntersCallback,
|
onEnters: EntersCallback,
|
||||||
onMoves: MovesCallback,
|
onMoves: MovesCallback,
|
||||||
onLeaves: LeavesCallback)
|
onLeaves: LeavesCallback,
|
||||||
{
|
onEmote: EmoteCallback,
|
||||||
|
) {
|
||||||
this.roomId = roomId;
|
this.roomId = roomId;
|
||||||
|
|
||||||
if (isRoomAnonymous(roomId)) {
|
if (isRoomAnonymous(roomId)) {
|
||||||
@ -74,7 +75,7 @@ export class GameRoom {
|
|||||||
this.minDistance = minDistance;
|
this.minDistance = minDistance;
|
||||||
this.groupRadius = groupRadius;
|
this.groupRadius = groupRadius;
|
||||||
// A zone is 10 sprites wide.
|
// A zone is 10 sprites wide.
|
||||||
this.positionNotifier = new PositionNotifier(320, 320, onEnters, onMoves, onLeaves);
|
this.positionNotifier = new PositionNotifier(320, 320, onEnters, onMoves, onLeaves, onEmote);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getGroups(): Group[] {
|
public getGroups(): Group[] {
|
||||||
@ -325,4 +326,8 @@ export class GameRoom {
|
|||||||
this.versionNumber++
|
this.versionNumber++
|
||||||
return this.versionNumber;
|
return this.versionNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public emitEmoteEvent(user: User, emoteEventMessage: EmoteEventMessage) {
|
||||||
|
this.positionNotifier.emitEmoteEvent(user, emoteEventMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,10 +8,12 @@
|
|||||||
* The PositionNotifier is important for performance. It allows us to send the position of players only to a restricted
|
* The PositionNotifier is important for performance. It allows us to send the position of players only to a restricted
|
||||||
* number of players around the current player.
|
* number of players around the current player.
|
||||||
*/
|
*/
|
||||||
import {EntersCallback, LeavesCallback, MovesCallback, Zone} from "./Zone";
|
import {EmoteCallback, EntersCallback, LeavesCallback, MovesCallback, Zone} from "./Zone";
|
||||||
import {Movable} from "_Model/Movable";
|
import {Movable} from "_Model/Movable";
|
||||||
import {PositionInterface} from "_Model/PositionInterface";
|
import {PositionInterface} from "_Model/PositionInterface";
|
||||||
import {ZoneSocket} from "../RoomManager";
|
import {ZoneSocket} from "../RoomManager";
|
||||||
|
import {User} from "_Model/User";
|
||||||
|
import {EmoteEventMessage} from "../Messages/generated/messages_pb";
|
||||||
|
|
||||||
interface ZoneDescriptor {
|
interface ZoneDescriptor {
|
||||||
i: number;
|
i: number;
|
||||||
@ -24,7 +26,7 @@ export class PositionNotifier {
|
|||||||
|
|
||||||
private zones: Zone[][] = [];
|
private zones: Zone[][] = [];
|
||||||
|
|
||||||
constructor(private zoneWidth: number, private zoneHeight: number, private onUserEnters: EntersCallback, private onUserMoves: MovesCallback, private onUserLeaves: LeavesCallback) {
|
constructor(private zoneWidth: number, private zoneHeight: number, private onUserEnters: EntersCallback, private onUserMoves: MovesCallback, private onUserLeaves: LeavesCallback, private onEmote: EmoteCallback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private getZoneDescriptorFromCoordinates(x: number, y: number): ZoneDescriptor {
|
private getZoneDescriptorFromCoordinates(x: number, y: number): ZoneDescriptor {
|
||||||
@ -77,7 +79,7 @@ export class PositionNotifier {
|
|||||||
|
|
||||||
let zone = this.zones[j][i];
|
let zone = this.zones[j][i];
|
||||||
if (zone === undefined) {
|
if (zone === undefined) {
|
||||||
zone = new Zone(this.onUserEnters, this.onUserMoves, this.onUserLeaves, i, j);
|
zone = new Zone(this.onUserEnters, this.onUserMoves, this.onUserLeaves, this.onEmote, i, j);
|
||||||
this.zones[j][i] = zone;
|
this.zones[j][i] = zone;
|
||||||
}
|
}
|
||||||
return zone;
|
return zone;
|
||||||
@ -93,4 +95,11 @@ export class PositionNotifier {
|
|||||||
const zone = this.getZone(x, y);
|
const zone = this.getZone(x, y);
|
||||||
zone.removeListener(call);
|
zone.removeListener(call);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public emitEmoteEvent(user: User, emoteEventMessage: EmoteEventMessage) {
|
||||||
|
const zoneDesc = this.getZoneDescriptorFromCoordinates(user.getPosition().x, user.getPosition().y);
|
||||||
|
const zone = this.getZone(zoneDesc.i, zoneDesc.j);
|
||||||
|
zone.emitEmoteEvent(emoteEventMessage);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,21 +3,19 @@ import {PositionInterface} from "_Model/PositionInterface";
|
|||||||
import {Movable} from "./Movable";
|
import {Movable} from "./Movable";
|
||||||
import {Group} from "./Group";
|
import {Group} from "./Group";
|
||||||
import {ZoneSocket} from "../RoomManager";
|
import {ZoneSocket} from "../RoomManager";
|
||||||
|
import {EmoteEventMessage} from "../Messages/generated/messages_pb";
|
||||||
|
|
||||||
export type EntersCallback = (thing: Movable, fromZone: Zone|null, listener: ZoneSocket) => void;
|
export type EntersCallback = (thing: Movable, fromZone: Zone|null, listener: ZoneSocket) => void;
|
||||||
export type MovesCallback = (thing: Movable, position: PositionInterface, listener: ZoneSocket) => void;
|
export type MovesCallback = (thing: Movable, position: PositionInterface, listener: ZoneSocket) => void;
|
||||||
export type LeavesCallback = (thing: Movable, newZone: Zone|null, listener: ZoneSocket) => void;
|
export type LeavesCallback = (thing: Movable, newZone: Zone|null, listener: ZoneSocket) => void;
|
||||||
|
export type EmoteCallback = (emoteEventMessage: EmoteEventMessage, listener: ZoneSocket) => void;
|
||||||
|
|
||||||
export class Zone {
|
export class Zone {
|
||||||
private things: Set<Movable> = new Set<Movable>();
|
private things: Set<Movable> = new Set<Movable>();
|
||||||
private listeners: Set<ZoneSocket> = new Set<ZoneSocket>();
|
private listeners: Set<ZoneSocket> = new Set<ZoneSocket>();
|
||||||
|
|
||||||
/**
|
|
||||||
* @param x For debugging purpose only
|
constructor(private onEnters: EntersCallback, private onMoves: MovesCallback, private onLeaves: LeavesCallback, private onEmote: EmoteCallback, public readonly x: number, public readonly y: number) { }
|
||||||
* @param y For debugging purpose only
|
|
||||||
*/
|
|
||||||
constructor(private onEnters: EntersCallback, private onMoves: MovesCallback, private onLeaves: LeavesCallback, public readonly x: number, public readonly y: number) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A user/thing leaves the zone
|
* A user/thing leaves the zone
|
||||||
@ -41,9 +39,7 @@ export class Zone {
|
|||||||
*/
|
*/
|
||||||
private notifyLeft(thing: Movable, newZone: Zone|null) {
|
private notifyLeft(thing: Movable, newZone: Zone|null) {
|
||||||
for (const listener of this.listeners) {
|
for (const listener of this.listeners) {
|
||||||
//if (listener !== thing && (newZone === null || !listener.listenedZones.has(newZone))) {
|
|
||||||
this.onLeaves(thing, newZone, listener);
|
this.onLeaves(thing, newZone, listener);
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,15 +53,6 @@ export class Zone {
|
|||||||
*/
|
*/
|
||||||
private notifyEnter(thing: Movable, oldZone: Zone|null, position: PositionInterface) {
|
private notifyEnter(thing: Movable, oldZone: Zone|null, position: PositionInterface) {
|
||||||
for (const listener of this.listeners) {
|
for (const listener of this.listeners) {
|
||||||
|
|
||||||
/*if (listener === thing) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (oldZone === null || !listener.listenedZones.has(oldZone)) {
|
|
||||||
this.onEnters(thing, listener);
|
|
||||||
} else {
|
|
||||||
this.onMoves(thing, position, listener);
|
|
||||||
}*/
|
|
||||||
this.onEnters(thing, oldZone, listener);
|
this.onEnters(thing, oldZone, listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -85,28 +72,6 @@ export class Zone {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*public startListening(listener: User): void {
|
|
||||||
for (const thing of this.things) {
|
|
||||||
if (thing !== listener) {
|
|
||||||
this.onEnters(thing, listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.listeners.add(listener);
|
|
||||||
listener.listenedZones.add(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public stopListening(listener: User): void {
|
|
||||||
for (const thing of this.things) {
|
|
||||||
if (thing !== listener) {
|
|
||||||
this.onLeaves(thing, listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.listeners.delete(listener);
|
|
||||||
listener.listenedZones.delete(this);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
public getThings(): Set<Movable> {
|
public getThings(): Set<Movable> {
|
||||||
return this.things;
|
return this.things;
|
||||||
}
|
}
|
||||||
@ -119,4 +84,11 @@ export class Zone {
|
|||||||
public removeListener(socket: ZoneSocket): void {
|
public removeListener(socket: ZoneSocket): void {
|
||||||
this.listeners.delete(socket);
|
this.listeners.delete(socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public emitEmoteEvent(emoteEventMessage: EmoteEventMessage) {
|
||||||
|
for (const listener of this.listeners) {
|
||||||
|
this.onEmote(emoteEventMessage, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
AdminPusherToBackMessage,
|
AdminPusherToBackMessage,
|
||||||
AdminRoomMessage,
|
AdminRoomMessage,
|
||||||
BanMessage,
|
BanMessage,
|
||||||
|
EmotePromptMessage,
|
||||||
EmptyMessage,
|
EmptyMessage,
|
||||||
ItemEventMessage,
|
ItemEventMessage,
|
||||||
JoinRoomMessage,
|
JoinRoomMessage,
|
||||||
@ -71,6 +72,8 @@ const roomManager: IRoomManagerServer = {
|
|||||||
socketManager.emitPlayGlobalMessage(room, message.getPlayglobalmessage() as PlayGlobalMessage);
|
socketManager.emitPlayGlobalMessage(room, message.getPlayglobalmessage() as PlayGlobalMessage);
|
||||||
} else if (message.hasQueryjitsijwtmessage()){
|
} else if (message.hasQueryjitsijwtmessage()){
|
||||||
socketManager.handleQueryJitsiJwtMessage(user, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
|
socketManager.handleQueryJitsiJwtMessage(user, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
|
||||||
|
} else if (message.hasEmotepromptmessage()){
|
||||||
|
socketManager.handleEmoteEventMessage(room, user, message.getEmotepromptmessage() as EmotePromptMessage);
|
||||||
}else if (message.hasSendusermessage()) {
|
}else if (message.hasSendusermessage()) {
|
||||||
const sendUserMessage = message.getSendusermessage();
|
const sendUserMessage = message.getSendusermessage();
|
||||||
if(sendUserMessage !== undefined) {
|
if(sendUserMessage !== undefined) {
|
||||||
|
@ -26,7 +26,8 @@ import {
|
|||||||
GroupLeftZoneMessage,
|
GroupLeftZoneMessage,
|
||||||
WorldFullWarningMessage,
|
WorldFullWarningMessage,
|
||||||
UserLeftZoneMessage,
|
UserLeftZoneMessage,
|
||||||
BanUserMessage, RefreshRoomMessage,
|
EmoteEventMessage,
|
||||||
|
BanUserMessage, RefreshRoomMessage, EmotePromptMessage,
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import {User, UserSocket} from "../Model/User";
|
import {User, UserSocket} from "../Model/User";
|
||||||
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
||||||
@ -67,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);
|
||||||
});
|
});
|
||||||
@ -263,7 +265,8 @@ export class SocketManager {
|
|||||||
GROUP_RADIUS,
|
GROUP_RADIUS,
|
||||||
(thing: Movable, fromZone: Zone|null, listener: ZoneSocket) => this.onZoneEnter(thing, fromZone, listener),
|
(thing: Movable, fromZone: Zone|null, listener: ZoneSocket) => this.onZoneEnter(thing, fromZone, listener),
|
||||||
(thing: Movable, position:PositionInterface, listener: ZoneSocket) => this.onClientMove(thing, position, listener),
|
(thing: Movable, position:PositionInterface, listener: ZoneSocket) => this.onClientMove(thing, position, listener),
|
||||||
(thing: Movable, newZone: Zone|null, listener: ZoneSocket) => this.onClientLeave(thing, newZone, listener)
|
(thing: Movable, newZone: Zone|null, listener: ZoneSocket) => this.onClientLeave(thing, newZone, listener),
|
||||||
|
(emoteEventMessage:EmoteEventMessage, listener: ZoneSocket) => this.onEmote(emoteEventMessage, listener),
|
||||||
);
|
);
|
||||||
gaugeManager.incNbRoomGauge();
|
gaugeManager.incNbRoomGauge();
|
||||||
this.rooms.set(roomId, world);
|
this.rooms.set(roomId, world);
|
||||||
@ -339,6 +342,14 @@ export class SocketManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private onEmote(emoteEventMessage: EmoteEventMessage, client: ZoneSocket) {
|
||||||
|
const subMessage = new SubToPusherMessage();
|
||||||
|
subMessage.setEmoteeventmessage(emoteEventMessage);
|
||||||
|
|
||||||
|
emitZoneMessage(subMessage, client);
|
||||||
|
}
|
||||||
|
|
||||||
private emitCreateUpdateGroupEvent(client: ZoneSocket, fromZone: Zone|null, group: Group): void {
|
private emitCreateUpdateGroupEvent(client: ZoneSocket, fromZone: Zone|null, group: Group): void {
|
||||||
const position = group.getPosition();
|
const position = group.getPosition();
|
||||||
const pointMessage = new PointMessage();
|
const pointMessage = new PointMessage();
|
||||||
@ -751,6 +762,13 @@ export class SocketManager {
|
|||||||
recipient.socket.write(clientMessage);
|
recipient.socket.write(clientMessage);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleEmoteEventMessage(room: GameRoom, user: User, emotePromptMessage: EmotePromptMessage) {
|
||||||
|
const emoteEventMessage = new EmoteEventMessage();
|
||||||
|
emoteEventMessage.setEmote(emotePromptMessage.getEmote());
|
||||||
|
emoteEventMessage.setActoruserid(user.id);
|
||||||
|
room.emitEmoteEvent(user, emoteEventMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const socketManager = new SocketManager();
|
export const socketManager = new SocketManager();
|
||||||
|
@ -5,6 +5,7 @@ import {Group} from "../src/Model/Group";
|
|||||||
import {User, UserSocket} from "_Model/User";
|
import {User, UserSocket} from "_Model/User";
|
||||||
import {JoinRoomMessage, PositionMessage} from "../src/Messages/generated/messages_pb";
|
import {JoinRoomMessage, PositionMessage} from "../src/Messages/generated/messages_pb";
|
||||||
import Direction = PositionMessage.Direction;
|
import Direction = PositionMessage.Direction;
|
||||||
|
import {EmoteCallback} from "_Model/Zone";
|
||||||
|
|
||||||
function createMockUser(userId: number): User {
|
function createMockUser(userId: number): User {
|
||||||
return {
|
return {
|
||||||
@ -33,6 +34,8 @@ function createJoinRoomMessage(uuid: string, x: number, y: number): JoinRoomMess
|
|||||||
return joinRoomMessage;
|
return joinRoomMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const emote: EmoteCallback = (emoteEventMessage, listener): void => {}
|
||||||
|
|
||||||
describe("GameRoom", () => {
|
describe("GameRoom", () => {
|
||||||
it("should connect user1 and user2", () => {
|
it("should connect user1 and user2", () => {
|
||||||
let connectCalledNumber: number = 0;
|
let connectCalledNumber: number = 0;
|
||||||
@ -43,7 +46,8 @@ describe("GameRoom", () => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const world = new GameRoom('_/global/test.json', connect, disconnect, 160, 160, () => {}, () => {}, () => {});
|
|
||||||
|
const world = new GameRoom('_/global/test.json', connect, disconnect, 160, 160, () => {}, () => {}, () => {}, emote);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -72,7 +76,7 @@ describe("GameRoom", () => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const world = new GameRoom('_/global/test.json', connect, disconnect, 160, 160, () => {}, () => {}, () => {});
|
const world = new GameRoom('_/global/test.json', connect, disconnect, 160, 160, () => {}, () => {}, () => {}, emote);
|
||||||
|
|
||||||
const user1 = world.join(createMockUserSocket(), createJoinRoomMessage('1', 100, 100));
|
const user1 = world.join(createMockUserSocket(), createJoinRoomMessage('1', 100, 100));
|
||||||
|
|
||||||
@ -101,7 +105,7 @@ describe("GameRoom", () => {
|
|||||||
disconnectCallNumber++;
|
disconnectCallNumber++;
|
||||||
}
|
}
|
||||||
|
|
||||||
const world = new GameRoom('_/global/test.json', connect, disconnect, 160, 160, () => {}, () => {}, () => {});
|
const world = new GameRoom('_/global/test.json', connect, disconnect, 160, 160, () => {}, () => {}, () => {}, emote);
|
||||||
|
|
||||||
const user1 = world.join(createMockUserSocket(), createJoinRoomMessage('1', 100, 100));
|
const user1 = world.join(createMockUserSocket(), createJoinRoomMessage('1', 100, 100));
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ describe("PositionNotifier", () => {
|
|||||||
moveTriggered = true;
|
moveTriggered = true;
|
||||||
}, (thing: Movable) => {
|
}, (thing: Movable) => {
|
||||||
leaveTriggered = true;
|
leaveTriggered = true;
|
||||||
});
|
}, () => {});
|
||||||
|
|
||||||
const user1 = new User(1, 'test', '10.0.0.2', {
|
const user1 = new User(1, 'test', '10.0.0.2', {
|
||||||
x: 500,
|
x: 500,
|
||||||
@ -98,7 +98,7 @@ describe("PositionNotifier", () => {
|
|||||||
moveTriggered = true;
|
moveTriggered = true;
|
||||||
}, (thing: Movable) => {
|
}, (thing: Movable) => {
|
||||||
leaveTriggered = true;
|
leaveTriggered = true;
|
||||||
});
|
}, () => {});
|
||||||
|
|
||||||
const user1 = new User(1, 'test', '10.0.0.2', {
|
const user1 = new User(1, 'test', '10.0.0.2', {
|
||||||
x: 500,
|
x: 500,
|
||||||
|
BIN
front/dist/resources/emotes/clap-emote.png
vendored
Normal file
BIN
front/dist/resources/emotes/clap-emote.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
front/dist/resources/emotes/hand-emote.png
vendored
Normal file
BIN
front/dist/resources/emotes/hand-emote.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
front/dist/resources/emotes/heart-emote.png
vendored
Normal file
BIN
front/dist/resources/emotes/heart-emote.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.9 KiB |
BIN
front/dist/resources/emotes/thanks-emote.png
vendored
Normal file
BIN
front/dist/resources/emotes/thanks-emote.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
front/dist/resources/emotes/thumb-down-emote.png
vendored
Normal file
BIN
front/dist/resources/emotes/thumb-down-emote.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.6 KiB |
BIN
front/dist/resources/emotes/thumb-up-emote.png
vendored
Normal file
BIN
front/dist/resources/emotes/thumb-up-emote.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.6 KiB |
19
front/src/Connexion/EmoteEventStream.ts
Normal file
19
front/src/Connexion/EmoteEventStream.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import {Subject} from "rxjs";
|
||||||
|
|
||||||
|
interface EmoteEvent {
|
||||||
|
userId: number,
|
||||||
|
emoteName: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
class EmoteEventStream {
|
||||||
|
|
||||||
|
private _stream:Subject<EmoteEvent> = new Subject();
|
||||||
|
public stream = this._stream.asObservable();
|
||||||
|
|
||||||
|
|
||||||
|
fire(userId: number, emoteName:string) {
|
||||||
|
this._stream.next({userId, emoteName});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const emoteEventStream = new EmoteEventStream();
|
@ -27,6 +27,8 @@ import {
|
|||||||
SendJitsiJwtMessage,
|
SendJitsiJwtMessage,
|
||||||
CharacterLayerMessage,
|
CharacterLayerMessage,
|
||||||
PingMessage,
|
PingMessage,
|
||||||
|
EmoteEventMessage,
|
||||||
|
EmotePromptMessage,
|
||||||
SendUserMessage,
|
SendUserMessage,
|
||||||
BanUserMessage
|
BanUserMessage
|
||||||
} from "../Messages/generated/messages_pb"
|
} from "../Messages/generated/messages_pb"
|
||||||
@ -47,6 +49,7 @@ import { adminMessagesService } from "./AdminMessagesService";
|
|||||||
import { worldFullMessageStream } from "./WorldFullMessageStream";
|
import { worldFullMessageStream } from "./WorldFullMessageStream";
|
||||||
import { worldFullWarningStream } from "./WorldFullWarningStream";
|
import { worldFullWarningStream } from "./WorldFullWarningStream";
|
||||||
import { connectionManager } from "./ConnectionManager";
|
import { connectionManager } from "./ConnectionManager";
|
||||||
|
import { emoteEventStream } from "./EmoteEventStream";
|
||||||
|
|
||||||
const manualPingDelay = 20000;
|
const manualPingDelay = 20000;
|
||||||
|
|
||||||
@ -123,7 +126,7 @@ export class RoomConnection implements RoomConnection {
|
|||||||
|
|
||||||
if (message.hasBatchmessage()) {
|
if (message.hasBatchmessage()) {
|
||||||
for (const subMessage of (message.getBatchmessage() as BatchMessage).getPayloadList()) {
|
for (const subMessage of (message.getBatchmessage() as BatchMessage).getPayloadList()) {
|
||||||
let event: string;
|
let event: string|null = null;
|
||||||
let payload;
|
let payload;
|
||||||
if (subMessage.hasUsermovedmessage()) {
|
if (subMessage.hasUsermovedmessage()) {
|
||||||
event = EventMessage.USER_MOVED;
|
event = EventMessage.USER_MOVED;
|
||||||
@ -143,12 +146,17 @@ export class RoomConnection implements RoomConnection {
|
|||||||
} else if (subMessage.hasItemeventmessage()) {
|
} else if (subMessage.hasItemeventmessage()) {
|
||||||
event = EventMessage.ITEM_EVENT;
|
event = EventMessage.ITEM_EVENT;
|
||||||
payload = subMessage.getItemeventmessage();
|
payload = subMessage.getItemeventmessage();
|
||||||
|
} else if (subMessage.hasEmoteeventmessage()) {
|
||||||
|
const emoteMessage = subMessage.getEmoteeventmessage() as EmoteEventMessage;
|
||||||
|
emoteEventStream.fire(emoteMessage.getActoruserid(), emoteMessage.getEmote());
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unexpected batch message type');
|
throw new Error('Unexpected batch message type');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event) {
|
||||||
this.dispatch(event, payload);
|
this.dispatch(event, payload);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else if (message.hasRoomjoinedmessage()) {
|
} else if (message.hasRoomjoinedmessage()) {
|
||||||
const roomJoinedMessage = message.getRoomjoinedmessage() as RoomJoinedMessage;
|
const roomJoinedMessage = message.getRoomjoinedmessage() as RoomJoinedMessage;
|
||||||
|
|
||||||
@ -599,6 +607,16 @@ export class RoomConnection implements RoomConnection {
|
|||||||
return this.hasTag('admin');
|
return this.hasTag('admin');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public emitEmoteEvent(emoteName: string): void {
|
||||||
|
const emoteMessage = new EmotePromptMessage();
|
||||||
|
emoteMessage.setEmote(emoteName)
|
||||||
|
|
||||||
|
const clientToServerMessage = new ClientToServerMessage();
|
||||||
|
clientToServerMessage.setEmotepromptmessage(emoteMessage);
|
||||||
|
|
||||||
|
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
||||||
|
}
|
||||||
|
|
||||||
public getAllTag() : string[] {
|
public getAllTag() : string[] {
|
||||||
return this.tags;
|
return this.tags;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { DEPTH_INGAME_TEXT_INDEX } from "../Game/DepthIndexes";
|
||||||
|
|
||||||
export class ChatModeIcon extends Phaser.GameObjects.Sprite {
|
export class ChatModeIcon extends Phaser.GameObjects.Sprite {
|
||||||
constructor(scene: Phaser.Scene, x: number, y: number) {
|
constructor(scene: Phaser.Scene, x: number, y: number) {
|
||||||
super(scene, x, y, 'layout_modes', 3);
|
super(scene, x, y, 'layout_modes', 3);
|
||||||
@ -6,6 +8,6 @@ export class ChatModeIcon extends Phaser.GameObjects.Sprite {
|
|||||||
this.setOrigin(0, 1);
|
this.setOrigin(0, 1);
|
||||||
this.setInteractive();
|
this.setInteractive();
|
||||||
this.setVisible(false);
|
this.setVisible(false);
|
||||||
this.setDepth(99999);
|
this.setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import VirtualJoystick from 'phaser3-rex-plugins/plugins/virtualjoystick.js';
|
import VirtualJoystick from 'phaser3-rex-plugins/plugins/virtualjoystick.js';
|
||||||
import {waScaleManager} from "../Services/WaScaleManager";
|
import {waScaleManager} from "../Services/WaScaleManager";
|
||||||
|
import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes";
|
||||||
|
|
||||||
//the assets were found here: https://hannemann.itch.io/virtual-joystick-pack-free
|
//the assets were found here: https://hannemann.itch.io/virtual-joystick-pack-free
|
||||||
export const joystickBaseKey = 'joystickBase';
|
export const joystickBaseKey = 'joystickBase';
|
||||||
@ -19,8 +20,8 @@ export class MobileJoystick extends VirtualJoystick {
|
|||||||
x: -1000,
|
x: -1000,
|
||||||
y: -1000,
|
y: -1000,
|
||||||
radius: radius * window.devicePixelRatio,
|
radius: radius * window.devicePixelRatio,
|
||||||
base: scene.add.image(0, 0, joystickBaseKey).setDisplaySize(baseSize * window.devicePixelRatio, baseSize * window.devicePixelRatio).setDepth(99999),
|
base: scene.add.image(0, 0, joystickBaseKey).setDisplaySize(baseSize * window.devicePixelRatio, baseSize * window.devicePixelRatio).setDepth(DEPTH_INGAME_TEXT_INDEX),
|
||||||
thumb: scene.add.image(0, 0, joystickThumbKey).setDisplaySize(thumbSize * window.devicePixelRatio, thumbSize * window.devicePixelRatio).setDepth(99999),
|
thumb: scene.add.image(0, 0, joystickThumbKey).setDisplaySize(thumbSize * window.devicePixelRatio, thumbSize * window.devicePixelRatio).setDepth(DEPTH_INGAME_TEXT_INDEX),
|
||||||
enable: true,
|
enable: true,
|
||||||
dir: "8dir",
|
dir: "8dir",
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import {discussionManager} from "../../WebRtc/DiscussionManager";
|
import {discussionManager} from "../../WebRtc/DiscussionManager";
|
||||||
|
import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes";
|
||||||
|
|
||||||
export const openChatIconName = 'openChatIcon';
|
export const openChatIconName = 'openChatIcon';
|
||||||
export class OpenChatIcon extends Phaser.GameObjects.Image {
|
export class OpenChatIcon extends Phaser.GameObjects.Image {
|
||||||
@ -9,7 +10,7 @@ export class OpenChatIcon extends Phaser.GameObjects.Image {
|
|||||||
this.setOrigin(0, 1);
|
this.setOrigin(0, 1);
|
||||||
this.setInteractive();
|
this.setInteractive();
|
||||||
this.setVisible(false);
|
this.setVisible(false);
|
||||||
this.setDepth(99999);
|
this.setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||||
|
|
||||||
this.on("pointerup", () => discussionManager.showDiscussionPart());
|
this.on("pointerup", () => discussionManager.showDiscussionPart());
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes";
|
||||||
|
|
||||||
export class PresentationModeIcon extends Phaser.GameObjects.Sprite {
|
export class PresentationModeIcon extends Phaser.GameObjects.Sprite {
|
||||||
constructor(scene: Phaser.Scene, x: number, y: number) {
|
constructor(scene: Phaser.Scene, x: number, y: number) {
|
||||||
super(scene, x, y, 'layout_modes', 0);
|
super(scene, x, y, 'layout_modes', 0);
|
||||||
@ -6,6 +8,6 @@ export class PresentationModeIcon extends Phaser.GameObjects.Sprite {
|
|||||||
this.setOrigin(0, 1);
|
this.setOrigin(0, 1);
|
||||||
this.setInteractive();
|
this.setInteractive();
|
||||||
this.setVisible(false);
|
this.setVisible(false);
|
||||||
this.setDepth(99999);
|
this.setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||||
}
|
}
|
||||||
}
|
}
|
74
front/src/Phaser/Components/RadialMenu.ts
Normal file
74
front/src/Phaser/Components/RadialMenu.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import Sprite = Phaser.GameObjects.Sprite;
|
||||||
|
import {DEPTH_UI_INDEX} from "../Game/DepthIndexes";
|
||||||
|
import {waScaleManager} from "../Services/WaScaleManager";
|
||||||
|
|
||||||
|
export interface RadialMenuItem {
|
||||||
|
image: string,
|
||||||
|
name: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RadialMenuClickEvent = 'radialClick';
|
||||||
|
|
||||||
|
export class RadialMenu extends Phaser.GameObjects.Container {
|
||||||
|
private resizeCallback: OmitThisParameter<() => void>;
|
||||||
|
|
||||||
|
constructor(scene: Phaser.Scene, x: number, y: number, private items: RadialMenuItem[]) {
|
||||||
|
super(scene, x, y);
|
||||||
|
this.setDepth(DEPTH_UI_INDEX)
|
||||||
|
this.scene.add.existing(this);
|
||||||
|
this.initItems();
|
||||||
|
|
||||||
|
this.resize();
|
||||||
|
this.resizeCallback = this.resize.bind(this);
|
||||||
|
this.scene.scale.on(Phaser.Scale.Events.RESIZE, this.resizeCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private initItems() {
|
||||||
|
const itemsNumber = this.items.length;
|
||||||
|
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, menuRadius: number) {
|
||||||
|
const image = new Sprite(this.scene, 0, menuRadius, item.image);
|
||||||
|
this.add(image);
|
||||||
|
this.scene.sys.updateList.add(image);
|
||||||
|
const scalingFactor = waScaleManager.uiScalingFactor * 0.075;
|
||||||
|
image.setScale(scalingFactor)
|
||||||
|
image.setInteractive({
|
||||||
|
useHandCursor: true,
|
||||||
|
});
|
||||||
|
image.on('pointerdown', () => this.emit(RadialMenuClickEvent, item));
|
||||||
|
image.on('pointerover', () => {
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: image,
|
||||||
|
props: {
|
||||||
|
scale: 2 * scalingFactor,
|
||||||
|
},
|
||||||
|
duration: 500,
|
||||||
|
ease: 'Power3',
|
||||||
|
})
|
||||||
|
});
|
||||||
|
image.on('pointerout', () => {
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: image,
|
||||||
|
props: {
|
||||||
|
scale: scalingFactor,
|
||||||
|
},
|
||||||
|
duration: 500,
|
||||||
|
ease: 'Power3',
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const angle = 2 * Math.PI * index / itemsNumber;
|
||||||
|
Phaser.Actions.RotateAroundDistance([image], {x: 0, y: 0}, angle, menuRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
private resize() {
|
||||||
|
this.setScale(waScaleManager.uiScalingFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy() {
|
||||||
|
this.scene.scale.removeListener(Phaser.Scale.Events.RESIZE, this.resizeCallback);
|
||||||
|
super.destroy();
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,11 @@ 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 type {GameScene} from "../Game/GameScene";
|
||||||
|
import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes";
|
||||||
|
import {waScaleManager} from "../Services/WaScaleManager";
|
||||||
|
|
||||||
|
const playerNameY = - 25;
|
||||||
|
|
||||||
interface AnimationData {
|
interface AnimationData {
|
||||||
key: string;
|
key: string;
|
||||||
@ -14,6 +19,8 @@ interface AnimationData {
|
|||||||
frames : number[]
|
frames : number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const interactiveRadius = 35;
|
||||||
|
|
||||||
export abstract class Character extends Container {
|
export abstract class Character extends Container {
|
||||||
private bubble: SpeechBubble|null = null;
|
private bubble: SpeechBubble|null = null;
|
||||||
private readonly playerName: BitmapText;
|
private readonly playerName: BitmapText;
|
||||||
@ -23,15 +30,19 @@ export abstract class Character extends Container {
|
|||||||
//private teleportation: Sprite;
|
//private teleportation: Sprite;
|
||||||
private invisible: boolean;
|
private invisible: boolean;
|
||||||
public companion?: Companion;
|
public companion?: Companion;
|
||||||
|
private emote: Phaser.GameObjects.Sprite | null = null;
|
||||||
|
private emoteTween: Phaser.Tweens.Tween|null = null;
|
||||||
|
|
||||||
constructor(scene: Phaser.Scene,
|
constructor(scene: GameScene,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
texturesPromise: Promise<string[]>,
|
texturesPromise: Promise<string[]>,
|
||||||
name: string,
|
name: string,
|
||||||
direction: PlayerAnimationDirections,
|
direction: PlayerAnimationDirections,
|
||||||
moving: boolean,
|
moving: boolean,
|
||||||
frame?: string | number
|
frame: string | number,
|
||||||
|
companion: string|null,
|
||||||
|
companionTexturePromise?: Promise<string>
|
||||||
) {
|
) {
|
||||||
super(scene, x, y/*, texture, frame*/);
|
super(scene, x, y/*, texture, frame*/);
|
||||||
this.PlayerValue = name;
|
this.PlayerValue = name;
|
||||||
@ -45,19 +56,18 @@ export abstract class Character extends Container {
|
|||||||
this.invisible = false
|
this.invisible = false
|
||||||
})
|
})
|
||||||
|
|
||||||
/*this.teleportation = new Sprite(scene, -20, -10, 'teleportation', 3);
|
this.playerName = new BitmapText(scene, 0, playerNameY, 'main_font', name, 7);
|
||||||
this.teleportation.setInteractive();
|
this.playerName.setOrigin(0.5).setCenterAlign().setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||||
this.teleportation.visible = false;
|
|
||||||
this.teleportation.on('pointerup', () => {
|
|
||||||
this.report.visible = false;
|
|
||||||
this.teleportation.visible = false;
|
|
||||||
});
|
|
||||||
this.add(this.teleportation);*/
|
|
||||||
|
|
||||||
this.playerName = new BitmapText(scene, 0, - 25, 'main_font', name, 7);
|
|
||||||
this.playerName.setOrigin(0.5).setCenterAlign().setDepth(99999);
|
|
||||||
this.add(this.playerName);
|
this.add(this.playerName);
|
||||||
|
|
||||||
|
if (this.isClickable()) {
|
||||||
|
this.setInteractive({
|
||||||
|
hitArea: new Phaser.Geom.Circle(0, 0, interactiveRadius),
|
||||||
|
hitAreaCallback: Phaser.Geom.Circle.Contains, //eslint-disable-line @typescript-eslint/unbound-method
|
||||||
|
useHandCursor: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
scene.add.existing(this);
|
scene.add.existing(this);
|
||||||
|
|
||||||
this.scene.physics.world.enableBody(this);
|
this.scene.physics.world.enableBody(this);
|
||||||
@ -69,6 +79,10 @@ export abstract class Character extends Container {
|
|||||||
this.setDepth(-1);
|
this.setDepth(-1);
|
||||||
|
|
||||||
this.playAnimation(direction, moving);
|
this.playAnimation(direction, moving);
|
||||||
|
|
||||||
|
if (typeof companion === 'string') {
|
||||||
|
this.addCompanion(companion, companionTexturePromise);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public addCompanion(name: string, texturePromise?: Promise<string>): void {
|
public addCompanion(name: string, texturePromise?: Promise<string>): void {
|
||||||
@ -77,13 +91,14 @@ export abstract class Character extends Container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract isClickable(): boolean;
|
||||||
|
|
||||||
public addTextures(textures: string[], frame?: string | number): void {
|
public addTextures(textures: string[], frame?: string | number): void {
|
||||||
for (const texture of textures) {
|
for (const texture of textures) {
|
||||||
if(!this.scene.textures.exists(texture)){
|
if(!this.scene.textures.exists(texture)){
|
||||||
throw new TextureError('texture not found');
|
throw new TextureError('texture not found');
|
||||||
}
|
}
|
||||||
const sprite = new Sprite(this.scene, 0, 0, texture, frame);
|
const sprite = new Sprite(this.scene, 0, 0, texture, frame);
|
||||||
sprite.setInteractive({useHandCursor: true});
|
|
||||||
this.add(sprite);
|
this.add(sprite);
|
||||||
this.getPlayerAnimations(texture).forEach(d => {
|
this.getPlayerAnimations(texture).forEach(d => {
|
||||||
this.scene.anims.create({
|
this.scene.anims.create({
|
||||||
@ -225,7 +240,84 @@ export abstract class Character extends Container {
|
|||||||
this.scene.sys.updateList.remove(sprite);
|
this.scene.sys.updateList.remove(sprite);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.list.forEach(objectContaining => objectContaining.destroy())
|
||||||
super.destroy();
|
super.destroy();
|
||||||
this.playerName.destroy();
|
}
|
||||||
|
|
||||||
|
playEmote(emoteKey: string) {
|
||||||
|
this.cancelPreviousEmote();
|
||||||
|
|
||||||
|
const scalingFactor = waScaleManager.uiScalingFactor * 0.05;
|
||||||
|
const emoteY = -30 - scalingFactor * 10;
|
||||||
|
|
||||||
|
this.playerName.setVisible(false);
|
||||||
|
this.emote = new Sprite(this.scene, 0, 0, emoteKey);
|
||||||
|
this.emote.setAlpha(0);
|
||||||
|
this.emote.setScale(0.1 * scalingFactor);
|
||||||
|
this.add(this.emote);
|
||||||
|
this.scene.sys.updateList.add(this.emote);
|
||||||
|
|
||||||
|
this.createStartTransition(scalingFactor, emoteY);
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
if (!this.emote) return;
|
||||||
|
|
||||||
|
this.emoteTween?.remove();
|
||||||
|
this.destroyEmote()
|
||||||
|
}
|
||||||
|
|
||||||
|
private destroyEmote() {
|
||||||
|
this.emote?.destroy();
|
||||||
|
this.emote = null;
|
||||||
|
this.playerName.setVisible(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,10 @@ import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
|||||||
import type {CharacterTexture} from "../../Connexion/LocalUser";
|
import type {CharacterTexture} from "../../Connexion/LocalUser";
|
||||||
import {BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES} from "./PlayerTextures";
|
import {BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES} from "./PlayerTextures";
|
||||||
|
|
||||||
|
export interface FrameConfig {
|
||||||
|
frameWidth: number,
|
||||||
|
frameHeight: number,
|
||||||
|
}
|
||||||
|
|
||||||
export const loadAllLayers = (load: LoaderPlugin): BodyResourceDescriptionInterface[][] => {
|
export const loadAllLayers = (load: LoaderPlugin): BodyResourceDescriptionInterface[][] => {
|
||||||
const returnArray:BodyResourceDescriptionInterface[][] = [];
|
const returnArray:BodyResourceDescriptionInterface[][] = [];
|
||||||
@ -26,7 +30,10 @@ export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptio
|
|||||||
export const loadCustomTexture = (loaderPlugin: LoaderPlugin, texture: CharacterTexture) : Promise<BodyResourceDescriptionInterface> => {
|
export const loadCustomTexture = (loaderPlugin: LoaderPlugin, texture: CharacterTexture) : Promise<BodyResourceDescriptionInterface> => {
|
||||||
const name = 'customCharacterTexture'+texture.id;
|
const name = 'customCharacterTexture'+texture.id;
|
||||||
const playerResourceDescriptor: BodyResourceDescriptionInterface = {name, img: texture.url, level: texture.level}
|
const playerResourceDescriptor: BodyResourceDescriptionInterface = {name, img: texture.url, level: texture.level}
|
||||||
return createLoadingPromise(loaderPlugin, playerResourceDescriptor);
|
return createLoadingPromise(loaderPlugin, playerResourceDescriptor, {
|
||||||
|
frameWidth: 32,
|
||||||
|
frameHeight: 32
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const lazyLoadPlayerCharacterTextures = (loadPlugin: LoaderPlugin, texturekeys:Array<string|BodyResourceDescriptionInterface>): Promise<string[]> => {
|
export const lazyLoadPlayerCharacterTextures = (loadPlugin: LoaderPlugin, texturekeys:Array<string|BodyResourceDescriptionInterface>): Promise<string[]> => {
|
||||||
@ -36,7 +43,10 @@ export const lazyLoadPlayerCharacterTextures = (loadPlugin: LoaderPlugin, textur
|
|||||||
//TODO refactor
|
//TODO refactor
|
||||||
const playerResourceDescriptor = getRessourceDescriptor(textureKey);
|
const playerResourceDescriptor = getRessourceDescriptor(textureKey);
|
||||||
if (playerResourceDescriptor && !loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
if (playerResourceDescriptor && !loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
||||||
promisesList.push(createLoadingPromise(loadPlugin, playerResourceDescriptor));
|
promisesList.push(createLoadingPromise(loadPlugin, playerResourceDescriptor, {
|
||||||
|
frameWidth: 32,
|
||||||
|
frameHeight: 32
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}catch (err){
|
}catch (err){
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@ -69,15 +79,12 @@ export const getRessourceDescriptor = (textureKey: string|BodyResourceDescriptio
|
|||||||
throw 'Could not find a data for texture '+textureName;
|
throw 'Could not find a data for texture '+textureName;
|
||||||
}
|
}
|
||||||
|
|
||||||
const createLoadingPromise = (loadPlugin: LoaderPlugin, playerResourceDescriptor: BodyResourceDescriptionInterface) => {
|
export const createLoadingPromise = (loadPlugin: LoaderPlugin, playerResourceDescriptor: BodyResourceDescriptionInterface, frameConfig: FrameConfig) => {
|
||||||
return new Promise<BodyResourceDescriptionInterface>((res) => {
|
return new Promise<BodyResourceDescriptionInterface>((res) => {
|
||||||
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
||||||
return res(playerResourceDescriptor);
|
return res(playerResourceDescriptor);
|
||||||
}
|
}
|
||||||
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, {
|
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, frameConfig);
|
||||||
frameWidth: 32,
|
|
||||||
frameHeight: 32
|
|
||||||
});
|
|
||||||
loadPlugin.once('filecomplete-spritesheet-' + playerResourceDescriptor.name, () => res(playerResourceDescriptor));
|
loadPlugin.once('filecomplete-spritesheet-' + playerResourceDescriptor.name, () => res(playerResourceDescriptor));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -21,14 +21,10 @@ export class RemotePlayer extends Character {
|
|||||||
companion: string|null,
|
companion: string|null,
|
||||||
companionTexturePromise?: Promise<string>
|
companionTexturePromise?: Promise<string>
|
||||||
) {
|
) {
|
||||||
super(Scene, x, y, texturesPromise, name, direction, moving, 1);
|
super(Scene, x, y, texturesPromise, name, direction, moving, 1, companion, companionTexturePromise);
|
||||||
|
|
||||||
//set data
|
//set data
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
|
|
||||||
if (typeof companion === 'string') {
|
|
||||||
this.addCompanion(companion, companionTexturePromise);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePosition(position: PointInterface): void {
|
updatePosition(position: PointInterface): void {
|
||||||
@ -42,4 +38,8 @@ export class RemotePlayer extends Character {
|
|||||||
this.companion.setTarget(position.x, position.y, position.direction as PlayerAnimationDirections);
|
this.companion.setTarget(position.x, position.y, position.direction as PlayerAnimationDirections);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isClickable(): boolean {
|
||||||
|
return false; //todo: make remote players clickable if they are logged in.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
8
front/src/Phaser/Game/DepthIndexes.ts
Normal file
8
front/src/Phaser/Game/DepthIndexes.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
//this file contains all the depth indexes which will be used in our game
|
||||||
|
|
||||||
|
export const DEPTH_TILE_INDEX = 0;
|
||||||
|
//Note: Player characters use their y coordinate as their depth to simulate a perspective.
|
||||||
|
//See the Character class.
|
||||||
|
export const DEPTH_OVERLAY_INDEX = 10000;
|
||||||
|
export const DEPTH_INGAME_TEXT_INDEX = 100000;
|
||||||
|
export const DEPTH_UI_INDEX = 1000000;
|
73
front/src/Phaser/Game/EmoteManager.ts
Normal file
73
front/src/Phaser/Game/EmoteManager.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||||
|
import {emoteEventStream} from "../../Connexion/EmoteEventStream";
|
||||||
|
import type {GameScene} from "./GameScene";
|
||||||
|
import type {RadialMenuItem} from "../Components/RadialMenu";
|
||||||
|
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
||||||
|
import type {Subscription} from "rxjs";
|
||||||
|
|
||||||
|
|
||||||
|
interface RegisteredEmote extends BodyResourceDescriptionInterface {
|
||||||
|
name: string;
|
||||||
|
img: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const emotes: {[key: string]: RegisteredEmote} = {
|
||||||
|
'emote-heart': {name: 'emote-heart', img: 'resources/emotes/heart-emote.png'},
|
||||||
|
'emote-clap': {name: 'emote-clap', img: 'resources/emotes/clap-emote.png'},
|
||||||
|
'emote-hand': {name: 'emote-hand', img: 'resources/emotes/hand-emote.png'},
|
||||||
|
'emote-thanks': {name: 'emote-thanks', img: 'resources/emotes/thanks-emote.png'},
|
||||||
|
'emote-thumb-up': {name: 'emote-thumb-up', img: 'resources/emotes/thumb-up-emote.png'},
|
||||||
|
'emote-thumb-down': {name: 'emote-thumb-down', img: 'resources/emotes/thumb-down-emote.png'},
|
||||||
|
};
|
||||||
|
|
||||||
|
export class EmoteManager {
|
||||||
|
private subscription: Subscription;
|
||||||
|
|
||||||
|
constructor(private scene: GameScene) {
|
||||||
|
this.subscription = emoteEventStream.stream.subscribe((event) => {
|
||||||
|
const actor = this.scene.MapPlayersByKey.get(event.userId);
|
||||||
|
if (actor) {
|
||||||
|
this.lazyLoadEmoteTexture(event.emoteName).then(emoteKey => {
|
||||||
|
actor.playEmote(emoteKey);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
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> {
|
||||||
|
const emoteDescriptor = emotes[textureKey];
|
||||||
|
if (emoteDescriptor === undefined) {
|
||||||
|
throw 'Emote not found!';
|
||||||
|
}
|
||||||
|
const loadPromise = this.createLoadingPromise(this.scene.load, emoteDescriptor);
|
||||||
|
this.scene.load.start();
|
||||||
|
return loadPromise
|
||||||
|
}
|
||||||
|
|
||||||
|
getMenuImages(): Promise<RadialMenuItem[]> {
|
||||||
|
const promises = [];
|
||||||
|
for (const key in emotes) {
|
||||||
|
const promise = this.lazyLoadEmoteTexture(key).then((textureKey) => {
|
||||||
|
return {
|
||||||
|
image: textureKey,
|
||||||
|
name: textureKey,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
promises.push(promise);
|
||||||
|
}
|
||||||
|
return Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.subscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import type {ITiledMap, ITiledMapLayer, ITiledMapTileLayer} from "../Map/ITiledMap";
|
import type {ITiledMap, ITiledMapLayer, ITiledMapTileLayer} from "../Map/ITiledMap";
|
||||||
import { flattenGroupLayersMap } from "../Map/LayersFlattener";
|
import { flattenGroupLayersMap } from "../Map/LayersFlattener";
|
||||||
import TilemapLayer = Phaser.Tilemaps.TilemapLayer;
|
import TilemapLayer = Phaser.Tilemaps.TilemapLayer;
|
||||||
|
import {DEPTH_OVERLAY_INDEX} from "./DepthIndexes";
|
||||||
|
|
||||||
export type PropertyChangeCallback = (newValue: string | number | boolean | undefined, oldValue: string | number | boolean | undefined, allProps: Map<string, string | boolean | number>) => void;
|
export type PropertyChangeCallback = (newValue: string | number | boolean | undefined, oldValue: string | number | boolean | undefined, allProps: Map<string, string | boolean | number>) => void;
|
||||||
|
|
||||||
@ -23,7 +24,7 @@ export class GameMap {
|
|||||||
this.phaserLayers.push(phaserMap.createLayer(layer.name, terrains, 0, 0).setDepth(depth));
|
this.phaserLayers.push(phaserMap.createLayer(layer.name, terrains, 0, 0).setDepth(depth));
|
||||||
}
|
}
|
||||||
if (layer.type === 'objectgroup' && layer.name === 'floorLayer') {
|
if (layer.type === 'objectgroup' && layer.name === 'floorLayer') {
|
||||||
depth = 10000;
|
depth = DEPTH_OVERLAY_INDEX;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import type {
|
|||||||
PositionInterface,
|
PositionInterface,
|
||||||
RoomJoinedMessageInterface
|
RoomJoinedMessageInterface
|
||||||
} from "../../Connexion/ConnexionModels";
|
} from "../../Connexion/ConnexionModels";
|
||||||
import { CurrentGamerInterface, hasMovedEventName, Player } from "../Player/Player";
|
import { hasMovedEventName, Player , requestEmoteEventName} from "../Player/Player";
|
||||||
import {
|
import {
|
||||||
DEBUG_MODE,
|
DEBUG_MODE,
|
||||||
JITSI_PRIVATE_MODE,
|
JITSI_PRIVATE_MODE,
|
||||||
@ -90,10 +90,11 @@ import { TextUtils } from "../Components/TextUtils";
|
|||||||
import { touchScreenManager } from "../../Touch/TouchScreenManager";
|
import { touchScreenManager } from "../../Touch/TouchScreenManager";
|
||||||
import { PinchManager } from "../UserInput/PinchManager";
|
import { PinchManager } from "../UserInput/PinchManager";
|
||||||
import { joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey } from "../Components/MobileJoystick";
|
import { joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey } from "../Components/MobileJoystick";
|
||||||
import { MenuScene, MenuSceneName } from '../Menu/MenuScene';
|
import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes";
|
||||||
import {waScaleManager } from "../Services/WaScaleManager";
|
import { waScaleManager } from "../Services/WaScaleManager";
|
||||||
|
import { EmoteManager } from "./EmoteManager";
|
||||||
import type { HasPlayerMovedEvent } from '../../Api/Events/HasPlayerMovedEvent';
|
import type { HasPlayerMovedEvent } from '../../Api/Events/HasPlayerMovedEvent';
|
||||||
|
import { MenuScene, MenuSceneName } from '../Menu/MenuScene';
|
||||||
|
|
||||||
export interface GameSceneInitInterface {
|
export interface GameSceneInitInterface {
|
||||||
initPosition: PointInterface | null,
|
initPosition: PointInterface | null,
|
||||||
@ -134,7 +135,7 @@ const defaultStartLayerName = 'start';
|
|||||||
|
|
||||||
export class GameScene extends DirtyScene implements CenterListener {
|
export class GameScene extends DirtyScene implements CenterListener {
|
||||||
Terrains: Array<Phaser.Tilemaps.Tileset>;
|
Terrains: Array<Phaser.Tilemaps.Tileset>;
|
||||||
CurrentPlayer!: CurrentGamerInterface;
|
CurrentPlayer!: Player;
|
||||||
MapPlayers!: Phaser.Physics.Arcade.Group;
|
MapPlayers!: Phaser.Physics.Arcade.Group;
|
||||||
MapPlayersByKey: Map<number, RemotePlayer> = new Map<number, RemotePlayer>();
|
MapPlayersByKey: Map<number, RemotePlayer> = new Map<number, RemotePlayer>();
|
||||||
Map!: Phaser.Tilemaps.Tilemap;
|
Map!: Phaser.Tilemaps.Tilemap;
|
||||||
@ -191,6 +192,7 @@ export class GameScene extends DirtyScene implements CenterListener {
|
|||||||
private physicsEnabled: boolean = true;
|
private physicsEnabled: boolean = true;
|
||||||
private mapTransitioning: boolean = false; //used to prevent transitions happenning at the same time.
|
private mapTransitioning: boolean = false; //used to prevent transitions happenning at the same time.
|
||||||
private onVisibilityChangeCallback: () => void;
|
private onVisibilityChangeCallback: () => void;
|
||||||
|
private emoteManager!: EmoteManager;
|
||||||
|
|
||||||
constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) {
|
constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) {
|
||||||
super({
|
super({
|
||||||
@ -228,6 +230,11 @@ export class GameScene extends DirtyScene implements CenterListener {
|
|||||||
this.load.image(joystickBaseKey, joystickBaseImg);
|
this.load.image(joystickBaseKey, joystickBaseImg);
|
||||||
this.load.image(joystickThumbKey, joystickThumbImg);
|
this.load.image(joystickThumbKey, joystickThumbImg);
|
||||||
}
|
}
|
||||||
|
//todo: in an emote manager.
|
||||||
|
this.load.spritesheet('emote-music', 'resources/emotes/pipo-popupemotes005.png', {
|
||||||
|
frameHeight: 32,
|
||||||
|
frameWidth: 32,
|
||||||
|
});
|
||||||
this.load.on(FILE_LOAD_ERROR, (file: { src: string }) => {
|
this.load.on(FILE_LOAD_ERROR, (file: { src: string }) => {
|
||||||
// If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments)
|
// If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments)
|
||||||
if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:') && this.originalMapUrl === undefined) {
|
if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:') && this.originalMapUrl === undefined) {
|
||||||
@ -502,6 +509,8 @@ export class GameScene extends DirtyScene implements CenterListener {
|
|||||||
}
|
}
|
||||||
console.log('display');
|
console.log('display');
|
||||||
document.addEventListener('visibilitychange', this.onVisibilityChangeCallback);
|
document.addEventListener('visibilitychange', this.onVisibilityChangeCallback);
|
||||||
|
|
||||||
|
this.emoteManager = new EmoteManager(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -986,6 +995,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();
|
||||||
@ -1178,6 +1188,12 @@ ${escapedMessage}
|
|||||||
this.companion,
|
this.companion,
|
||||||
this.companion !== null ? lazyLoadCompanionResource(this.load, this.companion) : undefined
|
this.companion !== null ? lazyLoadCompanionResource(this.load, this.companion) : undefined
|
||||||
);
|
);
|
||||||
|
this.CurrentPlayer.on('pointerdown', () => {
|
||||||
|
this.emoteManager.getMenuImages().then((emoteMenuElements) => this.CurrentPlayer.openOrCloseEmoteMenu(emoteMenuElements))
|
||||||
|
})
|
||||||
|
this.CurrentPlayer.on(requestEmoteEventName, (emoteKey: string) => {
|
||||||
|
this.connection?.emitEmoteEvent(emoteKey);
|
||||||
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof TextureError) {
|
if (err instanceof TextureError) {
|
||||||
gameManager.leaveGame(this, SelectCharacterSceneName, new SelectCharacterScene());
|
gameManager.leaveGame(this, SelectCharacterSceneName, new SelectCharacterScene());
|
||||||
|
@ -2,17 +2,16 @@ import {PlayerAnimationDirections} from "./Animation";
|
|||||||
import type {GameScene} from "../Game/GameScene";
|
import type {GameScene} from "../Game/GameScene";
|
||||||
import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager";
|
import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager";
|
||||||
import {Character} from "../Entity/Character";
|
import {Character} from "../Entity/Character";
|
||||||
|
import {RadialMenu, RadialMenuClickEvent, RadialMenuItem} from "../Components/RadialMenu";
|
||||||
|
|
||||||
export const hasMovedEventName = "hasMoved";
|
export const hasMovedEventName = "hasMoved";
|
||||||
export interface CurrentGamerInterface extends Character{
|
export const requestEmoteEventName = "requestEmote";
|
||||||
moveUser(delta: number) : void;
|
|
||||||
say(text : string) : void;
|
|
||||||
isMoving(): boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Player extends Character implements CurrentGamerInterface {
|
export class Player extends Character {
|
||||||
private previousDirection: string = PlayerAnimationDirections.Down;
|
private previousDirection: string = PlayerAnimationDirections.Down;
|
||||||
private wasMoving: boolean = false;
|
private wasMoving: boolean = false;
|
||||||
|
private emoteMenu: RadialMenu|null = null;
|
||||||
|
private updateListener: () => void;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
Scene: GameScene,
|
Scene: GameScene,
|
||||||
@ -26,14 +25,18 @@ export class Player extends Character implements CurrentGamerInterface {
|
|||||||
companion: string|null,
|
companion: string|null,
|
||||||
companionTexturePromise?: Promise<string>
|
companionTexturePromise?: Promise<string>
|
||||||
) {
|
) {
|
||||||
super(Scene, x, y, texturesPromise, name, direction, moving, 1);
|
super(Scene, x, y, texturesPromise, name, direction, moving, 1, companion, companionTexturePromise);
|
||||||
|
|
||||||
//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);
|
||||||
|
|
||||||
if (typeof companion === 'string') {
|
this.updateListener = () => {
|
||||||
this.addCompanion(companion, companionTexturePromise);
|
if (this.emoteMenu) {
|
||||||
|
this.emoteMenu.x = this.x;
|
||||||
|
this.emoteMenu.y = this.y;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
this.scene.events.addListener('postupdate', this.updateListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
moveUser(delta: number): void {
|
moveUser(delta: number): void {
|
||||||
@ -88,4 +91,37 @@ export class Player extends Character implements CurrentGamerInterface {
|
|||||||
public isMoving(): boolean {
|
public isMoving(): boolean {
|
||||||
return this.wasMoving;
|
return this.wasMoving;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openOrCloseEmoteMenu(emotes:RadialMenuItem[]) {
|
||||||
|
if(this.emoteMenu) {
|
||||||
|
this.closeEmoteMenu();
|
||||||
|
} else {
|
||||||
|
this.openEmoteMenu(emotes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isClickable(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
openEmoteMenu(emotes:RadialMenuItem[]): void {
|
||||||
|
this.cancelPreviousEmote();
|
||||||
|
this.emoteMenu = new RadialMenu(this.scene, this.x, this.y, emotes)
|
||||||
|
this.emoteMenu.on(RadialMenuClickEvent, (item: RadialMenuItem) => {
|
||||||
|
this.closeEmoteMenu();
|
||||||
|
this.emit(requestEmoteEventName, item.name);
|
||||||
|
this.playEmote(item.name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
closeEmoteMenu(): void {
|
||||||
|
if (!this.emoteMenu) return;
|
||||||
|
this.emoteMenu.destroy();
|
||||||
|
this.emoteMenu = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.scene.events.removeListener('postupdate', this.updateListener);
|
||||||
|
super.destroy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ class WaScaleManager {
|
|||||||
private hdpiManager: HdpiManager;
|
private hdpiManager: HdpiManager;
|
||||||
private scaleManager!: ScaleManager;
|
private scaleManager!: ScaleManager;
|
||||||
private game!: Game;
|
private game!: Game;
|
||||||
|
private actualZoom: number = 1;
|
||||||
|
|
||||||
public constructor(private minGamePixelsNumber: number, private absoluteMinPixelNumber: number) {
|
public constructor(private minGamePixelsNumber: number, private absoluteMinPixelNumber: number) {
|
||||||
this.hdpiManager = new HdpiManager(minGamePixelsNumber, absoluteMinPixelNumber);
|
this.hdpiManager = new HdpiManager(minGamePixelsNumber, absoluteMinPixelNumber);
|
||||||
@ -28,6 +29,7 @@ class WaScaleManager {
|
|||||||
|
|
||||||
const { game: gameSize, real: realSize } = this.hdpiManager.getOptimalGameSize({width: width * devicePixelRatio, height: height * devicePixelRatio});
|
const { game: gameSize, real: realSize } = this.hdpiManager.getOptimalGameSize({width: width * devicePixelRatio, height: height * devicePixelRatio});
|
||||||
|
|
||||||
|
this.actualZoom = realSize.width / gameSize.width / devicePixelRatio;
|
||||||
this.scaleManager.setZoom(realSize.width / gameSize.width / devicePixelRatio);
|
this.scaleManager.setZoom(realSize.width / gameSize.width / devicePixelRatio);
|
||||||
this.scaleManager.resize(gameSize.width, gameSize.height);
|
this.scaleManager.resize(gameSize.width, gameSize.height);
|
||||||
|
|
||||||
@ -48,6 +50,13 @@ class WaScaleManager {
|
|||||||
this.applyNewSize();
|
this.applyNewSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is used to scale back the ui components to counter-act the zoom.
|
||||||
|
*/
|
||||||
|
public get uiScalingFactor(): number {
|
||||||
|
return this.actualZoom > 1 ? 1 : 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const waScaleManager = new WaScaleManager(640*480, 196*196);
|
export const waScaleManager = new WaScaleManager(640*480, 196*196);
|
||||||
|
@ -20,5 +20,8 @@
|
|||||||
|
|
||||||
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||||
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */
|
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */
|
||||||
}
|
},
|
||||||
|
"include": [
|
||||||
|
"**/*.ts"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,15 @@ message ReportPlayerMessage {
|
|||||||
string reportComment = 2;
|
string reportComment = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message EmotePromptMessage {
|
||||||
|
string emote = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message EmoteEventMessage {
|
||||||
|
int32 actorUserId = 1;
|
||||||
|
string emote = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message QueryJitsiJwtMessage {
|
message QueryJitsiJwtMessage {
|
||||||
string jitsiRoom = 1;
|
string jitsiRoom = 1;
|
||||||
string tag = 2; // FIXME: rather than reading the tag from the query, we should read it from the current map!
|
string tag = 2; // FIXME: rather than reading the tag from the query, we should read it from the current map!
|
||||||
@ -84,6 +93,7 @@ message ClientToServerMessage {
|
|||||||
StopGlobalMessage stopGlobalMessage = 10;
|
StopGlobalMessage stopGlobalMessage = 10;
|
||||||
ReportPlayerMessage reportPlayerMessage = 11;
|
ReportPlayerMessage reportPlayerMessage = 11;
|
||||||
QueryJitsiJwtMessage queryJitsiJwtMessage = 12;
|
QueryJitsiJwtMessage queryJitsiJwtMessage = 12;
|
||||||
|
EmotePromptMessage emotePromptMessage = 13;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,6 +132,7 @@ message SubMessage {
|
|||||||
UserJoinedMessage userJoinedMessage = 4;
|
UserJoinedMessage userJoinedMessage = 4;
|
||||||
UserLeftMessage userLeftMessage = 5;
|
UserLeftMessage userLeftMessage = 5;
|
||||||
ItemEventMessage itemEventMessage = 6;
|
ItemEventMessage itemEventMessage = 6;
|
||||||
|
EmoteEventMessage emoteEventMessage = 7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,6 +258,7 @@ message ServerToClientMessage {
|
|||||||
WorldFullMessage worldFullMessage = 16;
|
WorldFullMessage worldFullMessage = 16;
|
||||||
RefreshRoomMessage refreshRoomMessage = 17;
|
RefreshRoomMessage refreshRoomMessage = 17;
|
||||||
WorldConnexionMessage worldConnexionMessage = 18;
|
WorldConnexionMessage worldConnexionMessage = 18;
|
||||||
|
EmoteEventMessage emoteEventMessage = 19;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,6 +329,7 @@ message PusherToBackMessage {
|
|||||||
QueryJitsiJwtMessage queryJitsiJwtMessage = 11;
|
QueryJitsiJwtMessage queryJitsiJwtMessage = 11;
|
||||||
SendUserMessage sendUserMessage = 12;
|
SendUserMessage sendUserMessage = 12;
|
||||||
BanUserMessage banUserMessage = 13;
|
BanUserMessage banUserMessage = 13;
|
||||||
|
EmotePromptMessage emotePromptMessage = 14;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -334,6 +347,7 @@ message SubToPusherMessage {
|
|||||||
ItemEventMessage itemEventMessage = 6;
|
ItemEventMessage itemEventMessage = 6;
|
||||||
SendUserMessage sendUserMessage = 7;
|
SendUserMessage sendUserMessage = 7;
|
||||||
BanUserMessage banUserMessage = 8;
|
BanUserMessage banUserMessage = 8;
|
||||||
|
EmoteEventMessage emoteEventMessage = 9;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,8 @@ import {
|
|||||||
WebRtcSignalToServerMessage,
|
WebRtcSignalToServerMessage,
|
||||||
PlayGlobalMessage,
|
PlayGlobalMessage,
|
||||||
ReportPlayerMessage,
|
ReportPlayerMessage,
|
||||||
QueryJitsiJwtMessage, SendUserMessage, ServerToClientMessage, CompanionMessage
|
EmoteEventMessage,
|
||||||
|
QueryJitsiJwtMessage, SendUserMessage, ServerToClientMessage, CompanionMessage, EmotePromptMessage
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import {UserMovesMessage} from "../Messages/generated/messages_pb";
|
import {UserMovesMessage} from "../Messages/generated/messages_pb";
|
||||||
import {TemplatedApp} from "uWebSockets.js"
|
import {TemplatedApp} from "uWebSockets.js"
|
||||||
@ -182,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,
|
||||||
@ -330,6 +331,8 @@ export class IoSocketController {
|
|||||||
socketManager.handleReportMessage(client, message.getReportplayermessage() as ReportPlayerMessage);
|
socketManager.handleReportMessage(client, message.getReportplayermessage() as ReportPlayerMessage);
|
||||||
} else if (message.hasQueryjitsijwtmessage()){
|
} else if (message.hasQueryjitsijwtmessage()){
|
||||||
socketManager.handleQueryJitsiJwtMessage(client, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
|
socketManager.handleQueryJitsiJwtMessage(client, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
|
||||||
|
} else if (message.hasEmotepromptmessage()){
|
||||||
|
socketManager.handleEmotePromptMessage(client, message.getEmotepromptmessage() as EmotePromptMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ok is false if backpressure was built up, wait for drain */
|
/* Ok is false if backpressure was built up, wait for drain */
|
||||||
|
@ -6,13 +6,11 @@ import {
|
|||||||
PointMessage, PositionMessage, UserJoinedMessage,
|
PointMessage, PositionMessage, UserJoinedMessage,
|
||||||
UserJoinedZoneMessage, UserLeftZoneMessage, UserMovedMessage,
|
UserJoinedZoneMessage, UserLeftZoneMessage, UserMovedMessage,
|
||||||
ZoneMessage,
|
ZoneMessage,
|
||||||
|
EmoteEventMessage,
|
||||||
CompanionMessage
|
CompanionMessage
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import * as messages_pb from "../Messages/generated/messages_pb";
|
|
||||||
import {ClientReadableStream} from "grpc";
|
import {ClientReadableStream} from "grpc";
|
||||||
import {PositionDispatcher} from "_Model/PositionDispatcher";
|
import {PositionDispatcher} from "_Model/PositionDispatcher";
|
||||||
import {socketManager} from "../Services/SocketManager";
|
|
||||||
import {ProtobufUtils} from "_Model/Websocket/ProtobufUtils";
|
|
||||||
import Debug from "debug";
|
import Debug from "debug";
|
||||||
|
|
||||||
const debug = Debug("zone");
|
const debug = Debug("zone");
|
||||||
@ -24,6 +22,7 @@ export interface ZoneEventListener {
|
|||||||
onGroupEnters(group: GroupDescriptor, listener: ExSocketInterface): void;
|
onGroupEnters(group: GroupDescriptor, listener: ExSocketInterface): void;
|
||||||
onGroupMoves(group: GroupDescriptor, listener: ExSocketInterface): void;
|
onGroupMoves(group: GroupDescriptor, listener: ExSocketInterface): void;
|
||||||
onGroupLeaves(groupId: number, listener: ExSocketInterface): void;
|
onGroupLeaves(groupId: number, listener: ExSocketInterface): void;
|
||||||
|
onEmote(emoteMessage: EmoteEventMessage, listener: ExSocketInterface): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*export type EntersCallback = (thing: Movable, listener: User) => void;
|
/*export type EntersCallback = (thing: Movable, listener: User) => void;
|
||||||
@ -184,6 +183,9 @@ export class Zone {
|
|||||||
userDescriptor.update(userMovedMessage);
|
userDescriptor.update(userMovedMessage);
|
||||||
|
|
||||||
this.notifyUserMove(userDescriptor);
|
this.notifyUserMove(userDescriptor);
|
||||||
|
} else if(message.hasEmoteeventmessage()) {
|
||||||
|
const emoteEventMessage = message.getEmoteeventmessage() as EmoteEventMessage;
|
||||||
|
this.notifyEmote(emoteEventMessage);
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unexpected message');
|
throw new Error('Unexpected message');
|
||||||
}
|
}
|
||||||
@ -262,6 +264,15 @@ export class Zone {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private notifyEmote(emoteMessage: EmoteEventMessage) {
|
||||||
|
for (const listener of this.listeners) {
|
||||||
|
if (listener.userId === emoteMessage.getActoruserid()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this.socketListener.onEmote(emoteMessage, listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify listeners of this zone that this group left
|
* Notify listeners of this zone that this group left
|
||||||
*/
|
*/
|
||||||
|
@ -23,7 +23,8 @@ import {
|
|||||||
WorldConnexionMessage,
|
WorldConnexionMessage,
|
||||||
AdminPusherToBackMessage,
|
AdminPusherToBackMessage,
|
||||||
ServerToAdminClientMessage,
|
ServerToAdminClientMessage,
|
||||||
UserJoinedRoomMessage, UserLeftRoomMessage, AdminMessage, BanMessage, RefreshRoomMessage
|
EmoteEventMessage,
|
||||||
|
UserJoinedRoomMessage, UserLeftRoomMessage, AdminMessage, BanMessage, RefreshRoomMessage, EmotePromptMessage
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
||||||
import {JITSI_ISS, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
|
import {JITSI_ISS, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
|
||||||
@ -73,6 +74,7 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
client.adminConnection = adminRoomStream;
|
client.adminConnection = adminRoomStream;
|
||||||
|
|
||||||
adminRoomStream.on('data', (message: ServerToAdminClientMessage) => {
|
adminRoomStream.on('data', (message: ServerToAdminClientMessage) => {
|
||||||
|
|
||||||
if (message.hasUserjoinedroom()) {
|
if (message.hasUserjoinedroom()) {
|
||||||
const userJoinedRoomMessage = message.getUserjoinedroom() as UserJoinedRoomMessage;
|
const userJoinedRoomMessage = message.getUserjoinedroom() as UserJoinedRoomMessage;
|
||||||
if (!client.disconnecting) {
|
if (!client.disconnecting) {
|
||||||
@ -254,6 +256,15 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
this.handleViewport(client, viewport.toObject())
|
this.handleViewport(client, viewport.toObject())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
onEmote(emoteMessage: EmoteEventMessage, listener: ExSocketInterface): void {
|
||||||
|
const subMessage = new SubMessage();
|
||||||
|
subMessage.setEmoteeventmessage(emoteMessage);
|
||||||
|
|
||||||
|
emitInBatch(listener, subMessage);
|
||||||
|
}
|
||||||
|
|
||||||
// Useless now, will be useful again if we allow editing details in game
|
// Useless now, will be useful again if we allow editing details in game
|
||||||
handleSetPlayerDetails(client: ExSocketInterface, playerDetailsMessage: SetPlayerDetailsMessage) {
|
handleSetPlayerDetails(client: ExSocketInterface, playerDetailsMessage: SetPlayerDetailsMessage) {
|
||||||
const pusherToBackMessage = new PusherToBackMessage();
|
const pusherToBackMessage = new PusherToBackMessage();
|
||||||
@ -321,6 +332,7 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
const room: PusherRoom | undefined = this.rooms.get(socket.roomId);
|
const room: PusherRoom | undefined = this.rooms.get(socket.roomId);
|
||||||
if (room) {
|
if (room) {
|
||||||
debug('Leaving room %s.', socket.roomId);
|
debug('Leaving room %s.', socket.roomId);
|
||||||
|
|
||||||
room.leave(socket);
|
room.leave(socket);
|
||||||
if (room.isEmpty()) {
|
if (room.isEmpty()) {
|
||||||
this.rooms.delete(socket.roomId);
|
this.rooms.delete(socket.roomId);
|
||||||
@ -578,6 +590,13 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
|
|
||||||
this.updateRoomWithAdminData(room);
|
this.updateRoomWithAdminData(room);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleEmotePromptMessage(client: ExSocketInterface, emoteEventmessage: EmotePromptMessage) {
|
||||||
|
const pusherToBackMessage = new PusherToBackMessage();
|
||||||
|
pusherToBackMessage.setEmotepromptmessage(emoteEventmessage);
|
||||||
|
|
||||||
|
client.backConnection.write(pusherToBackMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const socketManager = new SocketManager();
|
export const socketManager = new SocketManager();
|
||||||
|
Loading…
Reference in New Issue
Block a user