Merge pull request from thecodingmachine/strict_mode_front

Enabling Typescript strict mode on the front
This commit is contained in:
David Négrier 2020-06-04 22:17:00 +02:00 committed by GitHub
commit 7223c1804d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 302 additions and 231 deletions

View File

@ -98,10 +98,10 @@ export interface GroupCreatedUpdatedMessageInterface {
}
export interface ConnectionInterface {
socket: any;
token: string;
name: string;
userId: string;
socket: Socket|null;
token: string|null;
name: string|null;
userId: string|null;
createConnection(name: string, characterSelected: string): Promise<any>;
@ -112,7 +112,7 @@ export interface ConnectionInterface {
sharePosition(x: number, y: number, direction: string, moving: boolean): void;
/*webrtc*/
sendWebrtcSignal(signal: any, roomId: string, userId?: string, receiverId?: string): void;
sendWebrtcSignal(signal: any, roomId: string, userId?: string|null, receiverId?: string): void;
receiveWebrtcSignal(callBack: Function): void;
@ -122,15 +122,15 @@ export interface ConnectionInterface {
}
export class Connection implements ConnectionInterface {
socket: Socket;
token: string;
name: string; // TODO: drop "name" storage here
character: string;
userId: string;
socket: Socket|null = null;
token: string|null = null;
name: string|null = null; // TODO: drop "name" storage here
character: string|null = null;
userId: string|null = null;
GameManager: GameManager;
lastPositionShared: PointInterface = null;
lastPositionShared: PointInterface|null = null;
lastRoom: string|null = null;
constructor(GameManager: GameManager) {
@ -156,6 +156,13 @@ export class Connection implements ConnectionInterface {
});
}
private getSocket(): Socket {
if (this.socket === null) {
throw new Error('Socket not initialized while using Connection')
}
return this.socket;
}
/**
*
* @param character
@ -171,7 +178,7 @@ export class Connection implements ConnectionInterface {
this.onUserLeft();
return new Promise<ConnectionInterface>((resolve, reject) => {
this.socket.emit(EventMessage.SET_PLAYER_DETAILS, {
this.getSocket().emit(EventMessage.SET_PLAYER_DETAILS, {
name: this.name,
character: this.character
} as SetPlayerDetailsMessage, (id: string) => {
@ -215,7 +222,7 @@ export class Connection implements ConnectionInterface {
}
joinARoom(roomId: string, startX: number, startY: number, direction: string, moving: boolean): void {
this.socket.emit(EventMessage.JOIN_ROOM, { roomId, position: {x: startX, y: startY, direction, moving }}, (userPositions: MessageUserPositionInterface[]) => {
this.getSocket().emit(EventMessage.JOIN_ROOM, { roomId, position: {x: startX, y: startY, direction, moving }}, (userPositions: MessageUserPositionInterface[]) => {
this.GameManager.initUsersPosition(userPositions);
});
this.lastRoom = roomId;
@ -227,42 +234,42 @@ export class Connection implements ConnectionInterface {
}
let point = new Point(x, y, direction, moving);
this.lastPositionShared = point;
this.socket.emit(EventMessage.USER_POSITION, point);
this.getSocket().emit(EventMessage.USER_POSITION, point);
}
private onUserJoins(): void {
this.socket.on(EventMessage.JOIN_ROOM, (message: MessageUserJoined) => {
this.getSocket().on(EventMessage.JOIN_ROOM, (message: MessageUserJoined) => {
this.GameManager.onUserJoins(message);
});
}
private onUserMoved(): void {
this.socket.on(EventMessage.USER_MOVED, (message: MessageUserMovedInterface) => {
this.getSocket().on(EventMessage.USER_MOVED, (message: MessageUserMovedInterface) => {
this.GameManager.onUserMoved(message);
});
}
private onUserLeft(): void {
this.socket.on(EventMessage.USER_LEFT, (userId: string) => {
this.getSocket().on(EventMessage.USER_LEFT, (userId: string) => {
this.GameManager.onUserLeft(userId);
});
}
private groupUpdatedOrCreated(): void {
this.socket.on(EventMessage.GROUP_CREATE_UPDATE, (groupCreateUpdateMessage: GroupCreatedUpdatedMessageInterface) => {
this.getSocket().on(EventMessage.GROUP_CREATE_UPDATE, (groupCreateUpdateMessage: GroupCreatedUpdatedMessageInterface) => {
//console.log('Group ', groupCreateUpdateMessage.groupId, " position :", groupCreateUpdateMessage.position.x, groupCreateUpdateMessage.position.y)
this.GameManager.shareGroupPosition(groupCreateUpdateMessage);
})
}
private groupDeleted(): void {
this.socket.on(EventMessage.GROUP_DELETE, (groupId: string) => {
this.getSocket().on(EventMessage.GROUP_DELETE, (groupId: string) => {
this.GameManager.deleteGroup(groupId);
})
}
sendWebrtcSignal(signal: any, roomId: string, userId? : string, receiverId? : string) {
return this.socket.emit(EventMessage.WEBRTC_SIGNAL, {
sendWebrtcSignal(signal: any, roomId: string, userId? : string|null, receiverId? : string) {
return this.getSocket().emit(EventMessage.WEBRTC_SIGNAL, {
userId: userId ? userId : this.userId,
receiverId: receiverId ? receiverId : this.userId,
roomId: roomId,
@ -271,31 +278,34 @@ export class Connection implements ConnectionInterface {
}
receiveWebrtcStart(callback: Function) {
this.socket.on(EventMessage.WEBRTC_START, callback);
this.getSocket().on(EventMessage.WEBRTC_START, callback);
}
receiveWebrtcSignal(callback: Function) {
return this.socket.on(EventMessage.WEBRTC_SIGNAL, callback);
return this.getSocket().on(EventMessage.WEBRTC_SIGNAL, callback);
}
private errorMessage(): void {
this.socket.on(EventMessage.MESSAGE_ERROR, (message: string) => {
this.getSocket().on(EventMessage.MESSAGE_ERROR, (message: string) => {
console.error(EventMessage.MESSAGE_ERROR, message);
})
}
private disconnectServer(): void {
this.socket.on(EventMessage.CONNECT_ERROR, () => {
this.getSocket().on(EventMessage.CONNECT_ERROR, () => {
this.GameManager.switchToDisconnectedScene();
});
this.socket.on(EventMessage.RECONNECT, () => {
this.getSocket().on(EventMessage.RECONNECT, () => {
this.connectSocketServer();
if (this.lastPositionShared === null) {
throw new Error('No last position shared found while reconnecting');
}
this.GameManager.reconnectToGameScene(this.lastPositionShared);
});
}
disconnectMessage(callback: Function): void {
this.socket.on(EventMessage.WEBRTC_DISCONNECT, callback);
this.getSocket().on(EventMessage.WEBRTC_DISCONNECT, callback);
}
}

View File

@ -3,18 +3,18 @@ export class MessageUI {
static warningMessage(text: string){
this.removeMessage();
let body = document.getElementById("body");
body.insertAdjacentHTML('afterbegin', `
body?.insertAdjacentHTML('afterbegin', `
<div id="message-reconnect" class="message-info warning">
${text}
</div>
`);
}
static removeMessage(id : string = null) {
static removeMessage(id : string|null = null) {
if(!id){
let messages = document.getElementsByClassName("message-info");
for (let i = 0; i < messages.length; i++){
messages.item(i).remove();
messages.item(i)?.remove();
}
return;
}

View File

@ -24,14 +24,31 @@ export const PLAYER_RESOURCES: Array<any> = [
{name: "Female8", img: "resources/characters/pipoya/Female 16-4.png"/*, x: 128, y: 128*/}
];
export class PlayableCaracter extends Phaser.Physics.Arcade.Sprite {
private bubble: SpeechBubble;
interface AnimationData {
key: string;
frameRate: number;
repeat: number;
frameModel: string; //todo use an enum
frameStart: number;
frameEnd: number;
}
export abstract class Character extends Phaser.Physics.Arcade.Sprite {
private bubble: SpeechBubble|null = null;
private readonly playerName: BitmapText;
public PlayerValue: string;
public PlayerTexture: string;
constructor(scene: Phaser.Scene, x: number, y: number, texture: string, name: string, frame?: string | number) {
constructor(scene: Phaser.Scene,
x: number,
y: number,
texture: string,
name: string,
direction: string,
moving: boolean,
frame?: string | number
) {
super(scene, x, y, texture, frame);
this.PlayerValue = name;
@ -51,6 +68,64 @@ export class PlayableCaracter extends Phaser.Physics.Arcade.Sprite {
this.setDepth(-1);
this.scene.events.on('postupdate', this.postupdate.bind(this));
this.initAnimation();
this.playAnimation(direction, moving);
}
private initAnimation(): void {
this.getPlayerAnimations(this.PlayerTexture).forEach(d => {
this.scene.anims.create({
key: d.key,
frames: this.scene.anims.generateFrameNumbers(d.frameModel, {start: d.frameStart, end: d.frameEnd}),
frameRate: d.frameRate,
repeat: d.repeat
});
})
}
private getPlayerAnimations(name: string): AnimationData[] {
return [{
key: `${name}-${PlayerAnimationNames.WalkDown}`,
frameModel: name,
frameStart: 0,
frameEnd: 2,
frameRate: 10,
repeat: -1
}, {
key: `${name}-${PlayerAnimationNames.WalkLeft}`,
frameModel: name,
frameStart: 3,
frameEnd: 5,
frameRate: 10,
repeat: -1
}, {
key: `${name}-${PlayerAnimationNames.WalkRight}`,
frameModel: name,
frameStart: 6,
frameEnd: 8,
frameRate: 10,
repeat: -1
}, {
key: `${name}-${PlayerAnimationNames.WalkUp}`,
frameModel: name,
frameStart: 9,
frameEnd: 11,
frameRate: 10,
repeat: -1
}];
}
protected playAnimation(direction : string, moving: boolean): void {
if (moving && (!this.anims.currentAnim || this.anims.currentAnim.key !== direction)) {
this.play(this.PlayerTexture+'-'+direction, true);
} else if (!moving) {
/*if (this.anims.currentAnim) {
this.anims.stop();
}*/
this.play(this.PlayerTexture+'-'+direction, true);
this.stop();
}
}
move(x: number, y: number) {
@ -91,8 +166,10 @@ export class PlayableCaracter extends Phaser.Physics.Arcade.Sprite {
this.bubble = new SpeechBubble(this.scene, this, text)
//todo make the bubble destroy on player movement?
setTimeout(() => {
this.bubble.destroy();
this.bubble = null;
if (this.bubble !== null) {
this.bubble.destroy();
this.bubble = null;
}
}, 3000)
}

View File

@ -0,0 +1,38 @@
import {GameScene} from "../Game/GameScene";
import {PointInterface} from "../../Connection";
import {Character} from "../Entity/Character";
/**
* Class representing the sprite of a remote player (a player that plays on another computer)
*/
export class RemotePlayer extends Character {
userId: string;
previousDirection: string;
wasMoving: boolean;
constructor(
userId: string,
Scene: GameScene,
x: number,
y: number,
name: string,
PlayerTexture: string,
direction: string,
moving: boolean
) {
super(Scene, x, y, PlayerTexture, name, direction, moving, 1);
//set data
this.userId = userId;
//the current player model should be push away by other players to prevent conflict
//this.setImmovable(false);
}
updatePosition(position: PointInterface): void {
this.playAnimation(position.direction, position.moving);
this.setX(position.x);
this.setY(position.y);
this.setDepth(position.y);
}
}

View File

@ -1,5 +1,5 @@
import Scene = Phaser.Scene;
import {PlayableCaracter} from "./PlayableCaracter";
import {Character} from "./Character";
export class SpeechBubble {
private bubble: Phaser.GameObjects.Graphics;
@ -11,7 +11,7 @@ export class SpeechBubble {
* @param player
* @param text
*/
constructor(scene: Scene, player: PlayableCaracter, text: string = "") {
constructor(scene: Scene, player: Character, text: string = "") {
let bubbleHeight = 50;
let bubblePadding = 10;
@ -80,9 +80,6 @@ export class SpeechBubble {
destroy(): void {
this.bubble.setVisible(false) //todo find a better way
this.bubble.destroy();
this.bubble = null;
this.content.destroy();
this.content = null;
}
}

View File

@ -9,7 +9,7 @@ import {
Point,
PointInterface
} from "../../Connection";
import {SimplePeerInterface, SimplePeer} from "../../WebRtc/SimplePeer";
import {SimplePeer} from "../../WebRtc/SimplePeer";
import {AddPlayerInterface} from "./AddPlayerInterface";
import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene";
@ -35,7 +35,7 @@ export class GameManager {
private ConnectionInstance: Connection;
private currentGameScene: GameScene;
private playerName: string;
SimplePeer : SimplePeerInterface;
SimplePeer : SimplePeer;
private characterUserSelected: string;
constructor() {
@ -139,7 +139,7 @@ export class GameManager {
return this.playerName;
}
getPlayerId(): string {
getPlayerId(): string|null {
return this.ConnectionInstance.userId;
}
@ -155,9 +155,8 @@ export class GameManager {
let sceneKey = GameScene.getMapKeyByUrl(mapUrl);
let gameIndex = scene.getIndex(sceneKey);
let game : Phaser.Scene = null;
if(gameIndex === -1){
game = GameScene.createFromUrl(mapUrl, instance);
let game : Phaser.Scene = GameScene.createFromUrl(mapUrl, instance);
scene.add(sceneKey, game, false);
}
return sceneKey;

View File

@ -4,10 +4,10 @@ import {
MessageUserMovedInterface,
MessageUserPositionInterface, PointInterface, PositionInterface
} from "../../Connection";
import {CurrentGamerInterface, GamerInterface, hasMovedEventName, Player} from "../Player/Player";
import {CurrentGamerInterface, hasMovedEventName, Player} from "../Player/Player";
import { DEBUG_MODE, ZOOM_LEVEL, POSITION_DELAY } from "../../Enum/EnvironmentVariable";
import {ITiledMap, ITiledMapLayer, ITiledTileSet} from "../Map/ITiledMap";
import {PLAYER_RESOURCES} from "../Entity/PlayableCaracter";
import {PLAYER_RESOURCES} from "../Entity/Character";
import Texture = Phaser.Textures.Texture;
import Sprite = Phaser.GameObjects.Sprite;
import CanvasTexture = Phaser.Textures.CanvasTexture;
@ -15,6 +15,7 @@ import {AddPlayerInterface} from "./AddPlayerInterface";
import {PlayerAnimationNames} from "../Player/Animation";
import {PlayerMovement} from "./PlayerMovement";
import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator";
import {RemotePlayer} from "../Entity/RemotePlayer";
export enum Textures {
Player = "male1"
@ -29,16 +30,16 @@ export class GameScene extends Phaser.Scene {
Terrains : Array<Phaser.Tilemaps.Tileset>;
CurrentPlayer: CurrentGamerInterface;
MapPlayers : Phaser.Physics.Arcade.Group;
MapPlayersByKey : Map<string, GamerInterface> = new Map<string, GamerInterface>();
MapPlayersByKey : Map<string, RemotePlayer> = new Map<string, RemotePlayer>();
Map: Phaser.Tilemaps.Tilemap;
Layers : Array<Phaser.Tilemaps.StaticTilemapLayer>;
Objects : Array<Phaser.Physics.Arcade.Sprite>;
map: ITiledMap;
mapFile: ITiledMap;
groups: Map<string, Sprite>;
startX = 704;// 22 case
startY = 32; // 1 case
circleTexture: CanvasTexture;
initPosition: PositionInterface;
private initPosition: PositionInterface|null = null;
private playersPositionInterpolator = new PlayersPositionInterpolator();
MapKey: string;
@ -107,9 +108,9 @@ export class GameScene extends Phaser.Scene {
private onMapLoad(data: any): void {
// Triggered when the map is loaded
// Load tiles attached to the map recursively
this.map = data.data;
this.mapFile = data.data;
let url = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/'));
this.map.tilesets.forEach((tileset) => {
this.mapFile.tilesets.forEach((tileset) => {
if (typeof tileset.name === 'undefined' || typeof tileset.image === 'undefined') {
console.warn("Don't know how to handle tileset ", tileset)
return;
@ -121,14 +122,16 @@ export class GameScene extends Phaser.Scene {
//hook initialisation
init(initData : GameSceneInitInterface) {
this.initPosition = initData.initPosition;
if (initData.initPosition !== undefined) {
this.initPosition = initData.initPosition;
}
}
//hook create scene
create(): void {
//initalise map
this.Map = this.add.tilemap(this.MapKey);
this.map.tilesets.forEach((tileset: ITiledTileSet) => {
this.mapFile.tilesets.forEach((tileset: ITiledTileSet) => {
this.Terrains.push(this.Map.addTilesetImage(tileset.name, tileset.name));
});
@ -138,12 +141,12 @@ export class GameScene extends Phaser.Scene {
//add layer on map
this.Layers = new Array<Phaser.Tilemaps.StaticTilemapLayer>();
let depth = -2;
this.map.layers.forEach((layer : ITiledMapLayer) => {
this.mapFile.layers.forEach((layer : ITiledMapLayer) => {
if (layer.type === 'tilelayer') {
this.addLayer(this.Map.createStaticLayer(layer.name, this.Terrains, 0, 0).setDepth(depth));
}
if (layer.type === 'tilelayer' && this.getExitSceneUrl(layer) !== undefined) {
this.loadNextGame(layer, this.map.width, this.map.tilewidth, this.map.tileheight);
this.loadNextGame(layer, this.mapFile.width, this.mapFile.tilewidth, this.mapFile.tileheight);
}
if (layer.type === 'tilelayer' && layer.name === "start") {
let startPosition = this.startUser(layer);
@ -196,7 +199,7 @@ export class GameScene extends Phaser.Scene {
// FIXME: entry should be dictated by a property passed to init()
path += '#'+url.hash;
}
window.history.pushState({}, null, path);
window.history.pushState({}, 'WorkAdventure', path);
}
private getExitSceneUrl(layer: ITiledMapLayer): string|undefined {
@ -232,6 +235,9 @@ export class GameScene extends Phaser.Scene {
*/
private loadNextGame(layer: ITiledMapLayer, mapWidth: number, tileWidth: number, tileHeight: number){
let exitSceneUrl = this.getExitSceneUrl(layer);
if (exitSceneUrl === undefined) {
throw new Error('Layer is not an exit scene layer.');
}
let instance = this.getExitSceneInstance(layer);
if (instance === undefined) {
instance = this.instance;
@ -265,7 +271,7 @@ export class GameScene extends Phaser.Scene {
* @param layer
*/
private startUser(layer: ITiledMapLayer): PositionInterface {
if (this.initPosition !== undefined) {
if (this.initPosition !== null) {
this.startX = this.initPosition.x;
this.startY = this.initPosition.y;
return {
@ -338,7 +344,6 @@ export class GameScene extends Phaser.Scene {
//initialise player
//TODO create animation moving between exit and start
this.CurrentPlayer = new Player(
null, // The current player is not has no id (because the id can change if connection is lost and we should check that id using the GameManager.
this,
this.startX,
this.startY,
@ -413,7 +418,7 @@ export class GameScene extends Phaser.Scene {
// Let's move all users
let updatedPlayersPositions = this.playersPositionInterpolator.getUpdatedPositions(time);
updatedPlayersPositions.forEach((moveEvent: HasMovedEvent, userId: string) => {
let player : GamerInterface | undefined = this.MapPlayersByKey.get(userId);
let player : RemotePlayer | undefined = this.MapPlayersByKey.get(userId);
if (player === undefined) {
throw new Error('Cannot find player with ID "' + userId +'"');
}
@ -450,11 +455,11 @@ export class GameScene extends Phaser.Scene {
let currentPlayerId = this.GameManager.getPlayerId();
// clean map
this.MapPlayersByKey.forEach((player: GamerInterface) => {
this.MapPlayersByKey.forEach((player: RemotePlayer) => {
player.destroy();
this.MapPlayers.remove(player);
});
this.MapPlayersByKey = new Map<string, GamerInterface>();
this.MapPlayersByKey = new Map<string, RemotePlayer>();
// load map
usersPosition.forEach((userPosition : MessageUserPositionInterface) => {
@ -478,7 +483,7 @@ export class GameScene extends Phaser.Scene {
return;
}
//initialise player
let player = new Player(
let player = new RemotePlayer(
addPlayerData.userId,
this,
addPlayerData.position.x,
@ -503,15 +508,16 @@ export class GameScene extends Phaser.Scene {
let player = this.MapPlayersByKey.get(userId);
if (player === undefined) {
console.error('Cannot find user with id ', userId);
} else {
player.destroy();
this.MapPlayers.remove(player);
}
player.destroy();
this.MapPlayers.remove(player);
this.MapPlayersByKey.delete(userId);
this.playersPositionInterpolator.removePlayer(userId);
}
updatePlayerPosition(message: MessageUserMovedInterface): void {
let player : GamerInterface | undefined = this.MapPlayersByKey.get(message.userId);
let player : RemotePlayer | undefined = this.MapPlayersByKey.get(message.userId);
if (player === undefined) {
throw new Error('Cannot find player with ID "' + message.userId +'"');
}
@ -525,8 +531,9 @@ export class GameScene extends Phaser.Scene {
shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface) {
let groupId = groupPositionMessage.groupId;
if (this.groups.has(groupId)) {
this.groups.get(groupId).setPosition(Math.round(groupPositionMessage.position.x), Math.round(groupPositionMessage.position.y));
let group = this.groups.get(groupId);
if (group !== undefined) {
group.setPosition(Math.round(groupPositionMessage.position.x), Math.round(groupPositionMessage.position.y));
} else {
// TODO: circle radius should not be hard stored
let sprite = new Sprite(
@ -541,10 +548,11 @@ export class GameScene extends Phaser.Scene {
}
deleteGroup(groupId: string): void {
if(!this.groups.get(groupId)){
let group = this.groups.get(groupId);
if(!group){
return;
}
this.groups.get(groupId).destroy();
group.destroy();
this.groups.delete(groupId);
}

View File

@ -4,7 +4,7 @@ import {TextInput} from "../Components/TextInput";
import {ClickButton} from "../Components/ClickButton";
import Image = Phaser.GameObjects.Image;
import Rectangle = Phaser.GameObjects.Rectangle;
import {PLAYER_RESOURCES} from "../Entity/PlayableCaracter";
import {PLAYER_RESOURCES} from "../Entity/Character";
import {cypressAsserter} from "../../Cypress/CypressAsserter";
import {SelectCharacterSceneInitDataInterface, SelectCharacterSceneName} from "./SelectCharacterScene";
@ -16,12 +16,12 @@ enum LoginTextures {
}
export class LoginScene extends Phaser.Scene {
private nameInput: TextInput;
private textField: TextField;
private infoTextField: TextField;
private pressReturnField: TextField;
private logo: Image;
private name: string;
private nameInput: TextInput|null = null;
private textField: TextField|null = null;
private infoTextField: TextField|null = null;
private pressReturnField: TextField|null = null;
private logo: Image|null = null;
private name: string = '';
constructor() {
super({
@ -82,9 +82,9 @@ export class LoginScene extends Phaser.Scene {
update(time: number, delta: number): void {
if (this.name == '') {
this.pressReturnField.setVisible(false);
this.pressReturnField?.setVisible(false);
} else {
this.pressReturnField.setVisible(!!(Math.floor(time / 500) % 2));
this.pressReturnField?.setVisible(!!(Math.floor(time / 500) % 2));
}
}

View File

@ -3,7 +3,7 @@ import {TextField} from "../Components/TextField";
import {ClickButton} from "../Components/ClickButton";
import Image = Phaser.GameObjects.Image;
import Rectangle = Phaser.GameObjects.Rectangle;
import {PLAYER_RESOURCES} from "../Entity/PlayableCaracter";
import {PLAYER_RESOURCES} from "../Entity/Character";
//todo: put this constants in a dedicated file
export const SelectCharacterSceneName = "SelectCharacterScene";

View File

@ -2,39 +2,21 @@ import {PlayerAnimationNames} from "./Animation";
import {GameScene, Textures} from "../Game/GameScene";
import {MessageUserPositionInterface, PointInterface} from "../../Connection";
import {ActiveEventList, UserInputEvent, UserInputManager} from "../UserInput/UserInputManager";
import {PlayableCaracter} from "../Entity/PlayableCaracter";
import {Character} from "../Entity/Character";
export const hasMovedEventName = "hasMoved";
export interface CurrentGamerInterface extends PlayableCaracter{
export interface CurrentGamerInterface extends Character{
moveUser(delta: number) : void;
say(text : string) : void;
}
export interface GamerInterface extends PlayableCaracter{
userId : string;
updatePosition(position: PointInterface): void;
say(text : string) : void;
}
interface AnimationData {
key: string;
frameRate: number;
repeat: number;
frameModel: string; //todo use an enum
frameStart: number;
frameEnd: number;
}
export class Player extends PlayableCaracter implements CurrentGamerInterface, GamerInterface {
userId: string;
export class Player extends Character implements CurrentGamerInterface {
userInputManager: UserInputManager;
previousDirection: string;
wasMoving: boolean;
constructor(
userId: string,
Scene: GameScene,
x: number,
y: number,
@ -43,62 +25,13 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G
direction: string,
moving: boolean
) {
super(Scene, x, y, PlayerTexture, name, 1);
super(Scene, x, y, PlayerTexture, name, direction, moving, 1);
//create input to move
this.userInputManager = new UserInputManager(Scene);
//set data
this.userId = userId;
//the current player model should be push away by other players to prevent conflict
this.setImmovable(false);
this.initAnimation();
this.playAnimation(direction, moving);
}
private initAnimation(): void {
this.getPlayerAnimations(this.PlayerTexture).forEach(d => {
this.scene.anims.create({
key: d.key,
frames: this.scene.anims.generateFrameNumbers(d.frameModel, {start: d.frameStart, end: d.frameEnd}),
frameRate: d.frameRate,
repeat: d.repeat
});
})
}
private getPlayerAnimations(name: string): AnimationData[] {
return [{
key: `${name}-${PlayerAnimationNames.WalkDown}`,
frameModel: name,
frameStart: 0,
frameEnd: 2,
frameRate: 10,
repeat: -1
}, {
key: `${name}-${PlayerAnimationNames.WalkLeft}`,
frameModel: name,
frameStart: 3,
frameEnd: 5,
frameRate: 10,
repeat: -1
}, {
key: `${name}-${PlayerAnimationNames.WalkRight}`,
frameModel: name,
frameStart: 6,
frameEnd: 8,
frameRate: 10,
repeat: -1
}, {
key: `${name}-${PlayerAnimationNames.WalkUp}`,
frameModel: name,
frameStart: 9,
frameEnd: 11,
frameRate: 10,
repeat: -1
}];
}
moveUser(delta: number): void {
@ -146,24 +79,4 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G
}
this.wasMoving = moving;
}
//todo: put this method into the NonPlayer class instead
updatePosition(position: PointInterface): void {
this.playAnimation(position.direction, position.moving);
this.setX(position.x);
this.setY(position.y);
this.setDepth(position.y);
}
private playAnimation(direction : string, moving: boolean): void {
if (moving && (!this.anims.currentAnim || this.anims.currentAnim.key !== direction)) {
this.play(this.PlayerTexture+'-'+direction, true);
} else if (!moving) {
/*if (this.anims.currentAnim) {
this.anims.stop();
}*/
this.play(this.PlayerTexture+'-'+direction, true);
this.stop();
}
}
}

View File

@ -4,7 +4,7 @@ import {TextInput} from "../Components/TextInput";
import {ClickButton} from "../Components/ClickButton";
import Image = Phaser.GameObjects.Image;
import Rectangle = Phaser.GameObjects.Rectangle;
import {PLAYER_RESOURCES} from "../Entity/PlayableCaracter";
import {PLAYER_RESOURCES} from "../Entity/Character";
import {cypressAsserter} from "../../Cypress/CypressAsserter";
import Sprite = Phaser.GameObjects.Sprite;

View File

@ -2,7 +2,6 @@ import Map = Phaser.Structs.Map;
import {GameScene} from "../Game/GameScene";
interface UserInputManagerDatum {
keyCode: number;
keyInstance: Phaser.Input.Keyboard.Key;
event: UserInputEvent
}
@ -33,27 +32,26 @@ export class ActiveEventList {
//this class is responsible for catching user inputs and listing all active user actions at every game tick events.
export class UserInputManager {
private KeysCode: UserInputManagerDatum[] = [
{keyCode: Phaser.Input.Keyboard.KeyCodes.Z, event: UserInputEvent.MoveUp, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.Q, event: UserInputEvent.MoveLeft, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.S, event: UserInputEvent.MoveDown, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.D, event: UserInputEvent.MoveRight, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.UP, event: UserInputEvent.MoveUp, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.LEFT, event: UserInputEvent.MoveLeft, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.DOWN, event: UserInputEvent.MoveDown, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.RIGHT, event: UserInputEvent.MoveRight, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.SHIFT, event: UserInputEvent.SpeedUp, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.E, event: UserInputEvent.Interact, keyInstance: null},
{keyCode: Phaser.Input.Keyboard.KeyCodes.F, event: UserInputEvent.Shout, keyInstance: null},
];
private KeysCode: UserInputManagerDatum[];
constructor(Scene : GameScene) {
this.KeysCode.forEach(d => {
d.keyInstance = Scene.input.keyboard.addKey(d.keyCode);
});
this.KeysCode = [
{event: UserInputEvent.MoveUp, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Z) },
{event: UserInputEvent.MoveLeft, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q) },
{event: UserInputEvent.MoveDown, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.S) },
{event: UserInputEvent.MoveRight, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.D) },
{event: UserInputEvent.MoveUp, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.UP) },
{event: UserInputEvent.MoveLeft, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.LEFT) },
{event: UserInputEvent.MoveDown, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.DOWN) },
{event: UserInputEvent.MoveRight, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.RIGHT) },
{event: UserInputEvent.SpeedUp, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SHIFT) },
{event: UserInputEvent.Interact, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.E) },
{event: UserInputEvent.Shout, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.F) },
];
}
getEventListForGameTick(): ActiveEventList {

View File

@ -4,26 +4,25 @@ const videoConstraint: {width : any, height: any, facingMode : string} = {
facingMode: "user"
};
export class MediaManager {
localStream: MediaStream;
localStream: MediaStream|null = null;
remoteVideo: Array<any> = new Array<any>();
myCamVideo: any;
myCamVideo: HTMLVideoElement;
cinemaClose: any = null;
cinema: any = null;
microphoneClose: any = null;
microphone: any = null;
webrtcInAudio: any;
webrtcInAudio: HTMLAudioElement;
constraintsMedia : {audio : any, video : any} = {
audio: true,
video: videoConstraint
};
getCameraPromise : Promise<any> = null;
updatedLocalStreamCallBack : Function;
constructor(updatedLocalStreamCallBack : Function) {
this.updatedLocalStreamCallBack = updatedLocalStreamCallBack;
this.myCamVideo = document.getElementById('myCamVideo');
this.webrtcInAudio = document.getElementById('audio-webrtc-in');
this.myCamVideo = this.getElementByIdOrFail<HTMLVideoElement>('myCamVideo');
this.webrtcInAudio = this.getElementByIdOrFail<HTMLAudioElement>('audio-webrtc-in');
this.webrtcInAudio.volume = 0.2;
this.microphoneClose = document.getElementById('microphone-close');
@ -56,7 +55,7 @@ export class MediaManager {
}
activeVisio(){
let webRtc = document.getElementById('webRtc');
let webRtc = this.getElementByIdOrFail('webRtc');
webRtc.classList.add('active');
}
@ -130,7 +129,7 @@ export class MediaManager {
} catch (e) {
promise = Promise.reject(false);
}
return this.getCameraPromise = promise;
return promise;
}
/**
@ -139,7 +138,7 @@ export class MediaManager {
*/
addActiveVideo(userId : string, userName: string = ""){
this.webrtcInAudio.play();
let elementRemoteVideo = document.getElementById("activeCam");
let elementRemoteVideo = this.getElementByIdOrFail("activeCam");
userName = userName.toUpperCase();
let color = this.getColorByString(userName);
elementRemoteVideo.insertAdjacentHTML('beforeend', `
@ -247,4 +246,14 @@ export class MediaManager {
}
return color;
}
private getElementByIdOrFail<T extends HTMLElement>(id: string): T {
let elem = document.getElementById(id);
if (elem === null) {
throw new Error("Cannot find HTML element with id '"+id+"'");
}
// FIXME: does not check the type of the returned type
return elem as T;
}
}

View File

@ -1,21 +1,21 @@
import {ConnectionInterface} from "../Connection";
import {MediaManager} from "./MediaManager";
let Peer = require('simple-peer');
import * as SimplePeerNamespace from "simple-peer";
let Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
class UserSimplePear{
class UserSimplePeer{
userId: string;
name?: string;
initiator?: boolean;
}
export class SimplePeerInterface {}
export class SimplePeer implements SimplePeerInterface{
export class SimplePeer {
private Connection: ConnectionInterface;
private WebRtcRoomId: string;
private Users: Array<UserSimplePear> = new Array<UserSimplePear>();
private Users: Array<UserSimplePeer> = new Array<UserSimplePeer>();
private MediaManager: MediaManager;
private PeerConnectionArray: Map<string, any> = new Map<string, any>();
private PeerConnectionArray: Map<string, SimplePeerNamespace.Instance> = new Map<string, SimplePeerNamespace.Instance>();
constructor(Connection: ConnectionInterface, WebRtcRoomId: string = "test-webrtc") {
this.Connection = Connection;
@ -66,7 +66,7 @@ export class SimplePeer implements SimplePeerInterface{
* server has two person connected, start the meet
*/
private startWebRtc() {
this.Users.forEach((user: UserSimplePear) => {
this.Users.forEach((user: UserSimplePeer) => {
//if it's not an initiator, peer connection will be created when gamer will receive offer signal
if(!user.initiator){
return;
@ -78,14 +78,14 @@ export class SimplePeer implements SimplePeerInterface{
/**
* create peer connection to bind users
*/
private createPeerConnection(user : UserSimplePear) {
private createPeerConnection(user : UserSimplePeer) {
if(this.PeerConnectionArray.has(user.userId)) {
return;
}
let name = user.name;
if(!name){
let userSearch = this.Users.find((userSearch: UserSimplePear) => userSearch.userId === user.userId);
let userSearch = this.Users.find((userSearch: UserSimplePeer) => userSearch.userId === user.userId);
if(userSearch) {
name = userSearch.name;
}
@ -112,11 +112,11 @@ export class SimplePeer implements SimplePeerInterface{
this.PeerConnectionArray.set(user.userId, peer);
//start listen signal for the peer connection
this.PeerConnectionArray.get(user.userId).on('signal', (data: any) => {
peer.on('signal', (data: any) => {
this.sendWebrtcSignal(data, user.userId);
});
this.PeerConnectionArray.get(user.userId).on('stream', (stream: MediaStream) => {
peer.on('stream', (stream: MediaStream) => {
let videoActive = false;
let microphoneActive = false;
stream.getTracks().forEach((track : MediaStreamTrack) => {
@ -141,23 +141,23 @@ export class SimplePeer implements SimplePeerInterface{
this.stream(user.userId, stream);
});
/*this.PeerConnectionArray.get(user.userId).on('track', (track: MediaStreamTrack, stream: MediaStream) => {
/*peer.on('track', (track: MediaStreamTrack, stream: MediaStream) => {
this.stream(user.userId, stream);
});*/
this.PeerConnectionArray.get(user.userId).on('close', () => {
peer.on('close', () => {
this.closeConnection(user.userId);
});
this.PeerConnectionArray.get(user.userId).on('error', (err: any) => {
peer.on('error', (err: any) => {
console.error(`error => ${user.userId} => ${err.code}`, err);
});
this.PeerConnectionArray.get(user.userId).on('connect', () => {
peer.on('connect', () => {
console.info(`connect => ${user.userId}`);
});
this.PeerConnectionArray.get(user.userId).on('data', (chunk: Buffer) => {
peer.on('data', (chunk: Buffer) => {
let data = JSON.parse(chunk.toString('utf8'));
if(data.type === "stream"){
this.stream(user.userId, data.stream);
@ -174,7 +174,7 @@ export class SimplePeer implements SimplePeerInterface{
return;
}
// @ts-ignore
this.PeerConnectionArray.get(userId).destroy();
this.PeerConnectionArray.get(userId)?.destroy();
this.PeerConnectionArray.delete(userId)
} catch (err) {
console.error("closeConnection", err)
@ -200,7 +200,12 @@ export class SimplePeer implements SimplePeerInterface{
if(data.signal.type === "offer"){
this.createPeerConnection(data);
}
this.PeerConnectionArray.get(data.userId).signal(data.signal);
let peer = this.PeerConnectionArray.get(data.userId);
if (peer !== undefined) {
peer.signal(data.signal);
} else {
console.error('Could not find peer whose ID is "'+data.userId+'" in PeerConnectionArray');
}
} catch (e) {
console.error(`receiveWebrtcSignal => ${data.userId}`, e);
}
@ -227,27 +232,32 @@ export class SimplePeer implements SimplePeerInterface{
private addMedia (userId : any = null) {
try {
let transceiver : any = null;
if(!this.MediaManager.localStream){
let localStream: MediaStream|null = this.MediaManager.localStream;
let peer = this.PeerConnectionArray.get(userId);
if(localStream === null) {
//send fake signal
if(!this.PeerConnectionArray.has(userId)){
if(peer === undefined){
return;
}
this.PeerConnectionArray.get(userId).write(new Buffer(JSON.stringify({
peer.write(new Buffer(JSON.stringify({
type: "stream",
stream: null
})));
return;
}
this.MediaManager.localStream.getTracks().forEach(
transceiver = (track: MediaStreamTrack) => this.PeerConnectionArray.get(userId).addTrack(track, this.MediaManager.localStream)
)
if (peer === undefined) {
throw new Error('While adding media, cannot find user with ID '+userId);
}
for (const track of localStream.getTracks()) {
peer.addTrack(track, localStream);
}
}catch (e) {
console.error(`addMedia => addMedia => ${userId}`, e);
}
}
updatedLocalStream(){
this.Users.forEach((user: UserSimplePear) => {
this.Users.forEach((user: UserSimplePeer) => {
this.addMedia(user.userId);
})
}

View File

@ -7,6 +7,18 @@
"module": "CommonJS",
"target": "es5",
"jsx": "react",
"allowJs": true
"allowJs": true,
"strict": false, /* Enable all strict type-checking options. */
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
"strictNullChecks": true, /* Enable strict null checks. */
"strictFunctionTypes": true, /* Enable strict checking of function types. */
"strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
"strictPropertyInitialization": false, /* Enable strict checking of property initialization in classes. */
"noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
"alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */
}
}