Merge branch 'develop' of github.com:thecodingmachine/workadventure
This commit is contained in:
commit
146129b829
56
.github/workflows/end_to_end_tests.yml
vendored
56
.github/workflows/end_to_end_tests.yml
vendored
@ -11,10 +11,42 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
|
start-runner:
|
||||||
|
name: Start self-hosted EC2 runner
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
label: ${{ steps.start-ec2-runner.outputs.label }}
|
||||||
|
ec2-instance-id: ${{ steps.start-ec2-runner.outputs.ec2-instance-id }}
|
||||||
|
steps:
|
||||||
|
- name: Configure AWS credentials
|
||||||
|
uses: aws-actions/configure-aws-credentials@v1
|
||||||
|
with:
|
||||||
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
|
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
|
aws-region: ${{ secrets.AWS_REGION }}
|
||||||
|
- name: Start EC2 runner
|
||||||
|
id: start-ec2-runner
|
||||||
|
uses: machulav/ec2-github-runner@v2
|
||||||
|
with:
|
||||||
|
mode: start
|
||||||
|
github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
|
||||||
|
ec2-image-id: ami-094dbcc53250a2480
|
||||||
|
ec2-instance-type: t3.xlarge
|
||||||
|
subnet-id: subnet-0ac40025f559df1bc
|
||||||
|
security-group-id: sg-0e36e96e3b8ed2d64
|
||||||
|
#iam-role-name: my-role-name # optional, requires additional permissions
|
||||||
|
#aws-resource-tags: > # optional, requires additional permissions
|
||||||
|
# [
|
||||||
|
# {"Key": "Name", "Value": "ec2-github-runner"},
|
||||||
|
# {"Key": "GitHubRepository", "Value": "${{ github.repository }}"}
|
||||||
|
# ]
|
||||||
|
|
||||||
|
|
||||||
end-to-end-tests:
|
end-to-end-tests:
|
||||||
name: "End-to-end testcafe tests"
|
name: "End-to-end testcafe tests"
|
||||||
|
|
||||||
runs-on: "ubuntu-latest"
|
needs: start-runner # required to start the main job when the runner is ready
|
||||||
|
runs-on: ${{ needs.start-runner.outputs.label }} # run the job on the newly created runner
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout"
|
- name: "Checkout"
|
||||||
@ -67,3 +99,25 @@ jobs:
|
|||||||
- name: Display logs
|
- name: Display logs
|
||||||
if: ${{ failure() }}
|
if: ${{ failure() }}
|
||||||
run: docker-compose logs
|
run: docker-compose logs
|
||||||
|
|
||||||
|
stop-runner:
|
||||||
|
name: Stop self-hosted EC2 runner
|
||||||
|
needs:
|
||||||
|
- start-runner # required to get output from the start-runner job
|
||||||
|
- end-to-end-tests # required to wait when the main job is done
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ always() }} # required to stop the runner even if the error happened in the previous jobs
|
||||||
|
steps:
|
||||||
|
- name: Configure AWS credentials
|
||||||
|
uses: aws-actions/configure-aws-credentials@v1
|
||||||
|
with:
|
||||||
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
|
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
|
aws-region: ${{ secrets.AWS_REGION }}
|
||||||
|
- name: Stop EC2 runner
|
||||||
|
uses: machulav/ec2-github-runner@v2
|
||||||
|
with:
|
||||||
|
mode: stop
|
||||||
|
github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
|
||||||
|
label: ${{ needs.start-runner.outputs.label }}
|
||||||
|
ec2-instance-id: ${{ needs.start-runner.outputs.ec2-instance-id }}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
# protobuf build
|
# protobuf build
|
||||||
FROM node:14-buster-slim as messages
|
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as messages
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
COPY messages .
|
COPY messages .
|
||||||
RUN yarn install && yarn proto
|
RUN yarn install && yarn proto
|
||||||
|
|
||||||
# typescript build
|
# typescript build
|
||||||
FROM node:14-buster-slim as builder
|
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
COPY back/yarn.lock back/package.json ./
|
COPY back/yarn.lock back/package.json ./
|
||||||
RUN yarn install
|
RUN yarn install
|
||||||
@ -15,7 +15,7 @@ ENV NODE_ENV=production
|
|||||||
RUN yarn run tsc
|
RUN yarn run tsc
|
||||||
|
|
||||||
# final production image
|
# final production image
|
||||||
FROM node:14-buster-slim
|
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
COPY back/yarn.lock back/package.json ./
|
COPY back/yarn.lock back/package.json ./
|
||||||
COPY --from=builder /usr/src/dist /usr/src/dist
|
COPY --from=builder /usr/src/dist /usr/src/dist
|
||||||
|
@ -21,6 +21,7 @@ import {
|
|||||||
SubToPusherRoomMessage,
|
SubToPusherRoomMessage,
|
||||||
VariableMessage,
|
VariableMessage,
|
||||||
VariableWithTagMessage,
|
VariableWithTagMessage,
|
||||||
|
ServerToClientMessage,
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
|
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
|
||||||
import { RoomSocket, ZoneSocket } from "src/RoomManager";
|
import { RoomSocket, ZoneSocket } from "src/RoomManager";
|
||||||
@ -110,10 +111,6 @@ export class GameRoom {
|
|||||||
return gameRoom;
|
return gameRoom;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getGroups(): Group[] {
|
|
||||||
return Array.from(this.groups.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
public getUsers(): Map<number, User> {
|
public getUsers(): Map<number, User> {
|
||||||
return this.users;
|
return this.users;
|
||||||
}
|
}
|
||||||
@ -176,6 +173,14 @@ export class GameRoom {
|
|||||||
if (userObj !== undefined && typeof userObj.group !== "undefined") {
|
if (userObj !== undefined && typeof userObj.group !== "undefined") {
|
||||||
this.leaveGroup(userObj);
|
this.leaveGroup(userObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (user.hasFollowers()) {
|
||||||
|
user.stopLeading();
|
||||||
|
}
|
||||||
|
if (user.following) {
|
||||||
|
user.following.delFollower(user);
|
||||||
|
}
|
||||||
|
|
||||||
this.users.delete(user.id);
|
this.users.delete(user.id);
|
||||||
this.usersByUuid.delete(user.uuid);
|
this.usersByUuid.delete(user.uuid);
|
||||||
|
|
||||||
@ -214,8 +219,8 @@ export class GameRoom {
|
|||||||
if (user.silent) {
|
if (user.silent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const group = user.group;
|
||||||
if (user.group === undefined) {
|
if (group === undefined) {
|
||||||
// If the user is not part of a group:
|
// If the user is not part of a group:
|
||||||
// should he join a group?
|
// should he join a group?
|
||||||
|
|
||||||
@ -246,13 +251,40 @@ export class GameRoom {
|
|||||||
} else {
|
} else {
|
||||||
// If the user is part of a group:
|
// If the user is part of a group:
|
||||||
// should he leave the group?
|
// should he leave the group?
|
||||||
const distance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), user.group.getPosition());
|
let noOneOutOfBounds = true;
|
||||||
if (distance > this.groupRadius) {
|
group.getUsers().forEach((foreignUser: User) => {
|
||||||
this.leaveGroup(user);
|
if (foreignUser.group === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const usrPos = foreignUser.getPosition();
|
||||||
|
const grpPos = foreignUser.group.getPosition();
|
||||||
|
const distance = GameRoom.computeDistanceBetweenPositions(usrPos, grpPos);
|
||||||
|
|
||||||
|
if (distance > this.groupRadius) {
|
||||||
|
if (foreignUser.hasFollowers() || foreignUser.following) {
|
||||||
|
// If one user is out of the group bounds BUT following, the group still exists... but should be hidden.
|
||||||
|
// We put it in 'outOfBounds' mode
|
||||||
|
group.setOutOfBounds(true);
|
||||||
|
noOneOutOfBounds = false;
|
||||||
|
} else {
|
||||||
|
this.leaveGroup(foreignUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (noOneOutOfBounds && !user.group?.isEmpty()) {
|
||||||
|
group.setOutOfBounds(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sendToOthersInGroupIncludingUser(user: User, message: ServerToClientMessage): void {
|
||||||
|
user.group?.getUsers().forEach((currentUser: User) => {
|
||||||
|
if (currentUser.id !== user.id) {
|
||||||
|
currentUser.socket.write(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setSilent(user: User, silent: boolean) {
|
setSilent(user: User, silent: boolean) {
|
||||||
if (user.silent === silent) {
|
if (user.silent === silent) {
|
||||||
return;
|
return;
|
||||||
@ -280,7 +312,6 @@ export class GameRoom {
|
|||||||
}
|
}
|
||||||
group.leave(user);
|
group.leave(user);
|
||||||
if (group.isEmpty()) {
|
if (group.isEmpty()) {
|
||||||
this.positionNotifier.leave(group);
|
|
||||||
group.destroy();
|
group.destroy();
|
||||||
if (!this.groups.has(group)) {
|
if (!this.groups.has(group)) {
|
||||||
throw new Error(`Could not find group ${group.getId()} referenced by user ${user.id} in World.`);
|
throw new Error(`Could not find group ${group.getId()} referenced by user ${user.id} in World.`);
|
||||||
|
@ -16,6 +16,10 @@ export class Group implements Movable {
|
|||||||
private wasDestroyed: boolean = false;
|
private wasDestroyed: boolean = false;
|
||||||
private roomId: string;
|
private roomId: string;
|
||||||
private currentZone: Zone | null = null;
|
private currentZone: Zone | null = null;
|
||||||
|
/**
|
||||||
|
* When outOfBounds = true, a user if out of the bounds of the group BUT still considered inside it (because we are in following mode)
|
||||||
|
*/
|
||||||
|
private outOfBounds = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
roomId: string,
|
roomId: string,
|
||||||
@ -78,6 +82,10 @@ export class Group implements Movable {
|
|||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
|
|
||||||
|
if (this.outOfBounds) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (oldX === undefined) {
|
if (oldX === undefined) {
|
||||||
this.currentZone = this.positionNotifier.enter(this);
|
this.currentZone = this.positionNotifier.enter(this);
|
||||||
} else {
|
} else {
|
||||||
@ -133,6 +141,10 @@ export class Group implements Movable {
|
|||||||
* Usually used when there is only one user left.
|
* Usually used when there is only one user left.
|
||||||
*/
|
*/
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
|
if (!this.outOfBounds) {
|
||||||
|
this.positionNotifier.leave(this);
|
||||||
|
}
|
||||||
|
|
||||||
for (const user of this.users) {
|
for (const user of this.users) {
|
||||||
this.leave(user);
|
this.leave(user);
|
||||||
}
|
}
|
||||||
@ -142,4 +154,26 @@ export class Group implements Movable {
|
|||||||
get getSize() {
|
get getSize() {
|
||||||
return this.users.size;
|
return this.users.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A group can have at most one person leading the way in it.
|
||||||
|
*/
|
||||||
|
get leader(): User | undefined {
|
||||||
|
for (const user of this.users) {
|
||||||
|
if (user.hasFollowers()) {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
setOutOfBounds(outOfBounds: boolean): void {
|
||||||
|
if (this.outOfBounds === true && outOfBounds === false) {
|
||||||
|
this.positionNotifier.enter(this);
|
||||||
|
this.outOfBounds = false;
|
||||||
|
} else if (this.outOfBounds === false && outOfBounds === true) {
|
||||||
|
this.positionNotifier.leave(this);
|
||||||
|
this.outOfBounds = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@ import { ServerDuplexStream } from "grpc";
|
|||||||
import {
|
import {
|
||||||
BatchMessage,
|
BatchMessage,
|
||||||
CompanionMessage,
|
CompanionMessage,
|
||||||
|
FollowAbortMessage,
|
||||||
|
FollowConfirmationMessage,
|
||||||
PusherToBackMessage,
|
PusherToBackMessage,
|
||||||
ServerToClientMessage,
|
ServerToClientMessage,
|
||||||
SetPlayerDetailsMessage,
|
SetPlayerDetailsMessage,
|
||||||
@ -19,6 +21,8 @@ export type UserSocket = ServerDuplexStream<PusherToBackMessage, ServerToClientM
|
|||||||
export class User implements Movable {
|
export class User implements Movable {
|
||||||
public listenedZones: Set<Zone>;
|
public listenedZones: Set<Zone>;
|
||||||
public group?: Group;
|
public group?: Group;
|
||||||
|
private _following: User | undefined;
|
||||||
|
private followedBy: Set<User> = new Set<User>();
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
public id: number,
|
public id: number,
|
||||||
@ -50,6 +54,45 @@ export class User implements Movable {
|
|||||||
this.positionNotifier.updatePosition(this, position, oldPosition);
|
this.positionNotifier.updatePosition(this, position, oldPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public addFollower(follower: User): void {
|
||||||
|
this.followedBy.add(follower);
|
||||||
|
follower._following = this;
|
||||||
|
|
||||||
|
const message = new FollowConfirmationMessage();
|
||||||
|
message.setFollower(follower.id);
|
||||||
|
message.setLeader(this.id);
|
||||||
|
const clientMessage = new ServerToClientMessage();
|
||||||
|
clientMessage.setFollowconfirmationmessage(message);
|
||||||
|
this.socket.write(clientMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public delFollower(follower: User): void {
|
||||||
|
this.followedBy.delete(follower);
|
||||||
|
follower._following = undefined;
|
||||||
|
|
||||||
|
const message = new FollowAbortMessage();
|
||||||
|
message.setFollower(follower.id);
|
||||||
|
message.setLeader(this.id);
|
||||||
|
const clientMessage = new ServerToClientMessage();
|
||||||
|
clientMessage.setFollowabortmessage(message);
|
||||||
|
this.socket.write(clientMessage);
|
||||||
|
follower.socket.write(clientMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasFollowers(): boolean {
|
||||||
|
return this.followedBy.size !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get following(): User | undefined {
|
||||||
|
return this._following;
|
||||||
|
}
|
||||||
|
|
||||||
|
public stopLeading(): void {
|
||||||
|
for (const follower of this.followedBy) {
|
||||||
|
this.delFollower(follower);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private batchedMessages: BatchMessage = new BatchMessage();
|
private batchedMessages: BatchMessage = new BatchMessage();
|
||||||
private batchTimeout: NodeJS.Timeout | null = null;
|
private batchTimeout: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
|
@ -9,6 +9,9 @@ import {
|
|||||||
BatchToPusherMessage,
|
BatchToPusherMessage,
|
||||||
BatchToPusherRoomMessage,
|
BatchToPusherRoomMessage,
|
||||||
EmotePromptMessage,
|
EmotePromptMessage,
|
||||||
|
FollowRequestMessage,
|
||||||
|
FollowConfirmationMessage,
|
||||||
|
FollowAbortMessage,
|
||||||
EmptyMessage,
|
EmptyMessage,
|
||||||
ItemEventMessage,
|
ItemEventMessage,
|
||||||
JoinRoomMessage,
|
JoinRoomMessage,
|
||||||
@ -119,6 +122,24 @@ const roomManager: IRoomManagerServer = {
|
|||||||
user,
|
user,
|
||||||
message.getEmotepromptmessage() as EmotePromptMessage
|
message.getEmotepromptmessage() as EmotePromptMessage
|
||||||
);
|
);
|
||||||
|
} else if (message.hasFollowrequestmessage()) {
|
||||||
|
socketManager.handleFollowRequestMessage(
|
||||||
|
room,
|
||||||
|
user,
|
||||||
|
message.getFollowrequestmessage() as FollowRequestMessage
|
||||||
|
);
|
||||||
|
} else if (message.hasFollowconfirmationmessage()) {
|
||||||
|
socketManager.handleFollowConfirmationMessage(
|
||||||
|
room,
|
||||||
|
user,
|
||||||
|
message.getFollowconfirmationmessage() as FollowConfirmationMessage
|
||||||
|
);
|
||||||
|
} else if (message.hasFollowabortmessage()) {
|
||||||
|
socketManager.handleFollowAbortMessage(
|
||||||
|
room,
|
||||||
|
user,
|
||||||
|
message.getFollowabortmessage() as FollowAbortMessage
|
||||||
|
);
|
||||||
} else if (message.hasSendusermessage()) {
|
} else if (message.hasSendusermessage()) {
|
||||||
const sendUserMessage = message.getSendusermessage();
|
const sendUserMessage = message.getSendusermessage();
|
||||||
socketManager.handleSendUserMessage(user, sendUserMessage as SendUserMessage);
|
socketManager.handleSendUserMessage(user, sendUserMessage as SendUserMessage);
|
||||||
|
@ -30,6 +30,9 @@ import {
|
|||||||
BanUserMessage,
|
BanUserMessage,
|
||||||
RefreshRoomMessage,
|
RefreshRoomMessage,
|
||||||
EmotePromptMessage,
|
EmotePromptMessage,
|
||||||
|
FollowRequestMessage,
|
||||||
|
FollowConfirmationMessage,
|
||||||
|
FollowAbortMessage,
|
||||||
VariableMessage,
|
VariableMessage,
|
||||||
BatchToPusherRoomMessage,
|
BatchToPusherRoomMessage,
|
||||||
SubToPusherRoomMessage,
|
SubToPusherRoomMessage,
|
||||||
@ -842,6 +845,39 @@ export class SocketManager {
|
|||||||
emoteEventMessage.setActoruserid(user.id);
|
emoteEventMessage.setActoruserid(user.id);
|
||||||
room.emitEmoteEvent(user, emoteEventMessage);
|
room.emitEmoteEvent(user, emoteEventMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleFollowRequestMessage(room: GameRoom, user: User, message: FollowRequestMessage) {
|
||||||
|
const clientMessage = new ServerToClientMessage();
|
||||||
|
clientMessage.setFollowrequestmessage(message);
|
||||||
|
room.sendToOthersInGroupIncludingUser(user, clientMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFollowConfirmationMessage(room: GameRoom, user: User, message: FollowConfirmationMessage) {
|
||||||
|
const leader = room.getUserById(message.getLeader());
|
||||||
|
if (!leader) {
|
||||||
|
const message = `Could not follow user "{message.getLeader()}" in room "{room.roomUrl}".`;
|
||||||
|
console.info(message, "Maybe the user just left.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// By security, we look at the group leader. If the group leader is NOT the leader in the message,
|
||||||
|
// everybody should stop following the group leader (to avoid having 2 group leaders)
|
||||||
|
if (user?.group?.leader && user?.group?.leader !== leader) {
|
||||||
|
user?.group?.leader?.stopLeading();
|
||||||
|
}
|
||||||
|
|
||||||
|
leader.addFollower(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFollowAbortMessage(room: GameRoom, user: User, message: FollowAbortMessage) {
|
||||||
|
if (user.id === message.getLeader()) {
|
||||||
|
user?.group?.leader?.stopLeading();
|
||||||
|
} else {
|
||||||
|
// Forward message
|
||||||
|
const leader = room.getUserById(message.getLeader());
|
||||||
|
leader?.delFollower(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const socketManager = new SocketManager();
|
export const socketManager = new SocketManager();
|
||||||
|
@ -1,27 +1,22 @@
|
|||||||
import { AdminMessageEventTypes, adminMessagesService } from "../Connexion/AdminMessagesService";
|
import { AdminMessageEventTypes, adminMessagesService } from "../Connexion/AdminMessagesService";
|
||||||
import { textMessageContentStore, textMessageVisibleStore } from "../Stores/TypeMessageStore/TextMessageStore";
|
import { textMessageStore } from "../Stores/TypeMessageStore/TextMessageStore";
|
||||||
import { soundPlayingStore } from "../Stores/SoundPlayingStore";
|
import { soundPlayingStore } from "../Stores/SoundPlayingStore";
|
||||||
import { UPLOADER_URL } from "../Enum/EnvironmentVariable";
|
import { UPLOADER_URL } from "../Enum/EnvironmentVariable";
|
||||||
import { banMessageContentStore, banMessageVisibleStore } from "../Stores/TypeMessageStore/BanMessageStore";
|
import { banMessageStore } from "../Stores/TypeMessageStore/BanMessageStore";
|
||||||
|
|
||||||
class UserMessageManager {
|
class UserMessageManager {
|
||||||
receiveBannedMessageListener!: Function;
|
receiveBannedMessageListener!: Function;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
adminMessagesService.messageStream.subscribe((event) => {
|
adminMessagesService.messageStream.subscribe((event) => {
|
||||||
textMessageVisibleStore.set(false);
|
|
||||||
banMessageVisibleStore.set(false);
|
|
||||||
if (event.type === AdminMessageEventTypes.admin) {
|
if (event.type === AdminMessageEventTypes.admin) {
|
||||||
textMessageContentStore.set(event.text);
|
textMessageStore.addMessage(event.text);
|
||||||
textMessageVisibleStore.set(true);
|
|
||||||
} else if (event.type === AdminMessageEventTypes.audio) {
|
} else if (event.type === AdminMessageEventTypes.audio) {
|
||||||
soundPlayingStore.playSound(UPLOADER_URL + event.text);
|
soundPlayingStore.playSound(UPLOADER_URL + event.text);
|
||||||
} else if (event.type === AdminMessageEventTypes.ban) {
|
} else if (event.type === AdminMessageEventTypes.ban) {
|
||||||
banMessageContentStore.set(event.text);
|
banMessageStore.addMessage(event.text);
|
||||||
banMessageVisibleStore.set(true);
|
|
||||||
} else if (event.type === AdminMessageEventTypes.banned) {
|
} else if (event.type === AdminMessageEventTypes.banned) {
|
||||||
banMessageContentStore.set(event.text);
|
banMessageStore.addMessage(event.text);
|
||||||
banMessageVisibleStore.set(true);
|
|
||||||
this.receiveBannedMessageListener();
|
this.receiveBannedMessageListener();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -30,10 +30,10 @@
|
|||||||
import EmoteMenu from "./EmoteMenu/EmoteMenu.svelte";
|
import EmoteMenu from "./EmoteMenu/EmoteMenu.svelte";
|
||||||
import VideoOverlay from "./Video/VideoOverlay.svelte";
|
import VideoOverlay from "./Video/VideoOverlay.svelte";
|
||||||
import { gameOverlayVisibilityStore } from "../Stores/GameOverlayStoreVisibility";
|
import { gameOverlayVisibilityStore } from "../Stores/GameOverlayStoreVisibility";
|
||||||
import AdminMessage from "./TypeMessage/BanMessage.svelte";
|
import BanMessageContainer from "./TypeMessage/BanMessageContainer.svelte";
|
||||||
import TextMessage from "./TypeMessage/TextMessage.svelte";
|
import TextMessageContainer from "./TypeMessage/TextMessageContainer.svelte";
|
||||||
import { banMessageVisibleStore } from "../Stores/TypeMessageStore/BanMessageStore";
|
import { banMessageStore } from "../Stores/TypeMessageStore/BanMessageStore";
|
||||||
import { textMessageVisibleStore } from "../Stores/TypeMessageStore/TextMessageStore";
|
import { textMessageStore } from "../Stores/TypeMessageStore/TextMessageStore";
|
||||||
import { warningContainerStore } from "../Stores/MenuStore";
|
import { warningContainerStore } from "../Stores/MenuStore";
|
||||||
import WarningContainer from "./WarningContainer/WarningContainer.svelte";
|
import WarningContainer from "./WarningContainer/WarningContainer.svelte";
|
||||||
import { layoutManagerVisibilityStore } from "../Stores/LayoutManagerStore";
|
import { layoutManagerVisibilityStore } from "../Stores/LayoutManagerStore";
|
||||||
@ -42,6 +42,9 @@
|
|||||||
import AudioManager from "./AudioManager/AudioManager.svelte";
|
import AudioManager from "./AudioManager/AudioManager.svelte";
|
||||||
import { showReportScreenStore, userReportEmpty } from "../Stores/ShowReportScreenStore";
|
import { showReportScreenStore, userReportEmpty } from "../Stores/ShowReportScreenStore";
|
||||||
import ReportMenu from "./ReportMenu/ReportMenu.svelte";
|
import ReportMenu from "./ReportMenu/ReportMenu.svelte";
|
||||||
|
import { followStateStore } from "../Stores/FollowStore";
|
||||||
|
import { peerStore } from "../Stores/PeerStore";
|
||||||
|
import FollowMenu from "./FollowMenu/FollowMenu.svelte";
|
||||||
|
|
||||||
export let game: Game;
|
export let game: Game;
|
||||||
</script>
|
</script>
|
||||||
@ -72,14 +75,13 @@
|
|||||||
<EnableCameraScene {game} />
|
<EnableCameraScene {game} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $banMessageVisibleStore}
|
{#if $banMessageStore.length > 0}
|
||||||
<div>
|
<div>
|
||||||
<AdminMessage />
|
<BanMessageContainer />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{:else if $textMessageStore.length > 0}
|
||||||
{#if $textMessageVisibleStore}
|
|
||||||
<div>
|
<div>
|
||||||
<TextMessage />
|
<TextMessageContainer />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $soundPlayingStore}
|
{#if $soundPlayingStore}
|
||||||
@ -102,6 +104,11 @@
|
|||||||
<ReportMenu />
|
<ReportMenu />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if $followStateStore !== "off" || $peerStore.size > 0}
|
||||||
|
<div>
|
||||||
|
<FollowMenu />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{#if $menuIconVisiblilityStore}
|
{#if $menuIconVisiblilityStore}
|
||||||
<div>
|
<div>
|
||||||
<MenuIcon />
|
<MenuIcon />
|
||||||
|
208
front/src/Components/FollowMenu/FollowMenu.svelte
Normal file
208
front/src/Components/FollowMenu/FollowMenu.svelte
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
<!--
|
||||||
|
vim: ft=typescript
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||||
|
import followImg from "../images/follow.svg";
|
||||||
|
|
||||||
|
import { followStateStore, followRoleStore, followUsersStore } from "../../Stores/FollowStore";
|
||||||
|
|
||||||
|
const gameScene = gameManager.getCurrentGameScene();
|
||||||
|
|
||||||
|
function name(userId: number): string | undefined {
|
||||||
|
return gameScene.MapPlayersByKey.get(userId)?.PlayerValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendFollowRequest() {
|
||||||
|
gameScene.connection?.emitFollowRequest();
|
||||||
|
followRoleStore.set("leader");
|
||||||
|
followStateStore.set("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
function acceptFollowRequest() {
|
||||||
|
gameScene.CurrentPlayer.enableFollowing();
|
||||||
|
gameScene.connection?.emitFollowConfirmation();
|
||||||
|
}
|
||||||
|
|
||||||
|
function abortEnding() {
|
||||||
|
followStateStore.set("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
gameScene.connection?.emitFollowAbort();
|
||||||
|
followUsersStore.stopFollowing();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeyDown(e: KeyboardEvent) {
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:keydown={onKeyDown} />
|
||||||
|
|
||||||
|
{#if $followStateStore === "requesting"}
|
||||||
|
<div class="interact-menu nes-container is-rounded">
|
||||||
|
{#if $followRoleStore === "follower"}
|
||||||
|
<section class="interact-menu-title">
|
||||||
|
<h2>Do you want to follow {name($followUsersStore[0])}?</h2>
|
||||||
|
</section>
|
||||||
|
<section class="interact-menu-action">
|
||||||
|
<button type="button" class="nes-btn is-success" on:click|preventDefault={acceptFollowRequest}
|
||||||
|
>Yes</button
|
||||||
|
>
|
||||||
|
<button type="button" class="nes-btn is-error" on:click|preventDefault={reset}>No</button>
|
||||||
|
</section>
|
||||||
|
{:else if $followRoleStore === "leader"}
|
||||||
|
<section class="interact-menu-question">
|
||||||
|
<p>Should never be displayed</p>
|
||||||
|
</section>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $followStateStore === "ending"}
|
||||||
|
<div class="interact-menu nes-container is-rounded">
|
||||||
|
<section class="interact-menu-title">
|
||||||
|
<h2>Interaction</h2>
|
||||||
|
</section>
|
||||||
|
{#if $followRoleStore === "follower"}
|
||||||
|
<section class="interact-menu-question">
|
||||||
|
<p>Do you want to stop following {name($followUsersStore[0])}?</p>
|
||||||
|
</section>
|
||||||
|
{:else if $followRoleStore === "leader"}
|
||||||
|
<section class="interact-menu-question">
|
||||||
|
<p>Do you want to stop leading the way?</p>
|
||||||
|
</section>
|
||||||
|
{/if}
|
||||||
|
<section class="interact-menu-action">
|
||||||
|
<button type="button" class="nes-btn is-success" on:click|preventDefault={reset}>Yes</button>
|
||||||
|
<button type="button" class="nes-btn is-error" on:click|preventDefault={abortEnding}>No</button>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $followStateStore === "active" || $followStateStore === "ending"}
|
||||||
|
<div class="interact-status nes-container is-rounded">
|
||||||
|
<section class="interact-status">
|
||||||
|
{#if $followRoleStore === "follower"}
|
||||||
|
<p>Following {name($followUsersStore[0])}</p>
|
||||||
|
{:else if $followUsersStore.length === 0}
|
||||||
|
<p>Waiting for followers' confirmation</p>
|
||||||
|
{:else if $followUsersStore.length === 1}
|
||||||
|
<p>{name($followUsersStore[0])} is following you</p>
|
||||||
|
{:else if $followUsersStore.length === 2}
|
||||||
|
<p>{name($followUsersStore[0])} and {name($followUsersStore[1])} are following you</p>
|
||||||
|
{:else}
|
||||||
|
<p>
|
||||||
|
{$followUsersStore.slice(0, -1).map(name).join(", ")} and {name(
|
||||||
|
$followUsersStore[$followUsersStore.length - 1]
|
||||||
|
)} are following you
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $followStateStore === "off"}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="nes-btn is-primary follow-menu-button"
|
||||||
|
on:click|preventDefault={sendFollowRequest}
|
||||||
|
title="Ask others to follow"><img class="background-img" src={followImg} alt="" /></button
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $followStateStore === "active" || $followStateStore === "ending"}
|
||||||
|
{#if $followRoleStore === "follower"}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="nes-btn is-error follow-menu-button"
|
||||||
|
on:click|preventDefault={reset}
|
||||||
|
title="Stop following"><img class="background-img" src={followImg} alt="" /></button
|
||||||
|
>
|
||||||
|
{:else}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="nes-btn is-error follow-menu-button"
|
||||||
|
on:click|preventDefault={reset}
|
||||||
|
title="Stop leading the way"><img class="background-img" src={followImg} alt="" /></button
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.nes-container {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.interact-status {
|
||||||
|
background-color: #333333;
|
||||||
|
color: whitesmoke;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
height: 2.7em;
|
||||||
|
width: 40vw;
|
||||||
|
top: 87vh;
|
||||||
|
margin: auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.interact-menu {
|
||||||
|
pointer-events: auto;
|
||||||
|
background-color: #333333;
|
||||||
|
color: whitesmoke;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
width: 60vw;
|
||||||
|
top: 60vh;
|
||||||
|
margin: auto;
|
||||||
|
|
||||||
|
section.interact-menu-title {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.interact-menu-question {
|
||||||
|
margin: 4px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 1.05em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
section.interact-menu-action {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 10%;
|
||||||
|
grid-template-columns: 45% 45%;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
margin-left: 5%;
|
||||||
|
margin-right: 5%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.follow-menu-button {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
left: 10px;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 800px) {
|
||||||
|
div.interact-status {
|
||||||
|
width: 100vw;
|
||||||
|
top: 78vh;
|
||||||
|
font-size: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.interact-menu {
|
||||||
|
height: 21vh;
|
||||||
|
width: 100vw;
|
||||||
|
font-size: 0.75em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -8,6 +8,7 @@
|
|||||||
let fullscreen: boolean = localUserStore.getFullscreen();
|
let fullscreen: boolean = localUserStore.getFullscreen();
|
||||||
let notification: boolean = localUserStore.getNotification() === "granted";
|
let notification: boolean = localUserStore.getNotification() === "granted";
|
||||||
let forceCowebsiteTrigger: boolean = localUserStore.getForceCowebsiteTrigger();
|
let forceCowebsiteTrigger: boolean = localUserStore.getForceCowebsiteTrigger();
|
||||||
|
let ignoreFollowRequests: boolean = localUserStore.getIgnoreFollowRequests();
|
||||||
let valueGame: number = localUserStore.getGameQualityValue();
|
let valueGame: number = localUserStore.getGameQualityValue();
|
||||||
let valueVideo: number = localUserStore.getVideoQualityValue();
|
let valueVideo: number = localUserStore.getVideoQualityValue();
|
||||||
let previewValueGame = valueGame;
|
let previewValueGame = valueGame;
|
||||||
@ -59,6 +60,10 @@
|
|||||||
localUserStore.setForceCowebsiteTrigger(forceCowebsiteTrigger);
|
localUserStore.setForceCowebsiteTrigger(forceCowebsiteTrigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function changeIgnoreFollowRequests() {
|
||||||
|
localUserStore.setIgnoreFollowRequests(ignoreFollowRequests);
|
||||||
|
}
|
||||||
|
|
||||||
function closeMenu() {
|
function closeMenu() {
|
||||||
menuVisiblilityStore.set(false);
|
menuVisiblilityStore.set(false);
|
||||||
}
|
}
|
||||||
@ -123,6 +128,15 @@
|
|||||||
/>
|
/>
|
||||||
<span>Always ask before opening websites and Jitsi Meet rooms</span>
|
<span>Always ask before opening websites and Jitsi Meet rooms</span>
|
||||||
</label>
|
</label>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="nes-checkbox is-dark"
|
||||||
|
bind:checked={ignoreFollowRequests}
|
||||||
|
on:change={changeIgnoreFollowRequests}
|
||||||
|
/>
|
||||||
|
<span>Ignore requests to follow other users</span>
|
||||||
|
</label>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fly } from "svelte/transition";
|
import { fly, fade } from "svelte/transition";
|
||||||
import { banMessageVisibleStore, banMessageContentStore } from "../../Stores/TypeMessageStore/BanMessageStore";
|
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
import type { Message } from "../../Stores/TypeMessageStore/MessageStore";
|
||||||
|
import { banMessageStore } from "../../Stores/TypeMessageStore/BanMessageStore";
|
||||||
|
|
||||||
|
export let message: Message;
|
||||||
|
|
||||||
let text: string;
|
|
||||||
$: {
|
|
||||||
text = $banMessageContentStore;
|
|
||||||
}
|
|
||||||
const NAME_BUTTON = "Ok";
|
const NAME_BUTTON = "Ok";
|
||||||
let nbSeconds = 10;
|
let nbSeconds = 10;
|
||||||
let nameButton = "";
|
let nameButton = "";
|
||||||
@ -28,17 +27,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function closeBanMessage() {
|
function closeBanMessage() {
|
||||||
banMessageVisibleStore.set(false);
|
banMessageStore.clearMessageById(message.id);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="main-ban-message nes-container is-rounded" transition:fly={{ y: -1000, duration: 500 }}>
|
<div
|
||||||
|
class="main-ban-message nes-container is-rounded"
|
||||||
|
in:fly={{ y: -1000, duration: 500, delay: 250 }}
|
||||||
|
out:fade={{ duration: 200 }}
|
||||||
|
>
|
||||||
<h2 class="title-ban-message">
|
<h2 class="title-ban-message">
|
||||||
<img src="resources/logos/report.svg" alt="***" /> Important message
|
<img src="resources/logos/report.svg" alt="***" /> Important message
|
||||||
<img src="resources/logos/report.svg" alt="***" />
|
<img src="resources/logos/report.svg" alt="***" />
|
||||||
</h2>
|
</h2>
|
||||||
<div class="content-ban-message">
|
<div class="content-ban-message">
|
||||||
<p>{text}</p>
|
<p>{message.text}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-ban-message">
|
<div class="footer-ban-message">
|
||||||
<button
|
<button
|
||||||
|
13
front/src/Components/TypeMessage/BanMessageContainer.svelte
Normal file
13
front/src/Components/TypeMessage/BanMessageContainer.svelte
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { flip } from "svelte/animate";
|
||||||
|
import { banMessageStore } from "../../Stores/TypeMessageStore/BanMessageStore";
|
||||||
|
import BanMessage from "./BanMessage.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="main-ban-message-container">
|
||||||
|
{#each $banMessageStore.slice(0, 1) as message (message.id)}
|
||||||
|
<div animate:flip={{ duration: 250 }}>
|
||||||
|
<BanMessage {message} />
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
@ -1,17 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fly } from "svelte/transition";
|
import { fly, fade } from "svelte/transition";
|
||||||
import { textMessageContentStore, textMessageVisibleStore } from "../../Stores/TypeMessageStore/TextMessageStore";
|
|
||||||
import { QuillDeltaToHtmlConverter } from "quill-delta-to-html";
|
import { QuillDeltaToHtmlConverter } from "quill-delta-to-html";
|
||||||
|
import type { Message } from "../../Stores/TypeMessageStore/MessageStore";
|
||||||
|
import { textMessageStore } from "../../Stores/TypeMessageStore/TextMessageStore";
|
||||||
|
|
||||||
let converter: QuillDeltaToHtmlConverter;
|
export let message: Message;
|
||||||
$: {
|
|
||||||
const content = JSON.parse($textMessageContentStore);
|
const content = JSON.parse(message.text);
|
||||||
converter = new QuillDeltaToHtmlConverter(content.ops, { inlineStyles: true });
|
const converter = new QuillDeltaToHtmlConverter(content.ops, { inlineStyles: true });
|
||||||
}
|
|
||||||
const NAME_BUTTON = "Ok";
|
const NAME_BUTTON = "Ok";
|
||||||
|
|
||||||
function closeTextMessage() {
|
function closeTextMessage() {
|
||||||
textMessageVisibleStore.set(false);
|
textMessageStore.clearMessageById(message.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onKeyDown(e: KeyboardEvent) {
|
function onKeyDown(e: KeyboardEvent) {
|
||||||
@ -23,7 +23,11 @@
|
|||||||
|
|
||||||
<svelte:window on:keydown={onKeyDown} />
|
<svelte:window on:keydown={onKeyDown} />
|
||||||
|
|
||||||
<div class="main-text-message nes-container is-rounded" transition:fly={{ x: -1000, duration: 500 }}>
|
<div
|
||||||
|
class="main-text-message nes-container is-rounded"
|
||||||
|
in:fly={{ x: -1000, duration: 500, delay: 250 }}
|
||||||
|
out:fade={{ duration: 250 }}
|
||||||
|
>
|
||||||
<div class="content-text-message">
|
<div class="content-text-message">
|
||||||
{@html converter.convert()}
|
{@html converter.convert()}
|
||||||
</div>
|
</div>
|
||||||
@ -43,6 +47,8 @@
|
|||||||
width: 80vw;
|
width: 80vw;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
margin-top: 0;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
|
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
|
21
front/src/Components/TypeMessage/TextMessageContainer.svelte
Normal file
21
front/src/Components/TypeMessage/TextMessageContainer.svelte
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { flip } from "svelte/animate";
|
||||||
|
import TextMessage from "./TextMessage.svelte";
|
||||||
|
import { textMessageStore } from "../../Stores/TypeMessageStore/TextMessageStore";
|
||||||
|
|
||||||
|
const MAX_MESSAGES = 3;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="main-text-message-container">
|
||||||
|
{#each $textMessageStore.slice(0, MAX_MESSAGES) as message (message.id)}
|
||||||
|
<div animate:flip={{ duration: 250 }}>
|
||||||
|
<TextMessage {message} />
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
div.main-text-message-container {
|
||||||
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
1
front/src/Components/images/follow.svg
Normal file
1
front/src/Components/images/follow.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24" viewBox="0 0 24 24" width="24"><rect fill="none" height="24" width="24"/><path d="M9.5,5.5c1.1,0,2-0.9,2-2s-0.9-2-2-2s-2,0.9-2,2S8.4,5.5,9.5,5.5z M5.75,8.9L3,23h2.1l1.75-8L9,17v6h2v-7.55L8.95,13.4 l0.6-3C10.85,12,12.8,13,15,13v-2c-1.85,0-3.45-1-4.35-2.45L9.7,6.95C9.35,6.35,8.7,6,8,6C7.75,6,7.5,6.05,7.25,6.15L2,8.3V13h2 V9.65L5.75,8.9 M13,2v7h3.75v14h1.5V9H22V2H13z M18.01,8V6.25H14.5v-1.5h3.51V3l2.49,2.5L18.01,8z"/></svg>
|
After Width: | Height: | Size: 510 B |
@ -1,5 +1,5 @@
|
|||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import { PUSHER_URL, START_ROOM_URL } from "../Enum/EnvironmentVariable";
|
import { PUSHER_URL } from "../Enum/EnvironmentVariable";
|
||||||
import { RoomConnection } from "./RoomConnection";
|
import { RoomConnection } from "./RoomConnection";
|
||||||
import type { OnConnectInterface, PositionInterface, ViewportInterface } from "./ConnexionModels";
|
import type { OnConnectInterface, PositionInterface, ViewportInterface } from "./ConnexionModels";
|
||||||
import { GameConnexionTypes, urlManager } from "../Url/UrlManager";
|
import { GameConnexionTypes, urlManager } from "../Url/UrlManager";
|
||||||
|
@ -14,6 +14,7 @@ const audioPlayerMuteKey = "audioMute";
|
|||||||
const helpCameraSettingsShown = "helpCameraSettingsShown";
|
const helpCameraSettingsShown = "helpCameraSettingsShown";
|
||||||
const fullscreenKey = "fullscreen";
|
const fullscreenKey = "fullscreen";
|
||||||
const forceCowebsiteTriggerKey = "forceCowebsiteTrigger";
|
const forceCowebsiteTriggerKey = "forceCowebsiteTrigger";
|
||||||
|
const ignoreFollowRequests = "ignoreFollowRequests";
|
||||||
const lastRoomUrl = "lastRoomUrl";
|
const lastRoomUrl = "lastRoomUrl";
|
||||||
const authToken = "authToken";
|
const authToken = "authToken";
|
||||||
const state = "state";
|
const state = "state";
|
||||||
@ -128,6 +129,13 @@ class LocalUserStore {
|
|||||||
return localStorage.getItem(forceCowebsiteTriggerKey) === "true";
|
return localStorage.getItem(forceCowebsiteTriggerKey) === "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setIgnoreFollowRequests(value: boolean): void {
|
||||||
|
localStorage.setItem(ignoreFollowRequests, value.toString());
|
||||||
|
}
|
||||||
|
getIgnoreFollowRequests(): boolean {
|
||||||
|
return localStorage.getItem(ignoreFollowRequests) === "true";
|
||||||
|
}
|
||||||
|
|
||||||
setLastRoomUrl(roomUrl: string): void {
|
setLastRoomUrl(roomUrl: string): void {
|
||||||
localStorage.setItem(lastRoomUrl, roomUrl.toString());
|
localStorage.setItem(lastRoomUrl, roomUrl.toString());
|
||||||
if ("caches" in window) {
|
if ("caches" in window) {
|
||||||
|
@ -30,6 +30,9 @@ import {
|
|||||||
PingMessage,
|
PingMessage,
|
||||||
EmoteEventMessage,
|
EmoteEventMessage,
|
||||||
EmotePromptMessage,
|
EmotePromptMessage,
|
||||||
|
FollowRequestMessage,
|
||||||
|
FollowConfirmationMessage,
|
||||||
|
FollowAbortMessage,
|
||||||
SendUserMessage,
|
SendUserMessage,
|
||||||
BanUserMessage,
|
BanUserMessage,
|
||||||
VariableMessage,
|
VariableMessage,
|
||||||
@ -59,7 +62,10 @@ import { adminMessagesService } from "./AdminMessagesService";
|
|||||||
import { worldFullMessageStream } from "./WorldFullMessageStream";
|
import { worldFullMessageStream } from "./WorldFullMessageStream";
|
||||||
import { connectionManager } from "./ConnectionManager";
|
import { connectionManager } from "./ConnectionManager";
|
||||||
import { emoteEventStream } from "./EmoteEventStream";
|
import { emoteEventStream } from "./EmoteEventStream";
|
||||||
|
import { get } from "svelte/store";
|
||||||
import { warningContainerStore } from "../Stores/MenuStore";
|
import { warningContainerStore } from "../Stores/MenuStore";
|
||||||
|
import { followStateStore, followRoleStore, followUsersStore } from "../Stores/FollowStore";
|
||||||
|
import { localUserStore } from "./LocalUserStore";
|
||||||
|
|
||||||
const manualPingDelay = 20000;
|
const manualPingDelay = 20000;
|
||||||
|
|
||||||
@ -262,6 +268,21 @@ export class RoomConnection implements RoomConnection {
|
|||||||
warningContainerStore.activateWarningContainer();
|
warningContainerStore.activateWarningContainer();
|
||||||
} else if (message.hasRefreshroommessage()) {
|
} else if (message.hasRefreshroommessage()) {
|
||||||
//todo: implement a way to notify the user the room was refreshed.
|
//todo: implement a way to notify the user the room was refreshed.
|
||||||
|
} else if (message.hasFollowrequestmessage()) {
|
||||||
|
const requestMessage = message.getFollowrequestmessage() as FollowRequestMessage;
|
||||||
|
if (!localUserStore.getIgnoreFollowRequests()) {
|
||||||
|
followUsersStore.addFollowRequest(requestMessage.getLeader());
|
||||||
|
}
|
||||||
|
} else if (message.hasFollowconfirmationmessage()) {
|
||||||
|
const responseMessage = message.getFollowconfirmationmessage() as FollowConfirmationMessage;
|
||||||
|
followUsersStore.addFollower(responseMessage.getFollower());
|
||||||
|
} else if (message.hasFollowabortmessage()) {
|
||||||
|
const abortMessage = message.getFollowabortmessage() as FollowAbortMessage;
|
||||||
|
if (get(followRoleStore) === "follower") {
|
||||||
|
followUsersStore.stopFollowing();
|
||||||
|
} else {
|
||||||
|
followUsersStore.removeFollower(abortMessage.getFollower());
|
||||||
|
}
|
||||||
} else if (message.hasErrormessage()) {
|
} else if (message.hasErrormessage()) {
|
||||||
const errorMessage = message.getErrormessage() as ErrorMessage;
|
const errorMessage = message.getErrormessage() as ErrorMessage;
|
||||||
console.error("An error occurred server side: " + errorMessage.getMessage());
|
console.error("An error occurred server side: " + errorMessage.getMessage());
|
||||||
@ -746,6 +767,43 @@ export class RoomConnection implements RoomConnection {
|
|||||||
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public emitFollowRequest(): void {
|
||||||
|
if (!this.userId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const message = new FollowRequestMessage();
|
||||||
|
message.setLeader(this.userId);
|
||||||
|
const clientToServerMessage = new ClientToServerMessage();
|
||||||
|
clientToServerMessage.setFollowrequestmessage(message);
|
||||||
|
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public emitFollowConfirmation(): void {
|
||||||
|
if (!this.userId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const message = new FollowConfirmationMessage();
|
||||||
|
message.setLeader(get(followUsersStore)[0]);
|
||||||
|
message.setFollower(this.userId);
|
||||||
|
const clientToServerMessage = new ClientToServerMessage();
|
||||||
|
clientToServerMessage.setFollowconfirmationmessage(message);
|
||||||
|
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public emitFollowAbort(): void {
|
||||||
|
const isLeader = get(followRoleStore) === "leader";
|
||||||
|
const hasFollowers = get(followUsersStore).length > 0;
|
||||||
|
if (!this.userId || (isLeader && !hasFollowers)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const message = new FollowAbortMessage();
|
||||||
|
message.setLeader(isLeader ? this.userId : get(followUsersStore)[0]);
|
||||||
|
message.setFollower(isLeader ? 0 : this.userId);
|
||||||
|
const clientToServerMessage = new ClientToServerMessage();
|
||||||
|
clientToServerMessage.setFollowabortmessage(message);
|
||||||
|
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
||||||
|
}
|
||||||
|
|
||||||
public getAllTags(): string[] {
|
public getAllTags(): string[] {
|
||||||
return this.tags;
|
return this.tags;
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ export abstract class Character extends Container {
|
|||||||
private readonly playerName: Text;
|
private readonly playerName: Text;
|
||||||
public PlayerValue: string;
|
public PlayerValue: string;
|
||||||
public sprites: Map<string, Sprite>;
|
public sprites: Map<string, Sprite>;
|
||||||
private lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down;
|
protected lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down;
|
||||||
//private teleportation: Sprite;
|
//private teleportation: Sprite;
|
||||||
private invisible: boolean;
|
private invisible: boolean;
|
||||||
public companion?: Companion;
|
public companion?: Companion;
|
||||||
@ -277,24 +277,20 @@ export abstract class Character extends Container {
|
|||||||
|
|
||||||
body.setVelocity(x, y);
|
body.setVelocity(x, y);
|
||||||
|
|
||||||
// up or down animations are prioritized over left and right
|
if (Math.abs(body.velocity.x) > Math.abs(body.velocity.y)) {
|
||||||
if (body.velocity.y < 0) {
|
if (body.velocity.x < 0) {
|
||||||
//moving up
|
this.lastDirection = PlayerAnimationDirections.Left;
|
||||||
this.lastDirection = PlayerAnimationDirections.Up;
|
} else if (body.velocity.x > 0) {
|
||||||
this.playAnimation(PlayerAnimationDirections.Up, true);
|
this.lastDirection = PlayerAnimationDirections.Right;
|
||||||
} else if (body.velocity.y > 0) {
|
}
|
||||||
//moving down
|
} else {
|
||||||
this.lastDirection = PlayerAnimationDirections.Down;
|
if (body.velocity.y < 0) {
|
||||||
this.playAnimation(PlayerAnimationDirections.Down, true);
|
this.lastDirection = PlayerAnimationDirections.Up;
|
||||||
} else if (body.velocity.x > 0) {
|
} else if (body.velocity.y > 0) {
|
||||||
//moving right
|
this.lastDirection = PlayerAnimationDirections.Down;
|
||||||
this.lastDirection = PlayerAnimationDirections.Right;
|
}
|
||||||
this.playAnimation(PlayerAnimationDirections.Right, true);
|
|
||||||
} else if (body.velocity.x < 0) {
|
|
||||||
//moving left
|
|
||||||
this.lastDirection = PlayerAnimationDirections.Left;
|
|
||||||
this.playAnimation(PlayerAnimationDirections.Left, true);
|
|
||||||
}
|
}
|
||||||
|
this.playAnimation(this.lastDirection, true);
|
||||||
|
|
||||||
this.setDepth(this.y);
|
this.setDepth(this.y);
|
||||||
|
|
||||||
|
@ -81,7 +81,14 @@ export class GameMap {
|
|||||||
let depth = -2;
|
let depth = -2;
|
||||||
for (const layer of this.flatLayers) {
|
for (const layer of this.flatLayers) {
|
||||||
if (layer.type === "tilelayer") {
|
if (layer.type === "tilelayer") {
|
||||||
this.phaserLayers.push(phaserMap.createLayer(layer.name, terrains, 0, 0).setDepth(depth));
|
this.phaserLayers.push(
|
||||||
|
phaserMap
|
||||||
|
.createLayer(layer.name, terrains, (layer.x || 0) * 32, (layer.y || 0) * 32)
|
||||||
|
.setDepth(depth)
|
||||||
|
.setAlpha(layer.opacity)
|
||||||
|
.setVisible(layer.visible)
|
||||||
|
.setSize(layer.width, layer.height)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (layer.type === "objectgroup" && layer.name === "floorLayer") {
|
if (layer.type === "objectgroup" && layer.name === "floorLayer") {
|
||||||
depth = DEPTH_OVERLAY_INDEX;
|
depth = DEPTH_OVERLAY_INDEX;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type { Subscription } from "rxjs";
|
import type { Subscription } from "rxjs";
|
||||||
import AnimatedTiles from "phaser-animated-tiles";
|
import AnimatedTiles from "phaser-animated-tiles";
|
||||||
import { Queue } from "queue-typescript";
|
import { Queue } from "queue-typescript";
|
||||||
import { get } from "svelte/store";
|
import { get, Unsubscriber } from "svelte/store";
|
||||||
|
|
||||||
import { userMessageManager } from "../../Administration/UserMessageManager";
|
import { userMessageManager } from "../../Administration/UserMessageManager";
|
||||||
import { connectionManager } from "../../Connexion/ConnectionManager";
|
import { connectionManager } from "../../Connexion/ConnectionManager";
|
||||||
@ -91,6 +91,8 @@ import { deepCopy } from "deep-copy-ts";
|
|||||||
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
|
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
|
||||||
import { MapStore } from "../../Stores/Utils/MapStore";
|
import { MapStore } from "../../Stores/Utils/MapStore";
|
||||||
import { SetPlayerDetailsMessage } from "../../Messages/generated/messages_pb";
|
import { SetPlayerDetailsMessage } from "../../Messages/generated/messages_pb";
|
||||||
|
import { followUsersColorStore, followUsersStore } from "../../Stores/FollowStore";
|
||||||
|
import { getColorRgbFromHue } from "../../WebRtc/ColorGenerator";
|
||||||
|
|
||||||
export interface GameSceneInitInterface {
|
export interface GameSceneInitInterface {
|
||||||
initPosition: PointInterface | null;
|
initPosition: PointInterface | null;
|
||||||
@ -165,9 +167,11 @@ export class GameScene extends DirtyScene {
|
|||||||
private createPromise: Promise<void>;
|
private createPromise: Promise<void>;
|
||||||
private createPromiseResolve!: (value?: void | PromiseLike<void>) => void;
|
private createPromiseResolve!: (value?: void | PromiseLike<void>) => void;
|
||||||
private iframeSubscriptionList!: Array<Subscription>;
|
private iframeSubscriptionList!: Array<Subscription>;
|
||||||
private peerStoreUnsubscribe!: () => void;
|
private peerStoreUnsubscribe!: Unsubscriber;
|
||||||
private emoteUnsubscribe!: () => void;
|
private emoteUnsubscribe!: Unsubscriber;
|
||||||
private emoteMenuUnsubscribe!: () => void;
|
private emoteMenuUnsubscribe!: Unsubscriber;
|
||||||
|
private followUsersColorStoreUnsubscribe!: Unsubscriber;
|
||||||
|
|
||||||
private biggestAvailableAreaStoreUnsubscribe!: () => void;
|
private biggestAvailableAreaStoreUnsubscribe!: () => void;
|
||||||
MapUrlFile: string;
|
MapUrlFile: string;
|
||||||
roomUrl: string;
|
roomUrl: string;
|
||||||
@ -663,7 +667,17 @@ export class GameScene extends DirtyScene {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Promise.all([ this.connectionAnswerPromise as Promise<unknown>, ...scriptPromises ]).then(() => {
|
this.followUsersColorStoreUnsubscribe = followUsersColorStore.subscribe((color) => {
|
||||||
|
if (color !== undefined) {
|
||||||
|
this.CurrentPlayer.setOutlineColor(color);
|
||||||
|
this.connection?.emitPlayerOutlineColor(color);
|
||||||
|
} else {
|
||||||
|
this.CurrentPlayer.removeOutlineColor();
|
||||||
|
this.connection?.emitPlayerOutlineColor(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Promise.all([this.connectionAnswerPromise as Promise<unknown>, ...scriptPromises]).then(() => {
|
||||||
this.scene.wake();
|
this.scene.wake();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1476,6 +1490,7 @@ export class GameScene extends DirtyScene {
|
|||||||
this.peerStoreUnsubscribe();
|
this.peerStoreUnsubscribe();
|
||||||
this.emoteUnsubscribe();
|
this.emoteUnsubscribe();
|
||||||
this.emoteMenuUnsubscribe();
|
this.emoteMenuUnsubscribe();
|
||||||
|
this.followUsersColorStoreUnsubscribe();
|
||||||
this.biggestAvailableAreaStoreUnsubscribe();
|
this.biggestAvailableAreaStoreUnsubscribe();
|
||||||
iframeListener.unregisterAnswerer("getState");
|
iframeListener.unregisterAnswerer("getState");
|
||||||
iframeListener.unregisterAnswerer("loadTileset");
|
iframeListener.unregisterAnswerer("loadTileset");
|
||||||
|
@ -41,7 +41,7 @@ export class PlayerMovement {
|
|||||||
oldX: this.startPosition.x,
|
oldX: this.startPosition.x,
|
||||||
oldY: this.startPosition.y,
|
oldY: this.startPosition.y,
|
||||||
direction: this.endPosition.direction,
|
direction: this.endPosition.direction,
|
||||||
moving: true,
|
moving: this.endPosition.moving,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
import { PlayerAnimationDirections } from "./Animation";
|
import { PlayerAnimationDirections } from "./Animation";
|
||||||
import type { GameScene } from "../Game/GameScene";
|
import type { GameScene } from "../Game/GameScene";
|
||||||
import { UserInputEvent, UserInputManager } from "../UserInput/UserInputManager";
|
import { ActiveEventList, UserInputEvent, UserInputManager } from "../UserInput/UserInputManager";
|
||||||
import { Character } from "../Entity/Character";
|
import { Character } from "../Entity/Character";
|
||||||
|
import type { RemotePlayer } from "../Entity/RemotePlayer";
|
||||||
|
|
||||||
|
import { get } from "svelte/store";
|
||||||
import { userMovingStore } from "../../Stores/GameStore";
|
import { userMovingStore } from "../../Stores/GameStore";
|
||||||
|
import { followStateStore, followRoleStore, followUsersStore } from "../../Stores/FollowStore";
|
||||||
|
|
||||||
export const hasMovedEventName = "hasMoved";
|
export const hasMovedEventName = "hasMoved";
|
||||||
export const requestEmoteEventName = "requestEmote";
|
export const requestEmoteEventName = "requestEmote";
|
||||||
|
|
||||||
export class Player extends Character {
|
export class Player extends Character {
|
||||||
private previousDirection: string = PlayerAnimationDirections.Down;
|
|
||||||
private wasMoving: boolean = false;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
Scene: GameScene,
|
Scene: GameScene,
|
||||||
x: number,
|
x: number,
|
||||||
@ -29,71 +30,99 @@ export class Player extends Character {
|
|||||||
this.getBody().setImmovable(false);
|
this.getBody().setImmovable(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
moveUser(delta: number): void {
|
private inputStep(activeEvents: ActiveEventList, x: number, y: number) {
|
||||||
//if user client on shift, camera and player speed
|
// Process input events
|
||||||
let direction = null;
|
|
||||||
let moving = false;
|
|
||||||
|
|
||||||
const activeEvents = this.userInputManager.getEventListForGameTick();
|
|
||||||
const speedMultiplier = activeEvents.get(UserInputEvent.SpeedUp) ? 25 : 9;
|
|
||||||
const moveAmount = speedMultiplier * 20;
|
|
||||||
|
|
||||||
let x = 0;
|
|
||||||
let y = 0;
|
|
||||||
if (activeEvents.get(UserInputEvent.MoveUp)) {
|
if (activeEvents.get(UserInputEvent.MoveUp)) {
|
||||||
y = -moveAmount;
|
y = y - 1;
|
||||||
direction = PlayerAnimationDirections.Up;
|
|
||||||
moving = true;
|
|
||||||
} else if (activeEvents.get(UserInputEvent.MoveDown)) {
|
} else if (activeEvents.get(UserInputEvent.MoveDown)) {
|
||||||
y = moveAmount;
|
y = y + 1;
|
||||||
direction = PlayerAnimationDirections.Down;
|
|
||||||
moving = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeEvents.get(UserInputEvent.MoveLeft)) {
|
if (activeEvents.get(UserInputEvent.MoveLeft)) {
|
||||||
x = -moveAmount;
|
x = x - 1;
|
||||||
direction = PlayerAnimationDirections.Left;
|
|
||||||
moving = true;
|
|
||||||
} else if (activeEvents.get(UserInputEvent.MoveRight)) {
|
} else if (activeEvents.get(UserInputEvent.MoveRight)) {
|
||||||
x = moveAmount;
|
x = x + 1;
|
||||||
direction = PlayerAnimationDirections.Right;
|
|
||||||
moving = true;
|
|
||||||
}
|
}
|
||||||
moving = moving || activeEvents.get(UserInputEvent.JoystickMove);
|
|
||||||
|
|
||||||
if (x !== 0 || y !== 0) {
|
// Compute movement deltas
|
||||||
|
const followMode = get(followStateStore) !== "off";
|
||||||
|
const speedup = activeEvents.get(UserInputEvent.SpeedUp) && !followMode ? 25 : 9;
|
||||||
|
const moveAmount = speedup * 20;
|
||||||
|
x = x * moveAmount;
|
||||||
|
y = y * moveAmount;
|
||||||
|
|
||||||
|
// Compute moving state
|
||||||
|
const joystickMovement = activeEvents.get(UserInputEvent.JoystickMove);
|
||||||
|
const moving = x !== 0 || y !== 0 || joystickMovement;
|
||||||
|
|
||||||
|
// Compute direction
|
||||||
|
let direction = this.lastDirection;
|
||||||
|
if (moving && !joystickMovement) {
|
||||||
|
if (Math.abs(x) > Math.abs(y)) {
|
||||||
|
direction = x < 0 ? PlayerAnimationDirections.Left : PlayerAnimationDirections.Right;
|
||||||
|
} else {
|
||||||
|
direction = y < 0 ? PlayerAnimationDirections.Up : PlayerAnimationDirections.Down;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send movement events
|
||||||
|
const emit = () => this.emit(hasMovedEventName, { moving, direction, x: this.x, y: this.y });
|
||||||
|
if (moving) {
|
||||||
this.move(x, y);
|
this.move(x, y);
|
||||||
this.emit(hasMovedEventName, { moving, direction, x: this.x, y: this.y, oldX: x, oldY: y });
|
emit();
|
||||||
} else if (this.wasMoving && moving) {
|
} else if (get(userMovingStore)) {
|
||||||
// slow joystick movement
|
|
||||||
this.move(0, 0);
|
|
||||||
this.emit(hasMovedEventName, {
|
|
||||||
moving,
|
|
||||||
direction: this.previousDirection,
|
|
||||||
x: this.x,
|
|
||||||
y: this.y,
|
|
||||||
oldX: x,
|
|
||||||
oldY: y,
|
|
||||||
});
|
|
||||||
} else if (this.wasMoving && !moving) {
|
|
||||||
this.stop();
|
this.stop();
|
||||||
this.emit(hasMovedEventName, {
|
emit();
|
||||||
moving,
|
|
||||||
direction: this.previousDirection,
|
|
||||||
x: this.x,
|
|
||||||
y: this.y,
|
|
||||||
oldX: x,
|
|
||||||
oldY: y,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (direction !== null) {
|
// Update state
|
||||||
this.previousDirection = direction;
|
|
||||||
}
|
|
||||||
this.wasMoving = moving;
|
|
||||||
userMovingStore.set(moving);
|
userMovingStore.set(moving);
|
||||||
}
|
}
|
||||||
|
|
||||||
public isMoving(): boolean {
|
private computeFollowMovement(): number[] {
|
||||||
return this.wasMoving;
|
// Find followed WOKA and abort following if we lost it
|
||||||
|
const player = this.scene.MapPlayersByKey.get(get(followUsersStore)[0]);
|
||||||
|
if (!player) {
|
||||||
|
this.scene.connection?.emitFollowAbort();
|
||||||
|
followStateStore.set("off");
|
||||||
|
return [0, 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute movement direction
|
||||||
|
const xDistance = player.x - this.x;
|
||||||
|
const yDistance = player.y - this.y;
|
||||||
|
const distance = Math.pow(xDistance, 2) + Math.pow(yDistance, 2);
|
||||||
|
if (distance < 2000) {
|
||||||
|
return [0, 0];
|
||||||
|
}
|
||||||
|
const xMovement = xDistance / Math.sqrt(distance);
|
||||||
|
const yMovement = yDistance / Math.sqrt(distance);
|
||||||
|
return [xMovement, yMovement];
|
||||||
|
}
|
||||||
|
|
||||||
|
public enableFollowing() {
|
||||||
|
followStateStore.set("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
public moveUser(delta: number): void {
|
||||||
|
const activeEvents = this.userInputManager.getEventListForGameTick();
|
||||||
|
const state = get(followStateStore);
|
||||||
|
const role = get(followRoleStore);
|
||||||
|
|
||||||
|
if (activeEvents.get(UserInputEvent.Follow)) {
|
||||||
|
if (state === "off" && this.scene.groups.size > 0) {
|
||||||
|
followStateStore.set("requesting");
|
||||||
|
followRoleStore.set("leader");
|
||||||
|
} else if (state === "active") {
|
||||||
|
followStateStore.set("ending");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let x = 0;
|
||||||
|
let y = 0;
|
||||||
|
if ((state === "active" || state === "ending") && role === "follower") {
|
||||||
|
[x, y] = this.computeFollowMovement();
|
||||||
|
}
|
||||||
|
this.inputStep(activeEvents, x, y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ export enum UserInputEvent {
|
|||||||
MoveDown,
|
MoveDown,
|
||||||
SpeedUp,
|
SpeedUp,
|
||||||
Interact,
|
Interact,
|
||||||
|
Follow,
|
||||||
Shout,
|
Shout,
|
||||||
JoystickMove,
|
JoystickMove,
|
||||||
}
|
}
|
||||||
@ -147,6 +148,10 @@ export class UserInputManager {
|
|||||||
event: UserInputEvent.Interact,
|
event: UserInputEvent.Interact,
|
||||||
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE, false),
|
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE, false),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
event: UserInputEvent.Follow,
|
||||||
|
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.F, false),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
event: UserInputEvent.Shout,
|
event: UserInputEvent.Shout,
|
||||||
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.F, false),
|
keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.F, false),
|
||||||
|
90
front/src/Stores/FollowStore.ts
Normal file
90
front/src/Stores/FollowStore.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import { derived, writable } from "svelte/store";
|
||||||
|
import { getColorRgbFromHue } from "../WebRtc/ColorGenerator";
|
||||||
|
import { gameManager } from "../Phaser/Game/GameManager";
|
||||||
|
|
||||||
|
type FollowState = "off" | "requesting" | "active" | "ending";
|
||||||
|
type FollowRole = "leader" | "follower";
|
||||||
|
|
||||||
|
export const followStateStore = writable<FollowState>("off");
|
||||||
|
export const followRoleStore = writable<FollowRole>("leader");
|
||||||
|
|
||||||
|
function createFollowUsersStore() {
|
||||||
|
const { subscribe, update, set } = writable<number[]>([]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
addFollowRequest(leader: number): void {
|
||||||
|
followStateStore.set("requesting");
|
||||||
|
followRoleStore.set("follower");
|
||||||
|
set([leader]);
|
||||||
|
},
|
||||||
|
addFollower(user: number): void {
|
||||||
|
update((followers) => {
|
||||||
|
followers.push(user);
|
||||||
|
return followers;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Removes the follower from the store.
|
||||||
|
* Will update followStateStore and followRoleStore if nobody is following anymore.
|
||||||
|
* @param user
|
||||||
|
*/
|
||||||
|
removeFollower(user: number): void {
|
||||||
|
update((followers) => {
|
||||||
|
const oldFollowerCount = followers.length;
|
||||||
|
followers = followers.filter((id) => id !== user);
|
||||||
|
|
||||||
|
if (followers.length === 0 && oldFollowerCount > 0) {
|
||||||
|
followStateStore.set("off");
|
||||||
|
followRoleStore.set("leader");
|
||||||
|
}
|
||||||
|
|
||||||
|
return followers;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
stopFollowing(): void {
|
||||||
|
set([]);
|
||||||
|
followStateStore.set("off");
|
||||||
|
followRoleStore.set("leader");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const followUsersStore = createFollowUsersStore();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This store contains the color of the follow group. It is derived from the ID of the leader.
|
||||||
|
*/
|
||||||
|
export const followUsersColorStore = derived(
|
||||||
|
[followStateStore, followRoleStore, followUsersStore],
|
||||||
|
([$followStateStore, $followRoleStore, $followUsersStore]) => {
|
||||||
|
console.log($followStateStore);
|
||||||
|
if ($followStateStore !== "active") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($followUsersStore.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let leaderId: number;
|
||||||
|
if ($followRoleStore === "leader") {
|
||||||
|
// Let's get my ID by a quite complicated way....
|
||||||
|
leaderId = gameManager.getCurrentGameScene().connection?.getUserId() ?? 0;
|
||||||
|
} else {
|
||||||
|
leaderId = $followUsersStore[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's compute a random hue between 0 and 1 that varies enough to be interesting
|
||||||
|
const hue = ((leaderId * 197) % 255) / 255;
|
||||||
|
|
||||||
|
let { r, g, b } = getColorRgbFromHue(hue);
|
||||||
|
if ($followRoleStore === "follower") {
|
||||||
|
// Let's make the followers very slightly darker
|
||||||
|
r *= 0.9;
|
||||||
|
g *= 0.9;
|
||||||
|
b *= 0.9;
|
||||||
|
}
|
||||||
|
return (Math.round(r * 255) << 16) | (Math.round(g * 255) << 8) | Math.round(b * 255);
|
||||||
|
}
|
||||||
|
);
|
@ -1,5 +1,3 @@
|
|||||||
import { writable } from "svelte/store";
|
import { createMessageStore } from "./MessageStore";
|
||||||
|
|
||||||
export const banMessageVisibleStore = writable(false);
|
export const banMessageStore = createMessageStore();
|
||||||
|
|
||||||
export const banMessageContentStore = writable("");
|
|
||||||
|
29
front/src/Stores/TypeMessageStore/MessageStore.ts
Normal file
29
front/src/Stores/TypeMessageStore/MessageStore.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { writable } from "svelte/store";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
|
export interface Message {
|
||||||
|
id: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store that contains a list of messages to be displayed.
|
||||||
|
*/
|
||||||
|
export function createMessageStore() {
|
||||||
|
const { subscribe, update } = writable<Message[]>([]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
addMessage: (text: string): void => {
|
||||||
|
update((messages: Message[]) => {
|
||||||
|
return [...messages, { id: uuidv4(), text }];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
clearMessageById: (id: string): void => {
|
||||||
|
update((messages: Message[]) => {
|
||||||
|
messages = messages.filter((message) => message.id !== id);
|
||||||
|
return messages;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
@ -1,5 +1,3 @@
|
|||||||
import { writable } from "svelte/store";
|
import { createMessageStore } from "./MessageStore";
|
||||||
|
|
||||||
export const textMessageVisibleStore = writable(false);
|
export const textMessageStore = createMessageStore();
|
||||||
|
|
||||||
export const textMessageContentStore = writable("");
|
|
||||||
|
@ -1,13 +1,29 @@
|
|||||||
export function getRandomColor(): string {
|
export function getRandomColor(): string {
|
||||||
|
const { r, g, b } = getColorRgbFromHue(Math.random());
|
||||||
|
return toHexa(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toHexa(r: number, g: number, b: number): string {
|
||||||
|
return "#" + Math.floor(r * 256).toString(16) + Math.floor(g * 256).toString(16) + Math.floor(b * 256).toString(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getColorRgbFromHue(hue: number): { r: number; g: number; b: number } {
|
||||||
const golden_ratio_conjugate = 0.618033988749895;
|
const golden_ratio_conjugate = 0.618033988749895;
|
||||||
let hue = Math.random();
|
|
||||||
hue += golden_ratio_conjugate;
|
hue += golden_ratio_conjugate;
|
||||||
hue %= 1;
|
hue %= 1;
|
||||||
return hsv_to_rgb(hue, 0.5, 0.95);
|
return hsv_to_rgb(hue, 0.5, 0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stringToDouble(string: string): number {
|
||||||
|
let num = 1;
|
||||||
|
for (const char of string.split("")) {
|
||||||
|
num *= char.charCodeAt(0);
|
||||||
|
}
|
||||||
|
return (num % 255) / 255;
|
||||||
|
}
|
||||||
|
|
||||||
//todo: test this.
|
//todo: test this.
|
||||||
function hsv_to_rgb(hue: number, saturation: number, brightness: number): string {
|
function hsv_to_rgb(hue: number, saturation: number, brightness: number): { r: number; g: number; b: number } {
|
||||||
const h_i = Math.floor(hue * 6);
|
const h_i = Math.floor(hue * 6);
|
||||||
const f = hue * 6 - h_i;
|
const f = hue * 6 - h_i;
|
||||||
const p = brightness * (1 - saturation);
|
const p = brightness * (1 - saturation);
|
||||||
@ -48,5 +64,9 @@ function hsv_to_rgb(hue: number, saturation: number, brightness: number): string
|
|||||||
default:
|
default:
|
||||||
throw "h_i cannot be " + h_i;
|
throw "h_i cannot be " + h_i;
|
||||||
}
|
}
|
||||||
return "#" + Math.floor(r * 256).toString(16) + Math.floor(g * 256).toString(16) + Math.floor(b * 256).toString(16);
|
return {
|
||||||
|
r,
|
||||||
|
g,
|
||||||
|
b,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ describe("Interpolation / Extrapolation", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should should keep moving until it stops", () => {
|
it("should keep moving until it stops", () => {
|
||||||
const playerMovement = new PlayerMovement({
|
const playerMovement = new PlayerMovement({
|
||||||
x: 100, y: 200
|
x: 100, y: 200
|
||||||
}, 42000,
|
}, 42000,
|
||||||
@ -95,7 +95,7 @@ describe("Interpolation / Extrapolation", () => {
|
|||||||
oldX: 100,
|
oldX: 100,
|
||||||
oldY: 200,
|
oldY: 200,
|
||||||
direction: 'up',
|
direction: 'up',
|
||||||
moving: true
|
moving: false
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -120,6 +120,14 @@
|
|||||||
<a href="#" class="testLink" data-testmap="focusable_zone_map.json" target="_blank">Focusable Zones</a>
|
<a href="#" class="testLink" data-testmap="focusable_zone_map.json" target="_blank">Focusable Zones</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input type="radio" name="test-layer-visibility"> Success <input type="radio" name="test-layer-visibility"> Failure <input type="radio" name="test-layer-visibility" checked> Pending
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="#" class="testLink" data-testmap="layer-visibility.json" target="_blank">Layer visibility + Layer size and offset</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<h2>Iframe API</h2>
|
<h2>Iframe API</h2>
|
||||||
<table class="table">
|
<table class="table">
|
||||||
|
148
maps/tests/layer-visibility.json
Normal file
148
maps/tests/layer-visibility.json
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
{ "compressionlevel":-1,
|
||||||
|
"height":9,
|
||||||
|
"infinite":false,
|
||||||
|
"layers":[
|
||||||
|
{
|
||||||
|
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
|
"height":9,
|
||||||
|
"id":1,
|
||||||
|
"name":"floor",
|
||||||
|
"opacity":1,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":9,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data":[34, 34, 34, 0, 0, 0, 0, 0, 0, 34, 34, 34, 0, 0, 0, 0, 0, 0, 34, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"height":9,
|
||||||
|
"id":7,
|
||||||
|
"name":"invisible",
|
||||||
|
"opacity":1,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":false,
|
||||||
|
"width":9,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"height":9,
|
||||||
|
"id":2,
|
||||||
|
"name":"start",
|
||||||
|
"opacity":1,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":9,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data":[78, 79, 80, 89, 90, 91, 100, 101, 102],
|
||||||
|
"height":3,
|
||||||
|
"id":9,
|
||||||
|
"name":"smaller-offset",
|
||||||
|
"opacity":1,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":3,
|
||||||
|
"x":3,
|
||||||
|
"y":3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"draworder":"topdown",
|
||||||
|
"id":3,
|
||||||
|
"name":"floorLayer",
|
||||||
|
"objects":[
|
||||||
|
{
|
||||||
|
"height":113.62434712736,
|
||||||
|
"id":2,
|
||||||
|
"name":"",
|
||||||
|
"rotation":0,
|
||||||
|
"text":
|
||||||
|
{
|
||||||
|
"text":"The striped tiles in the top left corner should be invisible.",
|
||||||
|
"wrap":true
|
||||||
|
},
|
||||||
|
"type":"",
|
||||||
|
"visible":true,
|
||||||
|
"width":108.583142912147,
|
||||||
|
"x":4.76293503917236,
|
||||||
|
"y":3.18514798446498
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"height":196.249229945092,
|
||||||
|
"id":3,
|
||||||
|
"name":"",
|
||||||
|
"rotation":0,
|
||||||
|
"text":
|
||||||
|
{
|
||||||
|
"text":"The striped tiles in the top right corner should be transparent and appear above the player.",
|
||||||
|
"wrap":true
|
||||||
|
},
|
||||||
|
"type":"",
|
||||||
|
"visible":true,
|
||||||
|
"width":148.867058808759,
|
||||||
|
"x":137.442726119928,
|
||||||
|
"y":2.49946430962904
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"height":72.9975893933307,
|
||||||
|
"id":4,
|
||||||
|
"name":"",
|
||||||
|
"rotation":0,
|
||||||
|
"text":
|
||||||
|
{
|
||||||
|
"text":"The plant should be here (the center of the map)",
|
||||||
|
"wrap":true
|
||||||
|
},
|
||||||
|
"type":"",
|
||||||
|
"visible":true,
|
||||||
|
"width":151.609793508102,
|
||||||
|
"x":78.3025091653274,
|
||||||
|
"y":173.920383018616
|
||||||
|
}],
|
||||||
|
"opacity":1,
|
||||||
|
"type":"objectgroup",
|
||||||
|
"visible":true,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data":[0, 0, 0, 0, 0, 0, 34, 34, 34, 0, 0, 0, 0, 0, 0, 34, 34, 34, 0, 0, 0, 0, 0, 0, 34, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"height":9,
|
||||||
|
"id":8,
|
||||||
|
"name":"transparent",
|
||||||
|
"opacity":0.4,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":9,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
}],
|
||||||
|
"nextlayerid":10,
|
||||||
|
"nextobjectid":5,
|
||||||
|
"orientation":"orthogonal",
|
||||||
|
"renderorder":"right-down",
|
||||||
|
"tiledversion":"1.7.2",
|
||||||
|
"tileheight":32,
|
||||||
|
"tilesets":[
|
||||||
|
{
|
||||||
|
"columns":11,
|
||||||
|
"firstgid":1,
|
||||||
|
"image":"tileset1.png",
|
||||||
|
"imageheight":352,
|
||||||
|
"imagewidth":352,
|
||||||
|
"margin":0,
|
||||||
|
"name":"tileset1",
|
||||||
|
"spacing":0,
|
||||||
|
"tilecount":121,
|
||||||
|
"tileheight":32,
|
||||||
|
"tilewidth":32
|
||||||
|
}],
|
||||||
|
"tilewidth":32,
|
||||||
|
"type":"map",
|
||||||
|
"version":"1.6",
|
||||||
|
"width":9
|
||||||
|
}
|
@ -71,12 +71,12 @@ message ReportPlayerMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message EmotePromptMessage {
|
message EmotePromptMessage {
|
||||||
string emote = 2;
|
string emote = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message EmoteEventMessage {
|
message EmoteEventMessage {
|
||||||
int32 actorUserId = 1;
|
int32 actorUserId = 1;
|
||||||
string emote = 2;
|
string emote = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message QueryJitsiJwtMessage {
|
message QueryJitsiJwtMessage {
|
||||||
@ -84,6 +84,20 @@ message QueryJitsiJwtMessage {
|
|||||||
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!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message FollowRequestMessage {
|
||||||
|
int32 leader = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message FollowConfirmationMessage {
|
||||||
|
int32 leader = 1;
|
||||||
|
int32 follower = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message FollowAbortMessage {
|
||||||
|
int32 leader = 1;
|
||||||
|
int32 follower = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message ClientToServerMessage {
|
message ClientToServerMessage {
|
||||||
oneof message {
|
oneof message {
|
||||||
UserMovesMessage userMovesMessage = 2;
|
UserMovesMessage userMovesMessage = 2;
|
||||||
@ -99,6 +113,9 @@ message ClientToServerMessage {
|
|||||||
QueryJitsiJwtMessage queryJitsiJwtMessage = 12;
|
QueryJitsiJwtMessage queryJitsiJwtMessage = 12;
|
||||||
EmotePromptMessage emotePromptMessage = 13;
|
EmotePromptMessage emotePromptMessage = 13;
|
||||||
VariableMessage variableMessage = 14;
|
VariableMessage variableMessage = 14;
|
||||||
|
FollowRequestMessage followRequestMessage = 15;
|
||||||
|
FollowConfirmationMessage followConfirmationMessage = 16;
|
||||||
|
FollowAbortMessage followAbortMessage = 17;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,14 +260,14 @@ message SendUserMessage{
|
|||||||
message WorldFullWarningMessage{
|
message WorldFullWarningMessage{
|
||||||
}
|
}
|
||||||
message WorldFullWarningToRoomMessage{
|
message WorldFullWarningToRoomMessage{
|
||||||
string roomId = 1;
|
string roomId = 1;
|
||||||
}
|
}
|
||||||
message RefreshRoomPromptMessage{
|
message RefreshRoomPromptMessage{
|
||||||
string roomId = 1;
|
string roomId = 1;
|
||||||
}
|
}
|
||||||
message RefreshRoomMessage{
|
message RefreshRoomMessage{
|
||||||
string roomId = 1;
|
string roomId = 1;
|
||||||
int32 versionNumber = 2;
|
int32 versionNumber = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message WorldFullMessage{
|
message WorldFullMessage{
|
||||||
@ -292,6 +309,9 @@ message ServerToClientMessage {
|
|||||||
WorldConnexionMessage worldConnexionMessage = 18;
|
WorldConnexionMessage worldConnexionMessage = 18;
|
||||||
//EmoteEventMessage emoteEventMessage = 19;
|
//EmoteEventMessage emoteEventMessage = 19;
|
||||||
TokenExpiredMessage tokenExpiredMessage = 20;
|
TokenExpiredMessage tokenExpiredMessage = 20;
|
||||||
|
FollowRequestMessage followRequestMessage = 21;
|
||||||
|
FollowConfirmationMessage followConfirmationMessage = 22;
|
||||||
|
FollowAbortMessage followAbortMessage = 23;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,6 +398,9 @@ message PusherToBackMessage {
|
|||||||
BanUserMessage banUserMessage = 13;
|
BanUserMessage banUserMessage = 13;
|
||||||
EmotePromptMessage emotePromptMessage = 14;
|
EmotePromptMessage emotePromptMessage = 14;
|
||||||
VariableMessage variableMessage = 15;
|
VariableMessage variableMessage = 15;
|
||||||
|
FollowRequestMessage followRequestMessage = 16;
|
||||||
|
FollowConfirmationMessage followConfirmationMessage = 17;
|
||||||
|
FollowAbortMessage followAbortMessage = 18;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
# protobuf build
|
# protobuf build
|
||||||
FROM node:14-buster-slim as messages
|
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as messages
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
COPY messages .
|
COPY messages .
|
||||||
RUN yarn install && yarn proto
|
RUN yarn install && yarn proto
|
||||||
|
|
||||||
# typescript build
|
# typescript build
|
||||||
FROM node:14-buster-slim as builder
|
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
COPY pusher/yarn.lock pusher/package.json ./
|
COPY pusher/yarn.lock pusher/package.json ./
|
||||||
RUN yarn install
|
RUN yarn install
|
||||||
@ -17,7 +17,7 @@ ENV NODE_ENV=production
|
|||||||
RUN yarn run tsc
|
RUN yarn run tsc
|
||||||
|
|
||||||
# final production image
|
# final production image
|
||||||
FROM node:14-buster-slim
|
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
COPY pusher/yarn.lock pusher/package.json ./
|
COPY pusher/yarn.lock pusher/package.json ./
|
||||||
COPY --from=builder /usr/src/dist /usr/src/dist
|
COPY --from=builder /usr/src/dist /usr/src/dist
|
||||||
|
@ -17,6 +17,9 @@ import {
|
|||||||
ServerToClientMessage,
|
ServerToClientMessage,
|
||||||
CompanionMessage,
|
CompanionMessage,
|
||||||
EmotePromptMessage,
|
EmotePromptMessage,
|
||||||
|
FollowRequestMessage,
|
||||||
|
FollowConfirmationMessage,
|
||||||
|
FollowAbortMessage,
|
||||||
VariableMessage,
|
VariableMessage,
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import { UserMovesMessage } from "../Messages/generated/messages_pb";
|
import { UserMovesMessage } from "../Messages/generated/messages_pb";
|
||||||
@ -477,6 +480,18 @@ export class IoSocketController {
|
|||||||
client,
|
client,
|
||||||
message.getEmotepromptmessage() as EmotePromptMessage
|
message.getEmotepromptmessage() as EmotePromptMessage
|
||||||
);
|
);
|
||||||
|
} else if (message.hasFollowrequestmessage()) {
|
||||||
|
socketManager.handleFollowRequest(
|
||||||
|
client,
|
||||||
|
message.getFollowrequestmessage() as FollowRequestMessage
|
||||||
|
);
|
||||||
|
} else if (message.hasFollowconfirmationmessage()) {
|
||||||
|
socketManager.handleFollowConfirmation(
|
||||||
|
client,
|
||||||
|
message.getFollowconfirmationmessage() as FollowConfirmationMessage
|
||||||
|
);
|
||||||
|
} else if (message.hasFollowabortmessage()) {
|
||||||
|
socketManager.handleFollowAbort(client, message.getFollowabortmessage() as FollowAbortMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ok is false if backpressure was built up, wait for drain */
|
/* Ok is false if backpressure was built up, wait for drain */
|
||||||
|
@ -8,6 +8,9 @@ import {
|
|||||||
CharacterLayerMessage,
|
CharacterLayerMessage,
|
||||||
EmoteEventMessage,
|
EmoteEventMessage,
|
||||||
EmotePromptMessage,
|
EmotePromptMessage,
|
||||||
|
FollowRequestMessage,
|
||||||
|
FollowConfirmationMessage,
|
||||||
|
FollowAbortMessage,
|
||||||
GroupDeleteMessage,
|
GroupDeleteMessage,
|
||||||
ItemEventMessage,
|
ItemEventMessage,
|
||||||
JoinRoomMessage,
|
JoinRoomMessage,
|
||||||
@ -278,6 +281,24 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
this.handleViewport(client, viewport.toObject());
|
this.handleViewport(client, viewport.toObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleFollowRequest(client: ExSocketInterface, message: FollowRequestMessage): void {
|
||||||
|
const pusherToBackMessage = new PusherToBackMessage();
|
||||||
|
pusherToBackMessage.setFollowrequestmessage(message);
|
||||||
|
client.backConnection.write(pusherToBackMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFollowConfirmation(client: ExSocketInterface, message: FollowConfirmationMessage): void {
|
||||||
|
const pusherToBackMessage = new PusherToBackMessage();
|
||||||
|
pusherToBackMessage.setFollowconfirmationmessage(message);
|
||||||
|
client.backConnection.write(pusherToBackMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFollowAbort(client: ExSocketInterface, message: FollowAbortMessage): void {
|
||||||
|
const pusherToBackMessage = new PusherToBackMessage();
|
||||||
|
pusherToBackMessage.setFollowabortmessage(message);
|
||||||
|
client.backConnection.write(pusherToBackMessage);
|
||||||
|
}
|
||||||
|
|
||||||
onEmote(emoteMessage: EmoteEventMessage, listener: ExSocketInterface): void {
|
onEmote(emoteMessage: EmoteEventMessage, listener: ExSocketInterface): void {
|
||||||
const subMessage = new SubMessage();
|
const subMessage = new SubMessage();
|
||||||
subMessage.setEmoteeventmessage(emoteMessage);
|
subMessage.setEmoteeventmessage(emoteMessage);
|
||||||
|
@ -14,7 +14,7 @@ You'll need to adapt the `ADMIN_API_TOKEN` to the value you use in your `.env` f
|
|||||||
Alternatively, you can use docker-compose to run the tests:
|
Alternatively, you can use docker-compose to run the tests:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker-compose -f docker-compose.testcafe.yml up --exit-code-from testcafe
|
$ PROJECT_DIR=$(pwd) docker-compose -f docker-compose.testcafe.yml up --exit-code-from testcafe
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: by default, tests are running in Chrome locally and in Chromium in the Docker image.
|
Note: by default, tests are running in Chrome locally and in Chromium in the Docker image.
|
||||||
|
Loading…
Reference in New Issue
Block a user