FEAT: prototype game menu using html elements, better icon for text chat

This commit is contained in:
kharhamel 2020-12-04 11:30:35 +01:00
parent 855b55903b
commit 069f2f1cec
23 changed files with 520 additions and 226 deletions

BIN
front/dist/resources/fonts/ka1.ttf vendored Normal file

Binary file not shown.

34
front/dist/resources/html/gameMenu.html vendored Normal file
View File

@ -0,0 +1,34 @@
<style>
#gameMenu button {
background-color: black;
color: white;
border-radius: 7px;
}
#gameMenu section {
margin: 10px;
}
</style>
<div id="gameMenu">
<main>
<section>
<button id="shareButton">Share url</button>
</section>
<section>
<button id="sparkButton">Create map</button>
</section>
<section>
<button id="changeNameButton">Edit name</button>
</section>
<section>
<button id="changeSkinButton">Edit skin</button>
</section>
<section>
<button id="editGameSettingsButton">Settings</button>
</section>
<section id="adminConsoleSection" hidden>
<button id="adminConsoleButton">Admin console</button>
</section>
</main>
</div>

View File

@ -0,0 +1,16 @@
<style>
#menuIcon button {
background-color: black;
color: white;
border-radius: 7px;
}
#menuIcon section {
margin: 10px;
}
</style>
<main id="menuIcon">
<section>
<button id="openMenuButton">Menu</button>
</section>
</main>

View File

@ -0,0 +1,73 @@
<style>
#gameQuality {
background: #eceeee;
border: 1px solid #42464b;
border-radius: 6px;
height: 257px;
margin: 20px auto 0;
width: 298px;
}
#gameQuality .cautiousText {
font-size: 50%;
}
#gameQuality h1 {
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
border-bottom: 1px solid #a6abaf;
border-radius: 6px 6px 0 0;
box-sizing: border-box;
color: #727678;
display: block;
height: 43px;
padding-top: 10px;
margin: 0;
text-align: center;
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
}
#gameQuality select {
font-size: 70%;
background: linear-gradient(top, #d6d7d7, #dee0e0);
border: 1px solid #a1a3a3;
border-radius: 4px;
box-shadow: 0 1px #fff;
box-sizing: border-box;
color: #696969;
height: 30px;
transition: box-shadow 0.3s;
width: 240px;
}
#gameQuality section {
margin: 10px;
}
#gameQuality button {
margin-top: 10px;
background-color: black;
color: white;
border-radius: 7px;
}
</style>
<form id="gameQuality">
<section>
<h3>Game quality</h3>
<p class="cautiousText">(Editing this settings will restart the game)</p>
<select id="select-game-quality">
<option value="120">High video quality (120 fps)</option>
<option value="60">Medium video quality (60 fps, recommended)</option>
<option value="40">Minimum video quality (40 fps)</option>
<option value="20">Small video quality (20 fps)</option>
</select>
</section>
<section>
<h3>Video quality</h3>
<select id="select-video-quality">
<option value="30">High video quality (30 fps)</option>
<option value="20">Medium video quality (20 fps, recommended)</option>
<option value="10">Minimum video quality (10 fps)</option>
<option value="5">Small video quality (5 fps)</option>
</select>
</section>
<section>
<button type="submit" id="gameQualityFormSubmit">Save</button>
<button type="reset" id="gameQualityFormCancel">Cancel</button>
</section>
</form>

BIN
front/dist/resources/objects/talk.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@ -3,15 +3,12 @@ import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
import {RoomConnection} from "../Connexion/RoomConnection";
import {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
import {ADMIN_URL} from "../Enum/EnvironmentVariable";
import {mediaManager} from "../WebRtc/MediaManager";
export const CLASS_CONSOLE_MESSAGE = 'main-console';
export const INPUT_CONSOLE_MESSAGE = 'input-send-text';
export const UPLOAD_CONSOLE_MESSAGE = 'input-upload-music';
export const INPUT_TYPE_CONSOLE = 'input-type';
export const GAME_QUALITY_SELECT = 'select-game-quality';
export const VIDEO_QUALITY_SELECT = 'select-video-quality';
export const VIDEO_QUALITY_CONSOLE = 'video-quality';
export const AUDIO_TYPE = 'audio';
export const MESSAGE_TYPE = 'message';
@ -38,6 +35,7 @@ export class ConsoleGlobalMessageManager {
constructor(private Connection: RoomConnection, userInputManager : UserInputManager, private isAdmin: Boolean) {
this.buttonMainConsole = document.createElement('div');
this.buttonMainConsole.classList.add('console');
this.buttonMainConsole.hidden = true;
this.divMainConsole = document.createElement('div');
this.divMainConsole.className = CLASS_CONSOLE_MESSAGE;
this.divMessageConsole = document.createElement('div');
@ -49,6 +47,7 @@ export class ConsoleGlobalMessageManager {
this.buttonAdminMainConsole = document.createElement('img');
this.userInputManager = userInputManager;
this.initialise();
}
initialise() {
@ -118,9 +117,6 @@ export class ConsoleGlobalMessageManager {
}
});
/*const buttonText = document.createElement('p');
buttonText.innerText = 'Console';
this.buttonMainConsole.appendChild(buttonText);*/
this.divMessageConsole.appendChild(typeConsole);
if(this.isAdmin) {
@ -130,7 +126,6 @@ export class ConsoleGlobalMessageManager {
this.createUploadAudioPart();
}
this.buttonMainConsole.appendChild(this.buttonSettingsMainConsole);
this.createSettings();
this.divMainConsole.appendChild(this.buttonMainConsole);
this.divMainConsole.appendChild(this.divMessageConsole);
@ -263,92 +258,6 @@ export class ConsoleGlobalMessageManager {
this.divMessageConsole.appendChild(section);
}
createSettings(){
const labelGame = document.createElement('h1');
labelGame.innerText = "Game quality";
const selectGame = document.createElement('select');
selectGame.id = VIDEO_QUALITY_SELECT;
const option1 : HTMLOptionElement = document.createElement('option');
option1.value = '120';
option1.innerText = 'High video quality (120 fps)';
selectGame.appendChild(option1);
const option2 : HTMLOptionElement = document.createElement('option');
option2.value = '60';
option2.innerText = 'Medium video quality (60 fps, recommended)';
selectGame.appendChild(option2);
const option3 : HTMLOptionElement = document.createElement('option');
option3.value = '40';
option3.innerText = 'Minimum video quality (40 fps)';
selectGame.appendChild(option3);
const option4 : HTMLOptionElement = document.createElement('option');
option4.value = '20';
option4.innerText = 'Small video quality (20 fps)';
selectGame.appendChild(option4);
const labelVideo = document.createElement('h1');
labelVideo.innerText = "Video quality";
const selectVideo = document.createElement('select');
selectVideo.id = GAME_QUALITY_SELECT;
const optionVideo1 : HTMLOptionElement = document.createElement('option');
optionVideo1.value = '30';
optionVideo1.innerText = 'High video quality (30 fps)';
selectVideo.appendChild(optionVideo1);
const optionVideo2 : HTMLOptionElement = document.createElement('option');
optionVideo2.value = '20';
optionVideo2.innerText = 'Medium video quality (20 fps, recommended)';
selectVideo.appendChild(optionVideo2);
const optionVideo3 : HTMLOptionElement = document.createElement('option');
optionVideo3.value = '10';
optionVideo3.innerText = 'Minimum video quality (10 fps)';
selectVideo.appendChild(optionVideo3);
const optionVideo4 : HTMLOptionElement = document.createElement('option');
optionVideo4.value = '5';
optionVideo4.innerText = 'Small video quality (5 fps)';
selectVideo.appendChild(optionVideo4);
selectGame.value = '60';
const localQualityGame = localStorage.getItem(GAME_QUALITY_SELECT);
if(localQualityGame){
selectGame.value = localQualityGame;
}
selectVideo.value = '30';
const localQualityCam = localStorage.getItem(VIDEO_QUALITY_SELECT);
if(localQualityCam){
selectVideo.value = localQualityCam;
}
const divButtonAction = document.createElement('div');
divButtonAction.className = 'btn-action';
const buttonSave = document.createElement('button');
buttonSave.innerText = 'Save';
buttonSave.classList.add('btn');
buttonSave.addEventListener('click', () => {
this.saveSetting(selectGame.value, selectVideo.value);
this.disabledSettingConsole();
});
divButtonAction.appendChild(buttonSave);
const section = document.createElement('section');
section.id = this.getSectionId(VIDEO_QUALITY_CONSOLE);
section.appendChild(labelGame);
section.appendChild(selectGame);
section.appendChild(labelVideo);
section.appendChild(selectVideo);
section.appendChild(divButtonAction);
this.divSettingConsole.appendChild(section);
}
private static loadCss(): Promise<void> {
return new Promise<void>((resolve, reject) => {
if (ConsoleGlobalMessageManager.cssLoaded) {
@ -421,19 +330,6 @@ export class ConsoleGlobalMessageManager {
this.Connection.emitGlobalMessage(GlobalMessage);
}
private saveSetting(valueGame: string, valueVideo: string){
const previousGameValue = localStorage.getItem(GAME_QUALITY_SELECT);
if(!previousGameValue || previousGameValue !== valueGame) {
localStorage.setItem(GAME_QUALITY_SELECT, valueGame);
window.location.reload();
}
const previousVideoValue = localStorage.getItem(VIDEO_QUALITY_SELECT);
if(!previousVideoValue || previousVideoValue !== valueVideo) {
localStorage.setItem(VIDEO_QUALITY_SELECT, valueVideo);
mediaManager.updateCameraQuality(parseInt(valueVideo));
}
}
active(){
this.userInputManager.clearAllInputKeyboard();
this.divMainConsole.style.top = '0';
@ -453,12 +349,14 @@ export class ConsoleGlobalMessageManager {
}
this.active();
this.divMessageConsole.classList.add('active');
this.buttonMainConsole.hidden = false;
this.buttonSendMainConsole.classList.add('active');
}
disabledMessageConsole(){
this.activeMessage = false;
this.disabled();
this.buttonMainConsole.hidden = false;
this.divMessageConsole.classList.remove('active');
this.buttonSendMainConsole.classList.remove('active');
}

View File

@ -55,8 +55,7 @@ class ConnectionManager {
} else {
roomId = window.location.pathname + window.location.hash;
}
const room = new Room(roomId);
return Promise.resolve(room);
return Promise.resolve(new Room(roomId));
}
return Promise.reject('Invalid URL');

View File

@ -1,5 +1,9 @@
import {LocalUser} from "./LocalUser";
const characterLayersKey = 'characterLayers';
const gameQualityKey = 'gameQuality';
const videoQualityKey = 'videoQuality';
//todo: add localstorage fallback
class LocalUserStore {
@ -31,6 +35,27 @@ class LocalUserStore {
getCustomCursorPosition(): {activeRow:number, selectedLayers:number[]}|null {
return JSON.parse(window.localStorage.getItem('customCursorPosition') || "null");
}
setCharacterLayers(layers: string[]): void {
window.localStorage.setItem(characterLayersKey, JSON.stringify(layers));
}
getCharacterLayers(): string[]|null {
return JSON.parse(window.localStorage.getItem(characterLayersKey) || "null");
}
getGameQualityValue(): number {
return parseInt(window.localStorage.getItem(gameQualityKey) || '') || 60;
}
setGameQualityValue(value: number): void {
localStorage.setItem(gameQualityKey, '' + value);
}
getVideoQualityValue(): number {
return parseInt(window.localStorage.getItem(videoQualityKey) || '') || 20;
}
setVideoQualityValue(value: number): void {
localStorage.setItem(videoQualityKey, '' + value);
}
}
export const localUserStore = new LocalUserStore();

View File

@ -583,4 +583,8 @@ export class RoomConnection implements RoomConnection {
public hasTag(tag: string): boolean {
return this.tags.includes(tag);
}
public isAdmin(): boolean {
return this.hasTag('admin');
}
}

View File

@ -0,0 +1,11 @@
export class ChatModeIcon extends Phaser.GameObjects.Sprite {
constructor(scene: Phaser.Scene, x: number, y: number) {
super(scene, x, y, 'layout_modes', 3);
scene.add.existing(this);
this.setScrollFactor(0, 0);
this.setOrigin(0, 1);
this.setInteractive();
this.setVisible(false);
this.setDepth(99999);
}
}

View File

@ -0,0 +1,18 @@
import {discussionManager} from "../../WebRtc/DiscussionManager";
export const openChatIconName = 'openChatIcon';
export class OpenChatIcon extends Phaser.GameObjects.Image {
constructor(scene: Phaser.Scene, x: number, y: number) {
super(scene, x, y, openChatIconName);
scene.add.existing(this);
this.setScrollFactor(0, 0);
this.setOrigin(0, 1);
this.displayWidth = 30;
this.displayHeight = 30;
this.setInteractive();
this.setVisible(false)
this.setDepth(99999);
this.on("pointerup", () => discussionManager.showDiscussionPart());
}
}

View File

@ -0,0 +1,11 @@
export class PresentationModeIcon extends Phaser.GameObjects.Sprite {
constructor(scene: Phaser.Scene, x: number, y: number) {
super(scene, x, y, 'layout_modes', 0);
scene.add.existing(this);
this.setScrollFactor(0, 0);
this.setOrigin(0, 1);
this.setInteractive();
this.setVisible(false);
this.setDepth(99999);
}
}

View File

@ -1,6 +1,11 @@
import {GameScene} from "./GameScene";
import {connectionManager} from "../../Connexion/ConnectionManager";
import {Room} from "../../Connexion/Room";
import {MenuSceneName} from "../Menu/MenuScene";
import {LoginSceneName} from "../Login/LoginScene";
import {SelectCharacterSceneName} from "../Login/SelectCharacterScene";
import {EnableCameraSceneName} from "../Login/EnableCameraScene";
import {localUserStore} from "../../Connexion/LocalUserStore";
export interface HasMovedEvent {
direction: string;
@ -9,29 +14,48 @@ export interface HasMovedEvent {
y: number;
}
/**
* This class should be responsible for any scene starting/stopping
*/
export class GameManager {
private playerName!: string;
private characterLayers!: string[];
private playerName: string|null;
private characterLayers: string[]|null;
private startRoom!:Room;
currentSceneName: string|null = null;
public async init(scenePlugin: Phaser.Scenes.ScenePlugin) {
constructor() {
this.playerName = localUserStore.getName();
this.characterLayers = localUserStore.getCharacterLayers();
}
public async init(scenePlugin: Phaser.Scenes.ScenePlugin): Promise<string> {
this.startRoom = await connectionManager.initGameConnexion();
await this.loadMap(this.startRoom, scenePlugin);
if (!this.playerName) {
return LoginSceneName;
} else if (!this.characterLayers) {
return SelectCharacterSceneName;
} else {
return EnableCameraSceneName;
}
}
public setPlayerName(name: string): void {
this.playerName = name;
localUserStore.setName(name);
}
public setCharacterLayers(layers: string[]): void {
this.characterLayers = layers;
localUserStore.setCharacterLayers(layers);
}
getPlayerName(): string {
getPlayerName(): string|null {
return this.playerName;
}
getCharacterSelected(): string[] {
getCharacterLayers(): string[]|null {
return this.characterLayers;
}
@ -48,7 +72,28 @@ export class GameManager {
}
public goToStartingMap(scenePlugin: Phaser.Scenes.ScenePlugin): void {
scenePlugin.start(this.startRoom.id);
console.log('starting '+ (this.currentSceneName || this.startRoom.id))
scenePlugin.start(this.currentSceneName || this.startRoom.id);
//the menu scene launches faster than the gameScene, so we delay it to not have menu buttons on a black screen
setTimeout(() => scenePlugin.launch(MenuSceneName), 1000);
}
/**
* Temporary leave a gameScene to go back to the loginScene for example.
* This will close the socket connections and stop the gameScene, but won't remove it.
*/
leaveGame(scene: Phaser.Scene, targetSceneName: string): void {
if (this.currentSceneName === null) throw 'No current scene id set!';
const gameScene: GameScene = scene.scene.get(this.currentSceneName) as GameScene;
gameScene.cleanupClosingScene();
scene.scene.stop(this.currentSceneName);
scene.scene.stop(MenuSceneName);
scene.scene.run(targetSceneName);
}
public getCurrentGameScene(scene: Phaser.Scene): GameScene {
if (this.currentSceneName === null) throw 'No current scene id set!';
return scene.scene.get(this.currentSceneName) as GameScene
}
}

View File

@ -1,4 +1,4 @@
import {GameManager, gameManager, HasMovedEvent} from "./GameManager";
import {gameManager, HasMovedEvent} from "./GameManager";
import {
GroupCreatedUpdatedMessageInterface,
MessageUserJoined,
@ -60,6 +60,9 @@ import {ResizableScene} from "../Login/ResizableScene";
import {Room} from "../../Connexion/Room";
import {jitsiFactory} from "../../WebRtc/JitsiFactory";
import {urlManager} from "../../Url/UrlManager";
import {PresentationModeIcon} from "../Components/PresentationModeIcon";
import {ChatModeIcon} from "../Components/ChatModeIcon";
import {OpenChatIcon, openChatIconName} from "../Components/OpenChatIcon";
export interface GameSceneInitInterface {
initPosition: PointInterface|null,
@ -99,7 +102,6 @@ interface DeleteGroupEventInterface {
const defaultStartLayerName = 'start';
export class GameScene extends ResizableScene implements CenterListener {
GameManager : GameManager;
Terrains : Array<Phaser.Tilemaps.Tileset>;
CurrentPlayer!: CurrentGamerInterface;
MapPlayers!: Phaser.Physics.Arcade.Group;
@ -116,11 +118,11 @@ export class GameScene extends ResizableScene implements CenterListener {
pendingEvents: Queue<InitUserPositionEventInterface|AddPlayerEventInterface|RemovePlayerEventInterface|UserMovedEventInterface|GroupCreatedUpdatedEventInterface|DeleteGroupEventInterface> = new Queue<InitUserPositionEventInterface|AddPlayerEventInterface|RemovePlayerEventInterface|UserMovedEventInterface|GroupCreatedUpdatedEventInterface|DeleteGroupEventInterface>();
private initPosition: PositionInterface|null = null;
private playersPositionInterpolator = new PlayersPositionInterpolator();
private connection!: RoomConnection;
public connection!: RoomConnection;
private simplePeer!: SimplePeer;
private GlobalMessageManager!: GlobalMessageManager;
private UserMessageManager!: UserMessageManager;
private ConsoleGlobalMessageManager!: ConsoleGlobalMessageManager;
public ConsoleGlobalMessageManager!: ConsoleGlobalMessageManager;
private connectionAnswerPromise: Promise<RoomJoinedMessageInterface>;
private connectionAnswerPromiseResolve!: (value?: RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface>) => void;
// A promise that will resolve when the "create" method is called (signaling loading is ended)
@ -149,17 +151,19 @@ export class GameScene extends ResizableScene implements CenterListener {
private userInputManager!: UserInputManager;
private isReconnecting: boolean = false;
private startLayerName!: string | null;
private openChatIcon!: OpenChatIcon;
private playerName!: string;
private characterLayers!: string[];
constructor(private room: Room, MapUrlFile: string) {
super({
key: room.id
});
this.GameManager = gameManager;
this.Terrains = [];
this.groups = new Map<number, Sprite>();
this.instance = room.getInstance();
this.MapUrlFile = MapUrlFile;
this.RoomId = room.id;
@ -173,6 +177,7 @@ export class GameScene extends ResizableScene implements CenterListener {
//hook preload scene
preload(): void {
this.load.image(openChatIconName, 'resources/objects/talk.png');
this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => {
this.scene.start(FourOFourSceneName, {
file: file.src
@ -306,9 +311,22 @@ export class GameScene extends ResizableScene implements CenterListener {
//hook create scene
create(): void {
gameManager.currentSceneName = this.scene.key;
urlManager.pushRoomIdToUrl(this.room);
this.startLayerName = urlManager.getStartLayerNameFromUrl();
const playerName = gameManager.getPlayerName();
if (!playerName) {
throw 'playerName is not set';
}
this.playerName = playerName;
const characterLayers = gameManager.getCharacterLayers();
if (!characterLayers) {
throw 'characterLayers are not set';
}
this.characterLayers = characterLayers;
//initalise map
this.Map = this.add.tilemap(this.MapUrlFile);
this.gameMap = new GameMap(this.mapFile);
@ -415,23 +433,14 @@ export class GameScene extends ResizableScene implements CenterListener {
this.outlinedItem?.activate();
});
this.presentationModeSprite = this.add.sprite(2, this.game.renderer.height - 2, 'layout_modes', 0);
this.presentationModeSprite.setScrollFactor(0, 0);
this.presentationModeSprite.setOrigin(0, 1);
this.presentationModeSprite.setInteractive();
this.presentationModeSprite.setVisible(false);
this.presentationModeSprite.setDepth(99999);
this.presentationModeSprite = new PresentationModeIcon(this, 36, this.game.renderer.height - 2);
this.presentationModeSprite.on('pointerup', this.switchLayoutMode.bind(this));
this.chatModeSprite = this.add.sprite(36, this.game.renderer.height - 2, 'layout_modes', 3);
this.chatModeSprite.setScrollFactor(0, 0);
this.chatModeSprite.setOrigin(0, 1);
this.chatModeSprite.setInteractive();
this.chatModeSprite.setVisible(false);
this.chatModeSprite.setDepth(99999);
this.chatModeSprite = new ChatModeIcon(this, 70, this.game.renderer.height - 2);
this.chatModeSprite.on('pointerup', this.switchLayoutMode.bind(this));
this.openChatIcon = new OpenChatIcon(this, 2, this.game.renderer.height - 36)
// FIXME: change this to use the UserInputManager class for input
this.input.keyboard.on('keyup-' + 'M', () => {
this.input.keyboard.on('keyup-M', () => {
this.switchLayoutMode();
});
@ -445,8 +454,8 @@ export class GameScene extends ResizableScene implements CenterListener {
connectionManager.connectToRoomSocket(
this.RoomId,
gameManager.getPlayerName(),
gameManager.getCharacterSelected(),
this.playerName,
this.characterLayers,
{
x: this.startX,
y: this.startY
@ -459,10 +468,6 @@ export class GameScene extends ResizableScene implements CenterListener {
}).then((onConnect: OnConnectInterface) => {
this.connection = onConnect.connection;
//this.connection.emitPlayerDetailsMessage(gameManager.getPlayerName(), gameManager.getCharacterSelected())
/*this.connection.onStartRoom((roomJoinedMessage: RoomJoinedMessageInterface) => {
});*/
this.connection.onUserJoins((message: MessageUserJoined) => {
const userMessage: AddPlayerInterface = {
userId: message.userId,
@ -493,11 +498,13 @@ export class GameScene extends ResizableScene implements CenterListener {
this.connection.onGroupUpdatedOrCreated((groupPositionMessage: GroupCreatedUpdatedMessageInterface) => {
this.shareGroupPosition(groupPositionMessage);
this.openChatIcon.setVisible(true);
})
this.connection.onGroupDeleted((groupId: number) => {
try {
this.deleteGroup(groupId);
this.openChatIcon.setVisible(false);
} catch (e) {
console.error(e);
}
@ -541,7 +548,7 @@ export class GameScene extends ResizableScene implements CenterListener {
});
// When connection is performed, let's connect SimplePeer
this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic, this.GameManager.getPlayerName());
this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic, this.playerName);
this.GlobalMessageManager = new GlobalMessageManager(this.connection);
this.UserMessageManager = new UserMessageManager(this.connection);
@ -569,7 +576,7 @@ export class GameScene extends ResizableScene implements CenterListener {
//this.initUsersPosition(roomJoinedMessage.users);
this.connectionAnswerPromiseResolve(onConnect.room);
// Analyze tags to find if we are admin. If yes, show console.
this.ConsoleGlobalMessageManager = new ConsoleGlobalMessageManager(this.connection, this.userInputManager, this.connection.hasTag('admin'));
this.ConsoleGlobalMessageManager = new ConsoleGlobalMessageManager(this.connection, this.userInputManager, this.connection.isAdmin());
this.scene.wake();
@ -649,9 +656,7 @@ export class GameScene extends ResizableScene implements CenterListener {
if (!roomId) throw new Error('Could not find the room from its exit key: '+exitKey);
urlManager.pushStartLayerNameToUrl(hash);
if (roomId !== this.scene.key) {
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
this.connection.closeConnection();
this.simplePeer.unregister();
this.cleanupClosingScene();
this.scene.stop();
this.scene.remove(this.scene.key);
this.scene.start(roomId);
@ -663,6 +668,12 @@ export class GameScene extends ResizableScene implements CenterListener {
}
}
public cleanupClosingScene(): void {
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
this.connection.closeConnection();
this.simplePeer.unregister();
}
private switchLayoutMode(): void {
//if discussion is activated, this layout cannot be activated
if(mediaManager.activatedDiscussion){
@ -811,8 +822,8 @@ export class GameScene extends ResizableScene implements CenterListener {
this,
this.startX,
this.startY,
this.GameManager.getPlayerName(),
this.GameManager.getCharacterSelected(),
this.playerName,
this.characterLayers,
PlayerAnimationNames.WalkDown,
false,
this.userInputManager
@ -1174,7 +1185,7 @@ export class GameScene extends ResizableScene implements CenterListener {
}
public startJitsi(roomName: string, jwt?: string): void {
jitsiFactory.start(roomName, gameManager.getPlayerName(), jwt);
jitsiFactory.start(roomName, this.playerName, jwt);
this.connection.setSilent(true);
mediaManager.hideGameOverlay();
@ -1209,4 +1220,6 @@ export class GameScene extends ResizableScene implements CenterListener {
});
}))
}
}

View File

@ -9,6 +9,8 @@ import {gameManager} from "../Game/GameManager";
import {ResizableScene} from "./ResizableScene";
import {localUserStore} from "../../Connexion/LocalUserStore";
import {PlayerResourceDescriptionInterface} from "../Entity/Character";
import {SelectCharacterSceneName} from "./SelectCharacterScene";
import {LoginSceneName} from "./LoginScene";
export const CustomizeSceneName = "CustomizeScene";
@ -120,7 +122,8 @@ export class CustomizeScene extends ResizableScene {
gameManager.setCharacterLayers(layers);
return this.scene.start(EnableCameraSceneName);
this.scene.sleep(CustomizeSceneName)
this.scene.run(EnableCameraSceneName);
});
this.input.keyboard.on('keydown-RIGHT', () => this.moveCursorHorizontally(1));

View File

@ -263,6 +263,7 @@ export class EnableCameraScene extends Phaser.Scene {
mediaManager.stopCamera();
mediaManager.stopMicrophone();
this.scene.sleep(EnableCameraSceneName)
gameManager.goToStartingMap(this.scene);
}

View File

@ -1,13 +1,4 @@
import {gameManager} from "../Game/GameManager";
import {TextField} from "../Components/TextField";
import {TextInput} from "../Components/TextInput";
import {ClickButton} from "../Components/ClickButton";
import Image = Phaser.GameObjects.Image;
import Rectangle = Phaser.GameObjects.Rectangle;
import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "../Entity/Character";
import {cypressAsserter} from "../../Cypress/CypressAsserter";
import {SelectCharacterSceneName} from "./SelectCharacterScene";
import {ResizableScene} from "./ResizableScene";
import {Scene} from "phaser";
import {LoginSceneName} from "./LoginScene";
import {FourOFourSceneName} from "../Reconnecting/FourOFourScene";
@ -25,12 +16,9 @@ export class EntryScene extends Scene {
});
}
preload() {
}
create() {
gameManager.init(this.scene).then(() => {
this.scene.start(LoginSceneName);
gameManager.init(this.scene).then((nextSceneName) => {
this.scene.start(nextSceneName);
}).catch((err) => {
console.error(err)
this.scene.start(FourOFourSceneName, {

View File

@ -1,14 +1,11 @@
import {gameManager} from "../Game/GameManager";
import {TextField} from "../Components/TextField";
import {TextInput} from "../Components/TextInput";
import {ClickButton} from "../Components/ClickButton";
import Image = Phaser.GameObjects.Image;
import Rectangle = Phaser.GameObjects.Rectangle;
import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "../Entity/Character";
import {cypressAsserter} from "../../Cypress/CypressAsserter";
import {SelectCharacterSceneName} from "./SelectCharacterScene";
import {ResizableScene} from "./ResizableScene";
import {localUserStore} from "../../Connexion/LocalUserStore";
//todo: put this constants in a dedicated file
export const LoginSceneName = "LoginScene";
@ -29,7 +26,7 @@ export class LoginScene extends ResizableScene {
super({
key: LoginSceneName
});
this.name = localUserStore.getName();
this.name = gameManager.getPlayerName() || '';
}
preload() {
@ -55,7 +52,6 @@ export class LoginScene extends ResizableScene {
this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Enter your name:');
this.nameInput = new TextInput(this, this.game.renderer.width / 2, 70, 8, this.name,(text: string) => {
this.name = text;
localUserStore.setName(text);
});
this.pressReturnField = new TextField(this, this.game.renderer.width / 2, 130, 'Press enter to start');
@ -87,7 +83,8 @@ export class LoginScene extends ResizableScene {
private login(name: string): void {
gameManager.setPlayerName(name);
this.scene.start(SelectCharacterSceneName);
this.scene.sleep(LoginSceneName)
this.scene.run(SelectCharacterSceneName);
}
public onResize(ev: UIEvent): void {

View File

@ -116,11 +116,12 @@ export class SelectCharacterScene extends ResizableScene {
}
private nextScene(): void {
this.scene.sleep(SelectCharacterSceneName);
if (this.selectedPlayer !== null) {
gameManager.setCharacterLayers([this.selectedPlayer.texture.key]);
this.scene.start(EnableCameraSceneName);
this.scene.run(EnableCameraSceneName);
} else {
this.scene.start(CustomizeSceneName);
this.scene.run(CustomizeSceneName);
}
}

View File

@ -0,0 +1,189 @@
import {LoginSceneName} from "../Login/LoginScene";
import {SelectCharacterSceneName} from "../Login/SelectCharacterScene";
import {gameManager} from "../Game/GameManager";
import {localUserStore} from "../../Connexion/LocalUserStore";
import {mediaManager} from "../../WebRtc/MediaManager";
export const MenuSceneName = 'MenuScene';
const gameMenuKey = 'gameMenu';
const gameMenuIconKey = 'gameMenuIcon';
const gameSettingsMenuKey = 'gameSettingsMenu';
const closedSideMenuX = -200;
const openedSideMenuX = 0;
/**
* The scene that manages the game menu, rendered using a DOM element.
*/
export class MenuScene extends Phaser.Scene {
private menuElement!: Phaser.GameObjects.DOMElement;
private gameQualityMenuElement!: Phaser.GameObjects.DOMElement;
private sideMenuOpened = false;
private settingsMenuOpened = false;
private gameQualityValue: number;
private videoQualityValue: number;
private menuButton!: Phaser.GameObjects.DOMElement;
constructor() {
super({key: MenuSceneName});
this.gameQualityValue = localUserStore.getGameQualityValue();
this.videoQualityValue = localUserStore.getVideoQualityValue();
}
preload () {
this.load.html(gameMenuKey, 'resources/html/gameMenu.html');
this.load.html(gameMenuIconKey, 'resources/html/gameMenuIcon.html');
this.load.html(gameSettingsMenuKey, 'resources/html/gameQualityMenu.html');
}
create() {
this.menuElement = this.add.dom(closedSideMenuX, 25).createFromCache(gameMenuKey);
this.menuElement.setOrigin(0);
this.gameQualityMenuElement = this.add.dom(this.game.renderer.width / 2, -400).createFromCache(gameSettingsMenuKey);
this.gameQualityMenuElement.setOrigin(0.5);
this.input.keyboard.on('keyup-TAB', () => {
this.sideMenuOpened ? this.closeSideMenu() : this.openSideMenu();
});
this.menuButton = this.add.dom(35, 20).createFromCache(gameMenuIconKey);
this.menuButton.addListener('click');
this.menuButton.on('click', () => {
this.sideMenuOpened ? this.closeSideMenu() : this.openSideMenu();
});
}
openSideMenu() {
if (this.sideMenuOpened) return;
this.sideMenuOpened = true;
this.menuButton.getChildByID('openMenuButton').innerHTML = 'Close'
if (gameManager.getCurrentGameScene(this).connection.isAdmin()) {
const adminSection = this.menuElement.getChildByID('adminConsoleSection') as HTMLElement;
adminSection.hidden = false;
}
this.menuElement.addListener('click');
this.menuElement.on('click', this.onMenuClick.bind(this));
this.tweens.add({
targets: this.menuElement,
x: openedSideMenuX,
duration: 500,
ease: 'Power3'
});
}
private closeSideMenu(): void {
if (!this.sideMenuOpened) return;
this.sideMenuOpened = false;
this.closeGameQualityMenu()
this.menuButton.getChildByID('openMenuButton').innerHTML = 'Menu'
this.tweens.add({
targets: this.menuElement,
x: closedSideMenuX,
duration: 500,
ease: 'Power3'
});
this.menuElement.removeListener('click');
}
private openGameSettingsMenu(): void {
if (this.settingsMenuOpened) return;
this.settingsMenuOpened = true;
const gameQualitySelect = this.gameQualityMenuElement.getChildByID('select-game-quality') as HTMLInputElement;
gameQualitySelect.value = ''+this.gameQualityValue;
const videoQualitySelect = this.gameQualityMenuElement.getChildByID('select-video-quality') as HTMLInputElement;
videoQualitySelect.value = ''+this.videoQualityValue;
this.gameQualityMenuElement.addListener('click');
this.gameQualityMenuElement.on('click', (event:MouseEvent) => {
event.preventDefault();
if ((event?.target as HTMLInputElement).id === 'gameQualityFormSubmit') {
const gameQualitySelect = this.gameQualityMenuElement.getChildByID('select-game-quality') as HTMLInputElement;
const videoQualitySelect = this.gameQualityMenuElement.getChildByID('select-video-quality') as HTMLInputElement;
this.saveSetting(parseInt(gameQualitySelect.value), parseInt(videoQualitySelect.value));
} else if((event?.target as HTMLInputElement).id === 'gameQualityFormCancel') {
this.closeGameQualityMenu();
}
});
this.tweens.add({
targets: this.gameQualityMenuElement,
y: this.game.renderer.height / 2,
duration: 1000,
ease: 'Power3'
});
}
private closeGameQualityMenu(): void {
if (!this.settingsMenuOpened) return;
this.settingsMenuOpened = false;
this.gameQualityMenuElement.removeListener('click');
this.tweens.add({
targets: this.gameQualityMenuElement,
y: -400,
duration: 1000,
ease: 'Power3'
});
}
private onMenuClick(event:MouseEvent) {
event.preventDefault();
switch ((event?.target as HTMLInputElement).id) {
case 'changeNameButton':
this.closeSideMenu();
this.closeGameQualityMenu();
gameManager.leaveGame(this, LoginSceneName);
break;
case 'sparkButton':
this.goToSpark();
break;
case 'changeSkinButton':
this.closeSideMenu();
gameManager.leaveGame(this, SelectCharacterSceneName);
break;
case 'closeButton':
this.closeSideMenu();
break;
case 'shareButton':
this.shareUrl();
break;
case 'editGameSettingsButton':
this.openGameSettingsMenu();
break;
case 'adminConsoleButton':
gameManager.getCurrentGameScene(this).ConsoleGlobalMessageManager.activeMessageConsole();
break;
}
}
private async shareUrl() {
await navigator.clipboard.writeText(location.toString());
alert('URL was copy to your clipboard!');
}
private saveSetting(valueGame: number, valueVideo: number){
if (valueGame !== this.gameQualityValue) {
this.gameQualityValue = valueGame;
localUserStore.setGameQualityValue(valueGame);
window.location.reload();
}
if (valueVideo !== this.videoQualityValue) {
this.videoQualityValue = valueVideo;
localUserStore.setVideoQualityValue(valueVideo);
mediaManager.updateCameraQuality(valueVideo);
}
this.closeGameQualityMenu();
}
private goToSpark() {
const sparkHost = 'https://'+window.location.host.replace('play.', 'admin.')+'/register';
window.location.assign(sparkHost);
}
}

View File

@ -1,5 +1,5 @@
import {HtmlUtils} from "./HtmlUtils";
import {MediaManager, ReportCallback, UpdatedLocalStreamCallback} from "./MediaManager";
import {mediaManager, ReportCallback} from "./MediaManager";
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
import {connectionManager} from "../Connexion/ConnectionManager";
import {GameConnexionTypes} from "../Url/UrlManager";
@ -13,7 +13,6 @@ export class DiscussionManager {
private divParticipants?: HTMLDivElement;
private nbpParticipants?: HTMLParagraphElement;
private divMessages?: HTMLParagraphElement;
private buttonActiveDiscussion?: HTMLButtonElement;
private participants: Map<number|string, HTMLDivElement> = new Map<number|string, HTMLDivElement>();
@ -23,9 +22,9 @@ export class DiscussionManager {
private userInputManager?: UserInputManager;
constructor(private mediaManager: MediaManager, name: string) {
constructor() {
this.mainContainer = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
this.createDiscussPart(name);
this.createDiscussPart(''); //todo: why do we always use empty string?
}
private createDiscussPart(name: string) {
@ -33,20 +32,12 @@ export class DiscussionManager {
this.divDiscuss.classList.add('discussion');
const buttonCloseDiscussion: HTMLButtonElement = document.createElement('button');
this.buttonActiveDiscussion = document.createElement('button');
buttonCloseDiscussion.classList.add('close-btn');
buttonCloseDiscussion.innerHTML = `<img src="resources/logos/close.svg"/>`;
buttonCloseDiscussion.addEventListener('click', () => {
this.hideDiscussion();
this.showButtonDiscussionBtn();
});
this.buttonActiveDiscussion.classList.add('active-btn');
this.buttonActiveDiscussion.innerHTML = `<img src="resources/logos/discussion.svg"/>`;
this.buttonActiveDiscussion.addEventListener('click', () => {
this.showDiscussionPart();
});
this.divDiscuss.appendChild(buttonCloseDiscussion);
this.divDiscuss.appendChild(this.buttonActiveDiscussion);
const myName: HTMLParagraphElement = document.createElement('p');
myName.innerText = name.toUpperCase();
@ -128,7 +119,7 @@ export class DiscussionManager {
reportBanUserAction.innerText = 'Report';
reportBanUserAction.addEventListener('click', () => {
if(reportCallback) {
this.mediaManager.showReportModal(`${userId}`, name ?? '', reportCallback);
mediaManager.showReportModal(`${userId}`, name ?? '', reportCallback);
}else{
console.info('report feature is not activated!');
}
@ -139,7 +130,6 @@ export class DiscussionManager {
this.divParticipants?.appendChild(divParticipant);
this.participants.set(userId, divParticipant);
this.showButtonDiscussionBtn();
this.updateParticipant(this.participants.size);
}
@ -184,9 +174,6 @@ export class DiscussionManager {
this.participants.delete(userId);
}
//if all participant leave, hide discussion button
if(this.participants.size === 1){
this.hideButtonDiscussionBtn();
}
this.sendMessageCallBack.delete(userId);
}
@ -199,14 +186,6 @@ export class DiscussionManager {
return this.activeDiscussion;
}
private showButtonDiscussionBtn(){
//if it's first participant, show discussion button
if(this.activatedDiscussion || this.participants.size === 1) {
return;
}
this.buttonActiveDiscussion?.classList.add('active');
}
private showDiscussion(){
this.activeDiscussion = true;
if(this.userInputManager) {
@ -223,16 +202,13 @@ export class DiscussionManager {
this.divDiscuss?.classList.remove('active');
}
private hideButtonDiscussionBtn(){
this.buttonActiveDiscussion?.classList.remove('active');
}
public setUserInputManager(userInputManager : UserInputManager){
this.userInputManager = userInputManager;
}
public showDiscussionPart(){
this.showDiscussion();
this.hideButtonDiscussionBtn();
}
}
export const discussionManager = new DiscussionManager();

View File

@ -1,6 +1,6 @@
import {DivImportance, layoutManager} from "./LayoutManager";
import {HtmlUtils} from "./HtmlUtils";
import {DiscussionManager, SendMessageCallback} from "./DiscussionManager";
import {discussionManager, SendMessageCallback} from "./DiscussionManager";
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
import {VIDEO_QUALITY_SELECT} from "../Administration/ConsoleGlobalMessageManager";
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
@ -56,8 +56,6 @@ export class MediaManager {
private lastUpdateScene : Date = new Date();
private setTimeOutlastUpdateScene? : NodeJS.Timeout;
private discussionManager: DiscussionManager;
private hasCamera = true;
private triggerCloseJistiFrame : Map<String, Function> = new Map<String, Function>();
@ -120,8 +118,6 @@ export class MediaManager {
this.pingCameraStatus();
this.checkActiveUser(); //todo: desactivated in case of bug
this.discussionManager = new DiscussionManager(this,'');
}
public setLastUpdateScene(){
@ -687,11 +683,11 @@ export class MediaManager {
}
public addNewParticipant(userId: number|string, name: string|undefined, img?: string, reportCallBack?: ReportCallback){
this.discussionManager.addParticipant(userId, name, img, false, reportCallBack);
discussionManager.addParticipant(userId, name, img, false, reportCallBack);
}
public removeParticipant(userId: number|string){
this.discussionManager.removeParticipant(userId);
discussionManager.removeParticipant(userId);
}
public addTriggerCloseJitsiFrameButton(id: String, Function: Function){
this.triggerCloseJistiFrame.set(id, Function);
@ -718,24 +714,24 @@ export class MediaManager {
}
public addNewMessage(name: string, message: string, isMe: boolean = false){
this.discussionManager.addMessage(name, message, isMe);
discussionManager.addMessage(name, message, isMe);
//when there are new message, show discussion
if(!this.discussionManager.activatedDiscussion) {
this.discussionManager.showDiscussionPart();
if(!discussionManager.activatedDiscussion) {
discussionManager.showDiscussionPart();
}
}
public addSendMessageCallback(userId: string|number, callback: SendMessageCallback){
this.discussionManager.onSendMessageCallback(userId, callback);
discussionManager.onSendMessageCallback(userId, callback);
}
get activatedDiscussion(){
return this.discussionManager.activatedDiscussion;
return discussionManager.activatedDiscussion;
}
public setUserInputManager(userInputManager : UserInputManager){
this.discussionManager.setUserInputManager(userInputManager);
discussionManager.setUserInputManager(userInputManager);
}
//check if user is active
private checkActiveUser(){

View File

@ -13,7 +13,8 @@ import {CustomizeScene} from "./Phaser/Login/CustomizeScene";
import {ResizableScene} from "./Phaser/Login/ResizableScene";
import {EntryScene} from "./Phaser/Login/EntryScene";
import {coWebsiteManager} from "./WebRtc/CoWebsiteManager";
import {GAME_QUALITY_SELECT} from "./Administration/ConsoleGlobalMessageManager";
import {MenuScene} from "./Phaser/Menu/MenuScene";
import {localUserStore} from "./Connexion/LocalUserStore";
// Load Jitsi if the environment variable is set.
if (JITSI_URL) {
@ -24,15 +25,7 @@ if (JITSI_URL) {
const {width, height} = coWebsiteManager.getGameSize();
let valueGameQuality : number = 60
const localGameQuality = localStorage.getItem(GAME_QUALITY_SELECT);
if(localGameQuality){
try {
valueGameQuality = parseInt(localGameQuality);
}catch (err){
console.error(err);
}
}
const valueGameQuality = localUserStore.getGameQualityValue();
const fps : Phaser.Types.Core.FPSConfig = {
/**
* The minimum acceptable rendering rate, in frames per second.
@ -66,9 +59,12 @@ const config: GameConfig = {
width: width / RESOLUTION,
height: height / RESOLUTION,
parent: "game",
scene: [EntryScene, LoginScene, SelectCharacterScene, EnableCameraScene, ReconnectingScene, FourOFourScene, CustomizeScene],
scene: [EntryScene, LoginScene, SelectCharacterScene, EnableCameraScene, ReconnectingScene, FourOFourScene, CustomizeScene, MenuScene],
zoom: RESOLUTION,
fps: fps,
dom: {
createContainer: true
},
physics: {
default: "arcade",
arcade: {