Merge pull request #870 from joberthel/feature/player-companion

Added companion (pet) to player
This commit is contained in:
Kharhamel 2021-04-12 18:11:07 +02:00 committed by GitHub
commit 8cdf572685
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 643 additions and 23 deletions

View File

@ -105,7 +105,8 @@ export class GameRoom {
socket,
joinRoomMessage.getTagList(),
joinRoomMessage.getName(),
ProtobufUtils.toCharacterLayerObjects(joinRoomMessage.getCharacterlayerList())
ProtobufUtils.toCharacterLayerObjects(joinRoomMessage.getCharacterlayerList()),
joinRoomMessage.getCompanion()
);
this.nextUserId++;
this.users.set(user.id, user);

View File

@ -4,7 +4,7 @@ import {Zone} from "_Model/Zone";
import {Movable} from "_Model/Movable";
import {PositionNotifier} from "_Model/PositionNotifier";
import {ServerDuplexStream} from "grpc";
import {BatchMessage, PusherToBackMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb";
import {BatchMessage, CompanionMessage, PusherToBackMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb";
import {CharacterLayer} from "_Model/Websocket/CharacterLayer";
export type UserSocket = ServerDuplexStream<PusherToBackMessage, ServerToClientMessage>;
@ -23,7 +23,8 @@ export class User implements Movable {
public readonly socket: UserSocket,
public readonly tags: string[],
public readonly name: string,
public readonly characterLayers: CharacterLayer[]
public readonly characterLayers: CharacterLayer[],
public readonly companion?: CompanionMessage
) {
this.listenedZones = new Set<Zone>();

View File

@ -296,6 +296,7 @@ export class SocketManager {
userJoinedZoneMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
userJoinedZoneMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
userJoinedZoneMessage.setFromzone(this.toProtoZone(fromZone));
userJoinedZoneMessage.setCompanion(thing.companion);
const subMessage = new SubToPusherMessage();
subMessage.setUserjoinedzonemessage(userJoinedZoneMessage);
@ -605,6 +606,7 @@ export class SocketManager {
userJoinedMessage.setName(thing.name);
userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
userJoinedMessage.setCompanion(thing.companion);
const subMessage = new SubToPusherMessage();
subMessage.setUserjoinedzonemessage(userJoinedMessage);

View File

@ -30,6 +30,9 @@
<section>
<button id="changeSkinButton">Edit skin</button>
</section>
<section>
<button id="changeCompanionButton">Edit companion</button>
</section>
<section>
<button id="editGameSettingsButton">Settings</button>
</section>

View File

@ -88,9 +88,9 @@ class ConnectionManager {
this.localUser = new LocalUser('', 'test', []);
}
public connectToRoomSocket(roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface): Promise<OnConnectInterface> {
public connectToRoomSocket(roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface, companion: string|null): Promise<OnConnectInterface> {
return new Promise<OnConnectInterface>((resolve, reject) => {
const connection = new RoomConnection(this.localUser.jwtToken, roomId, name, characterLayers, position, viewport);
const connection = new RoomConnection(this.localUser.jwtToken, roomId, name, characterLayers, position, viewport, companion);
connection.onConnectError((error: object) => {
console.log('An error occurred while connecting to socket server. Retrying');
reject(error);
@ -111,7 +111,7 @@ class ConnectionManager {
this.reconnectingTimeout = setTimeout(() => {
//todo: allow a way to break recursion?
//todo: find a way to avoid recursive function. Otherwise, the call stack will grow indefinitely.
this.connectToRoomSocket(roomId, name, characterLayers, position, viewport).then((connection) => resolve(connection));
this.connectToRoomSocket(roomId, name, characterLayers, position, viewport, companion).then((connection) => resolve(connection));
}, 4000 + Math.floor(Math.random() * 2000) );
});
});

View File

@ -47,6 +47,7 @@ export interface MessageUserPositionInterface {
name: string;
characterLayers: BodyResourceDescriptionInterface[];
position: PointInterface;
companion: string|null;
}
export interface MessageUserMovedInterface {
@ -58,7 +59,8 @@ export interface MessageUserJoined {
userId: number;
name: string;
characterLayers: BodyResourceDescriptionInterface[];
position: PointInterface
position: PointInterface;
companion: string|null;
}
export interface PositionInterface {

View File

@ -4,6 +4,7 @@ const playerNameKey = 'playerName';
const selectedPlayerKey = 'selectedPlayer';
const customCursorPositionKey = 'customCursorPosition';
const characterLayersKey = 'characterLayers';
const companionKey = 'companion';
const gameQualityKey = 'gameQuality';
const videoQualityKey = 'videoQuality';
const audioPlayerVolumeKey = 'audioVolume';
@ -49,6 +50,22 @@ class LocalUserStore {
return areCharacterLayersValid(value) ? value : null;
}
setCompanion(companion: string|null): void {
return localStorage.setItem(companionKey, JSON.stringify(companion));
}
getCompanion(): string|null {
const companion = JSON.parse(localStorage.getItem(companionKey) || "null");
if (typeof companion !== "string" || companion === "") {
return null;
}
return companion;
}
wasCompanionSet(): boolean {
return localStorage.getItem(companionKey) ? true : false;
}
setGameQualityValue(value: number): void {
localStorage.setItem(gameQualityKey, '' + value);
}

View File

@ -66,7 +66,7 @@ export class RoomConnection implements RoomConnection {
* @param token A JWT token containing the UUID of the user
* @param roomId The ID of the room in the form "_/[instance]/[map_url]" or "@/[org]/[event]/[map]"
*/
public constructor(token: string|null, roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface) {
public constructor(token: string|null, roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface, companion: string|null) {
let url = new URL(PUSHER_URL, window.location.toString()).toString();
url = url.replace('http://', 'ws://').replace('https://', 'wss://');
if (!url.endsWith('/')) {
@ -85,6 +85,10 @@ export class RoomConnection implements RoomConnection {
url += '&bottom='+Math.floor(viewport.bottom);
url += '&left='+Math.floor(viewport.left);
url += '&right='+Math.floor(viewport.right);
if (typeof companion === 'string') {
url += '&companion='+encodeURIComponent(companion);
}
if (RoomConnection.websocketFactory) {
this.socket = RoomConnection.websocketFactory(url);
@ -322,11 +326,14 @@ export class RoomConnection implements RoomConnection {
}
})
const companion = message.getCompanion();
return {
userId: message.getUserid(),
name: message.getName(),
characterLayers,
position: ProtobufClientUtils.toPointInterface(position)
position: ProtobufClientUtils.toPointInterface(position),
companion: companion ? companion.getName() : null
}
}

View File

@ -0,0 +1,221 @@
import Sprite = Phaser.GameObjects.Sprite;
import Container = Phaser.GameObjects.Container;
import { lazyLoadCompanionResource } from "./CompanionTexturesLoadingManager";
import { PlayerAnimationDirections, PlayerAnimationTypes } from "../Player/Animation";
export interface CompanionStatus {
x: number;
y: number;
name: string;
moving: boolean;
direction: PlayerAnimationDirections;
}
export class Companion extends Container {
public sprites: Map<string, Sprite>;
private delta: number;
private invisible: boolean;
private updateListener: Function;
private target: { x: number, y: number, direction: PlayerAnimationDirections };
private companionName: string;
private direction: PlayerAnimationDirections;
private animationType: PlayerAnimationTypes;
constructor(scene: Phaser.Scene, x: number, y: number, name: string, texturePromise: Promise<string>) {
super(scene, x + 14, y + 4);
this.sprites = new Map<string, Sprite>();
this.delta = 0;
this.invisible = true;
this.target = { x, y, direction: PlayerAnimationDirections.Down };
this.direction = PlayerAnimationDirections.Down;
this.animationType = PlayerAnimationTypes.Idle;
this.companionName = name;
texturePromise.then(resource => {
this.addResource(resource);
this.invisible = false;
})
this.scene.physics.world.enableBody(this);
this.getBody().setImmovable(true);
this.getBody().setCollideWorldBounds(false);
this.setSize(16, 16);
this.getBody().setSize(16, 16);
this.getBody().setOffset(0, 8);
this.setDepth(-1);
this.updateListener = this.step.bind(this);
this.scene.events.addListener('update', this.updateListener);
this.scene.add.existing(this);
}
public setTarget(x: number, y: number, direction: PlayerAnimationDirections) {
this.target = { x, y: y + 4, direction };
}
public step(time: number, delta: number) {
if (typeof this.target === 'undefined') return;
this.delta += delta;
if (this.delta < 128) {
return;
}
this.delta = 0;
const xDist = this.target.x - this.x;
const yDist = this.target.y - this.y;
const distance = Math.pow(xDist, 2) + Math.pow(yDist, 2);
if (distance < 650) {
this.animationType = PlayerAnimationTypes.Idle;
this.direction = this.target.direction;
this.getBody().stop();
} else {
this.animationType = PlayerAnimationTypes.Walk;
const xDir = xDist / Math.max(Math.abs(xDist), 1);
const yDir = yDist / Math.max(Math.abs(yDist), 1);
const speed = 256;
this.getBody().setVelocity(Math.min(Math.abs(xDist * 2.5), speed) * xDir, Math.min(Math.abs(yDist * 2.5), speed) * yDir);
if (Math.abs(xDist) > Math.abs(yDist)) {
if (xDist < 0) {
this.direction = PlayerAnimationDirections.Left;
} else {
this.direction = PlayerAnimationDirections.Right;
}
} else {
if (yDist < 0) {
this.direction = PlayerAnimationDirections.Up;
} else {
this.direction = PlayerAnimationDirections.Down;
}
}
}
this.setDepth(this.y);
this.playAnimation(this.direction, this.animationType);
}
public getStatus(): CompanionStatus {
const { x, y, direction, animationType, companionName } = this;
return {
x,
y,
direction,
moving: animationType === PlayerAnimationTypes.Walk,
name: companionName
}
}
private playAnimation(direction: PlayerAnimationDirections, type: PlayerAnimationTypes): void {
if (this.invisible) return;
for (const [resource, sprite] of this.sprites.entries()) {
sprite.play(`${resource}-${direction}-${type}`, true);
}
}
private addResource(resource: string, frame?: string | number): void {
const sprite = new Sprite(this.scene, 0, 0, resource, frame);
this.add(sprite);
this.getAnimations(resource).forEach(animation => {
this.scene.anims.create(animation);
});
this.scene.sys.updateList.add(sprite);
this.sprites.set(resource, sprite);
}
private getAnimations(resource: string): Phaser.Types.Animations.Animation[] {
return [
{
key: `${resource}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Idle}`,
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [1]}),
frameRate: 10,
repeat: 1
},
{
key: `${resource}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Idle}`,
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [4]}),
frameRate: 10,
repeat: 1
},
{
key: `${resource}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Idle}`,
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [7]}),
frameRate: 10,
repeat: 1
},
{
key: `${resource}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Idle}`,
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [10]}),
frameRate: 10,
repeat: 1
},
{
key: `${resource}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Walk}`,
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [0, 1, 2]}),
frameRate: 15,
repeat: -1
},
{
key: `${resource}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Walk}`,
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [3, 4, 5]}),
frameRate: 15,
repeat: -1
},
{
key: `${resource}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Walk}`,
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [6, 7, 8]}),
frameRate: 15,
repeat: -1
},
{
key: `${resource}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Walk}`,
frames: this.scene.anims.generateFrameNumbers(resource, {frames: [9, 10, 11]}),
frameRate: 15,
repeat: -1
}
]
}
private getBody(): Phaser.Physics.Arcade.Body {
const body = this.body;
if (!(body instanceof Phaser.Physics.Arcade.Body)) {
throw new Error('Container does not have arcade body');
}
return body;
}
public destroy(): void {
for (const sprite of this.sprites.values()) {
if (this.scene) {
this.scene.sys.updateList.remove(sprite);
}
}
if (this.scene) {
this.scene.events.removeListener('update', this.updateListener);
}
super.destroy();
}
}

View File

@ -0,0 +1,14 @@
export interface CompanionResourceDescriptionInterface {
name: string,
img: string,
behaviour: "dog" | "cat"
}
export const COMPANION_RESOURCES: CompanionResourceDescriptionInterface[] = [
{ name: "dog1", img: "resources/characters/pipoya/Dog 01-1.png", behaviour: "dog" },
{ name: "dog2", img: "resources/characters/pipoya/Dog 01-2.png", behaviour: "dog" },
{ name: "dog3", img: "resources/characters/pipoya/Dog 01-3.png", behaviour: "dog" },
{ name: "cat1", img: "resources/characters/pipoya/Cat 01-1.png", behaviour: "cat" },
{ name: "cat2", img: "resources/characters/pipoya/Cat 01-2.png", behaviour: "cat" },
{ name: "cat3", img: "resources/characters/pipoya/Cat 01-3.png", behaviour: "cat" },
]

View File

@ -0,0 +1,29 @@
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
import { COMPANION_RESOURCES, CompanionResourceDescriptionInterface } from "./CompanionTextures";
export const getAllCompanionResources = (loader: LoaderPlugin): CompanionResourceDescriptionInterface[] => {
COMPANION_RESOURCES.forEach((resource: CompanionResourceDescriptionInterface) => {
lazyLoadCompanionResource(loader, resource.name);
});
return COMPANION_RESOURCES;
}
export const lazyLoadCompanionResource = (loader: LoaderPlugin, name: string): Promise<string> => {
return new Promise((resolve, reject) => {
const resource = COMPANION_RESOURCES.find(item => item.name === name);
if (typeof resource === 'undefined') {
return reject(`Texture '${name}' not found!`);
}
if (loader.textureManager.exists(resource.name)) {
return resolve(resource.name);
}
loader.spritesheet(resource.name, resource.img, { frameWidth: 32, frameHeight: 32, endFrame: 12 });
loader.once(`filecomplete-spritesheet-${resource.name}`, () => resolve(resource.name));
loader.start(); // It's only automatically started during the Scene preload.
});
}

View File

@ -4,6 +4,7 @@ import BitmapText = Phaser.GameObjects.BitmapText;
import Container = Phaser.GameObjects.Container;
import Sprite = Phaser.GameObjects.Sprite;
import {TextureError} from "../../Exception/TextureError";
import {Companion} from "../Companion/Companion";
interface AnimationData {
key: string;
@ -21,6 +22,7 @@ export abstract class Character extends Container {
private lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down;
//private teleportation: Sprite;
private invisible: boolean;
public companion?: Companion;
constructor(scene: Phaser.Scene,
x: number,
@ -69,6 +71,12 @@ export abstract class Character extends Container {
this.playAnimation(direction, moving);
}
public addCompanion(name: string, texturePromise?: Promise<string>): void {
if (typeof texturePromise !== 'undefined') {
this.companion = new Companion(this.scene, this.x, this.y, name, texturePromise);
}
}
public addTextures(textures: string[], frame?: string | number): void {
for (const texture of textures) {
if(!this.scene.textures.exists(texture)){
@ -189,6 +197,10 @@ export abstract class Character extends Container {
}
this.setDepth(this.y);
if (this.companion) {
this.companion.setTarget(this.x, this.y, this.lastDirection);
}
}
stop(){

View File

@ -17,12 +17,18 @@ export class RemotePlayer extends Character {
name: string,
texturesPromise: Promise<string[]>,
direction: PlayerAnimationDirections,
moving: boolean
moving: boolean,
companion: string|null,
companionTexturePromise?: Promise<string>
) {
super(Scene, x, y, texturesPromise, name, direction, moving, 1);
//set data
this.userId = userId;
if (typeof companion === 'string') {
this.addCompanion(companion, companionTexturePromise);
}
}
updatePosition(position: PointInterface): void {
@ -31,5 +37,9 @@ export class RemotePlayer extends Character {
this.setY(position.y);
this.setDepth(position.y); //this is to make sure the perspective (player models closer the bottom of the screen will appear in front of models nearer the top of the screen).
if (this.companion) {
this.companion.setTarget(position.x, position.y, position.direction as PlayerAnimationDirections);
}
}
}

View File

@ -6,4 +6,5 @@ export interface AddPlayerInterface {
name: string;
characterLayers: BodyResourceDescriptionInterface[];
position: PointInterface;
companion: string|null;
}

View File

@ -21,12 +21,14 @@ export interface HasMovedEvent {
export class GameManager {
private playerName: string|null;
private characterLayers: string[]|null;
private companion: string|null;
private startRoom!:Room;
currentGameSceneName: string|null = null;
constructor() {
this.playerName = localUserStore.getName();
this.characterLayers = localUserStore.getCharacterLayers();
this.companion = localUserStore.getCompanion();
}
public async init(scenePlugin: Phaser.Scenes.ScenePlugin): Promise<string> {
@ -63,6 +65,14 @@ export class GameManager {
return this.characterLayers;
}
setCompanion(companion: string|null): void {
this.companion = companion;
}
getCompanion(): string|null {
return this.companion;
}
public async loadMap(room: Room, scenePlugin: Phaser.Scenes.ScenePlugin): Promise<void> {
const roomID = room.id;

View File

@ -69,6 +69,7 @@ import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
import DOMElement = Phaser.GameObjects.DOMElement;
import {Subscription} from "rxjs";
import {worldFullMessageStream} from "../../Connexion/WorldFullMessageStream";
import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager";
export interface GameSceneInitInterface {
initPosition: PointInterface|null,
@ -159,6 +160,7 @@ export class GameScene extends ResizableScene implements CenterListener {
private openChatIcon!: OpenChatIcon;
private playerName!: string;
private characterLayers!: string[];
private companion!: string|null;
private messageSubscription: Subscription|null = null;
private popUpElements : Map<number, DOMElement> = new Map<number, Phaser.GameObjects.DOMElement>();
private originalMapUrl: string|undefined;
@ -352,7 +354,7 @@ export class GameScene extends ResizableScene implements CenterListener {
}
this.playerName = playerName;
this.characterLayers = gameManager.getCharacterLayers();
this.companion = gameManager.getCompanion();
//initalise map
this.Map = this.add.tilemap(this.MapUrlFile);
@ -476,7 +478,9 @@ export class GameScene extends ResizableScene implements CenterListener {
top: camera.scrollY,
right: camera.scrollX + camera.width,
bottom: camera.scrollY + camera.height,
}).then((onConnect: OnConnectInterface) => {
},
this.companion
).then((onConnect: OnConnectInterface) => {
this.connection = onConnect.connection;
this.connection.onUserJoins((message: MessageUserJoined) => {
@ -484,7 +488,8 @@ export class GameScene extends ResizableScene implements CenterListener {
userId: message.userId,
characterLayers: message.characterLayers,
name: message.name,
position: message.position
position: message.position,
companion: message.companion
}
this.addPlayer(userMessage);
});
@ -870,6 +875,11 @@ ${escapedMessage}
private removeAllRemotePlayers(): void {
this.MapPlayersByKey.forEach((player: RemotePlayer) => {
player.destroy();
if (player.companion) {
player.companion.destroy();
}
this.MapPlayers.remove(player);
});
this.MapPlayersByKey = new Map<number, RemotePlayer>();
@ -1040,7 +1050,9 @@ ${escapedMessage}
texturesPromise,
PlayerAnimationDirections.Down,
false,
this.userInputManager
this.userInputManager,
this.companion,
this.companion !== null ? lazyLoadCompanionResource(this.load, this.companion) : undefined
);
}catch (err){
if(err instanceof TextureError) {
@ -1232,7 +1244,9 @@ ${escapedMessage}
addPlayerData.name,
texturesPromise,
addPlayerData.position.direction as PlayerAnimationDirections,
addPlayerData.position.moving
addPlayerData.position.moving,
addPlayerData.companion,
addPlayerData.companion !== null ? lazyLoadCompanionResource(this.load, addPlayerData.companion) : undefined
);
this.MapPlayers.add(player);
this.MapPlayersByKey.set(player.userId, player);
@ -1255,6 +1269,11 @@ ${escapedMessage}
console.error('Cannot find user with id ', userId);
} else {
player.destroy();
if (player.companion) {
player.companion.destroy();
}
this.MapPlayers.remove(player);
}
this.MapPlayersByKey.delete(userId);

View File

@ -0,0 +1,236 @@
import Image = Phaser.GameObjects.Image;
import Rectangle = Phaser.GameObjects.Rectangle;
import { addLoader } from "../Components/Loader";
import { gameManager} from "../Game/GameManager";
import { ResizableScene } from "./ResizableScene";
import { TextField } from "../Components/TextField";
import { EnableCameraSceneName } from "./EnableCameraScene";
import { localUserStore } from "../../Connexion/LocalUserStore";
import { CompanionResourceDescriptionInterface } from "../Companion/CompanionTextures";
import { getAllCompanionResources } from "../Companion/CompanionTexturesLoadingManager";
export const SelectCompanionSceneName = "SelectCompanionScene";
enum LoginTextures {
playButton = "play_button",
icon = "icon",
mainFont = "main_font"
}
export class SelectCompanionScene extends ResizableScene {
private logo!: Image;
private textField!: TextField;
private pressReturnField!: TextField;
private readonly nbCharactersPerRow = 7;
private selectedRectangle!: Rectangle;
private selectedCompanion!: Phaser.Physics.Arcade.Sprite;
private companions: Array<Phaser.Physics.Arcade.Sprite> = new Array<Phaser.Physics.Arcade.Sprite>();
private companionModels: Array<CompanionResourceDescriptionInterface|null> = [null];
constructor() {
super({
key: SelectCompanionSceneName
});
}
preload() {
addLoader(this);
getAllCompanionResources(this.load).forEach(model => {
this.companionModels.push(model);
});
this.load.image(LoginTextures.icon, "resources/logos/tcm_full.png");
this.load.image(LoginTextures.playButton, "resources/objects/play_button.png");
// Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
this.load.bitmapFont(LoginTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
addLoader(this);
}
create() {
this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Select your companion');
this.pressReturnField = new TextField(
this,
this.game.renderer.width / 2,
90 + 32 * Math.ceil(this.companionModels.length / this.nbCharactersPerRow),
'Press enter to start'
);
const rectangleXStart = this.game.renderer.width / 2 - (this.nbCharactersPerRow / 2) * 32 + 16;
this.selectedRectangle = this.add.rectangle(rectangleXStart, 90, 32, 32).setStrokeStyle(2, 0xFFFFFF);
this.logo = new Image(this, this.game.renderer.width - 30, this.game.renderer.height - 20, LoginTextures.icon);
this.add.existing(this.logo);
// input events
this.input.keyboard.on('keyup-ENTER', this.nextScene.bind(this));
this.input.keyboard.on('keydown-RIGHT', this.selectNext.bind(this));
this.input.keyboard.on('keydown-LEFT', this.selectPrevious.bind(this));
this.input.keyboard.on('keydown-DOWN', this.jumpToNextRow.bind(this));
this.input.keyboard.on('keydown-UP', this.jumpToPreviousRow.bind(this));
this.createCurrentCompanion();
this.selectCompanion(this.getCompanionIndex());
}
update(time: number, delta: number): void {
this.pressReturnField.setVisible(!!(Math.floor(time / 500) % 2));
}
private jumpToPreviousRow(): void {
const index = this.companions.indexOf(this.selectedCompanion) - this.nbCharactersPerRow;
if (index >= 0) {
this.selectCompanion(index);
}
}
private jumpToNextRow(): void {
const index = this.companions.indexOf(this.selectedCompanion) + this.nbCharactersPerRow;
if (index < this.companions.length) {
this.selectCompanion(index);
}
}
private selectPrevious(): void {
const index = this.companions.indexOf(this.selectedCompanion);
this.selectCompanion(index - 1);
}
private selectNext(): void {
const index = this.companions.indexOf(this.selectedCompanion);
this.selectCompanion(index + 1);
}
private selectCompanion(index?: number): void {
if (typeof index === 'undefined') {
index = this.companions.indexOf(this.selectedCompanion);
}
// make sure index is inside possible range
index = Math.min(this.companions.length - 1, Math.max(0, index));
if (this.selectedCompanion === this.companions[index]) {
return;
}
this.selectedCompanion.anims.pause();
this.selectedCompanion = this.companions[index];
this.redrawSelectedRectangle();
const model = this.companionModels[index];
if (model !== null) {
this.selectedCompanion.anims.play(model.name);
}
}
private redrawSelectedRectangle(): void {
this.selectedRectangle.setVisible(true);
this.selectedRectangle.setX(this.selectedCompanion.x);
this.selectedRectangle.setY(this.selectedCompanion.y);
this.selectedRectangle.setSize(32, 32);
}
private storeCompanionSelection(): string|null {
const index = this.companions.indexOf(this.selectedCompanion);
const model = this.companionModels[index];
const companion = model === null ? null : model.name;
localUserStore.setCompanion(companion);
return companion;
}
private nextScene(): void {
const companion = this.storeCompanionSelection();
// next scene
this.scene.stop(SelectCompanionSceneName);
gameManager.setCompanion(companion);
gameManager.tryResumingGame(this, EnableCameraSceneName);
this.scene.remove(SelectCompanionSceneName);
}
private createCurrentCompanion(): void {
for (let i = 0; i < this.companionModels.length; i++) {
const companionResource = this.companionModels[i];
const col = i % this.nbCharactersPerRow;
const row = Math.floor(i / this.nbCharactersPerRow);
const [x, y] = this.getCharacterPosition(col, row);
let name = "null";
if (companionResource !== null) {
name = companionResource.name;
}
const companion = this.physics.add.sprite(x, y, name, 0);
companion.setBounce(0.2);
companion.setCollideWorldBounds(true);
if (companionResource !== null) {
this.anims.create({
key: name,
frames: this.anims.generateFrameNumbers(name, {start: 0, end: 2,}),
frameRate: 10,
repeat: -1
});
}
companion.setInteractive().on("pointerdown", () => {
this.selectCompanion(i);
});
this.companions.push(companion);
}
this.selectedCompanion = this.companions[0];
}
private getCharacterPosition(x: number, y: number): [number, number] {
return [
this.game.renderer.width / 2 + 16 + (x - this.nbCharactersPerRow / 2) * 32,
y * 32 + 90
];
}
public onResize(ev: UIEvent): void {
this.textField.x = this.game.renderer.width / 2;
this.pressReturnField.x = this.game.renderer.width / 2;
this.logo.x = this.game.renderer.width - 30;
this.logo.y = this.game.renderer.height - 20;
for (let i = 0; i < this.companionModels.length; i++) {
const companion = this.companions[i];
const col = i % this.nbCharactersPerRow;
const row = Math.floor(i / this.nbCharactersPerRow);
const [x, y] = this.getCharacterPosition(col, row);
companion.x = x;
companion.y = y;
}
this.redrawSelectedRectangle();
}
private getCompanionIndex(): number {
const companion = localUserStore.getCompanion();
if (companion === null) {
return 0;
}
return this.companionModels.findIndex(model => model !== null && model.name === companion);
}
}

View File

@ -1,5 +1,6 @@
import {LoginScene, LoginSceneName} from "../Login/LoginScene";
import {SelectCharacterScene, SelectCharacterSceneName} from "../Login/SelectCharacterScene";
import {SelectCompanionScene, SelectCompanionSceneName} from "../Login/SelectCompanionScene";
import {gameManager} from "../Game/GameManager";
import {localUserStore} from "../../Connexion/LocalUserStore";
import {mediaManager} from "../../WebRtc/MediaManager";
@ -277,6 +278,10 @@ export class MenuScene extends Phaser.Scene {
this.closeSideMenu();
gameManager.leaveGame(this, SelectCharacterSceneName, new SelectCharacterScene());
break;
case 'changeCompanionButton':
this.closeSideMenu();
gameManager.leaveGame(this, SelectCompanionSceneName, new SelectCompanionScene());
break;
case 'closeButton':
this.closeSideMenu();
break;

View File

@ -3,7 +3,6 @@ import {GameScene} from "../Game/GameScene";
import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager";
import {Character} from "../Entity/Character";
export const hasMovedEventName = "hasMoved";
export interface CurrentGamerInterface extends Character{
moveUser(delta: number) : void;
@ -22,12 +21,18 @@ export class Player extends Character implements CurrentGamerInterface {
texturesPromise: Promise<string[]>,
direction: PlayerAnimationDirections,
moving: boolean,
private userInputManager: UserInputManager
private userInputManager: UserInputManager,
companion: string|null,
companionTexturePromise?: Promise<string>
) {
super(Scene, x, y, texturesPromise, name, direction, moving, 1);
//the current player model should be push away by other players to prevent conflict
this.getBody().setImmovable(false);
if (typeof companion === 'string') {
this.addCompanion(companion, companionTexturePromise);
}
}
moveUser(delta: number): void {
@ -59,6 +64,7 @@ export class Player extends Character implements CurrentGamerInterface {
direction = PlayerAnimationDirections.Right;
moving = true;
}
if (x !== 0 || y !== 0) {
this.move(x, y);
this.emit(hasMovedEventName, {moving, direction, x: this.x, y: this.y});

View File

@ -6,6 +6,7 @@ import {DEBUG_MODE, JITSI_URL, RESOLUTION} from "./Enum/EnvironmentVariable";
import {LoginScene} from "./Phaser/Login/LoginScene";
import {ReconnectingScene} from "./Phaser/Reconnecting/ReconnectingScene";
import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene";
import {SelectCompanionScene} from "./Phaser/Login/SelectCompanionScene";
import {EnableCameraScene} from "./Phaser/Login/EnableCameraScene";
import {CustomizeScene} from "./Phaser/Login/CustomizeScene";
import {ResizableScene} from "./Phaser/Login/ResizableScene";
@ -74,7 +75,7 @@ const config: GameConfig = {
width: width / RESOLUTION,
height: height / RESOLUTION,
parent: "game",
scene: [EntryScene, LoginScene, SelectCharacterScene, EnableCameraScene, ReconnectingScene, ErrorScene, CustomizeScene, MenuScene, HelpCameraSettingsScene],
scene: [EntryScene, LoginScene, SelectCharacterScene, SelectCompanionScene, EnableCameraScene, ReconnectingScene, ErrorScene, CustomizeScene, MenuScene, HelpCameraSettingsScene],
zoom: RESOLUTION,
fps: fps,
dom: {

View File

@ -36,6 +36,10 @@ message CharacterLayerMessage {
string name = 2;
}
message CompanionMessage {
string name = 1;
}
/*********** CLIENT TO SERVER MESSAGES *************/
message PingMessage {
@ -141,6 +145,7 @@ message UserJoinedMessage {
string name = 2;
repeated CharacterLayerMessage characterLayers = 3;
PositionMessage position = 4;
CompanionMessage companion = 5;
}
message UserLeftMessage {
@ -251,6 +256,7 @@ message JoinRoomMessage {
string roomId = 5;
repeated string tag = 6;
string IPAddress = 7;
CompanionMessage companion = 8;
}
message UserJoinedZoneMessage {
@ -259,6 +265,7 @@ message UserJoinedZoneMessage {
repeated CharacterLayerMessage characterLayers = 3;
PositionMessage position = 4;
Zone fromZone = 5;
CompanionMessage companion = 6;
}
message UserLeftZoneMessage {

View File

@ -12,7 +12,7 @@ import {
WebRtcSignalToServerMessage,
PlayGlobalMessage,
ReportPlayerMessage,
QueryJitsiJwtMessage, SendUserMessage, ServerToClientMessage
QueryJitsiJwtMessage, SendUserMessage, ServerToClientMessage, CompanionMessage
} from "../Messages/generated/messages_pb";
import {UserMovesMessage} from "../Messages/generated/messages_pb";
import {TemplatedApp} from "uWebSockets.js"
@ -138,6 +138,14 @@ export class IoSocketController {
const left = Number(query.left);
const right = Number(query.right);
const name = query.name;
let companion: CompanionMessage|undefined = undefined;
if (typeof query.companion === 'string') {
companion = new CompanionMessage();
companion.setName(query.companion);
}
if (typeof name !== 'string') {
throw new Error('Expecting name');
}
@ -221,6 +229,7 @@ export class IoSocketController {
IPAddress,
roomId,
name,
companion,
characterLayers: characterLayerObjs,
messages: memberMessages,
tags: memberTags,
@ -350,6 +359,7 @@ export class IoSocketController {
client.tags = ws.tags;
client.textures = ws.textures;
client.characterLayers = ws.characterLayers;
client.companion = ws.companion;
client.roomId = ws.roomId;
client.listenedZones = new Set<Zone>();
return client;

View File

@ -3,6 +3,7 @@ import {Identificable} from "./Identificable";
import {ViewportInterface} from "_Model/Websocket/ViewportMessage";
import {
BatchMessage,
CompanionMessage,
PusherToBackMessage,
ServerToClientMessage,
SubMessage
@ -29,6 +30,7 @@ export interface ExSocketInterface extends WebSocket, Identificable {
characterLayers: CharacterLayer[];
position: PointInterface;
viewport: ViewportInterface;
companion?: CompanionMessage;
/**
* Pushes an event that will be sent in the next batch of events
*/

View File

@ -5,7 +5,8 @@ import {
CharacterLayerMessage, GroupLeftZoneMessage, GroupUpdateMessage, GroupUpdateZoneMessage,
PointMessage, PositionMessage, UserJoinedMessage,
UserJoinedZoneMessage, UserLeftZoneMessage, UserMovedMessage,
ZoneMessage
ZoneMessage,
CompanionMessage
} from "../Messages/generated/messages_pb";
import * as messages_pb from "../Messages/generated/messages_pb";
import {ClientReadableStream} from "grpc";
@ -30,7 +31,7 @@ export type MovesCallback = (thing: Movable, position: PositionInterface, listen
export type LeavesCallback = (thing: Movable, listener: User) => void;*/
export class UserDescriptor {
private constructor(public readonly userId: number, private name: string, private characterLayers: CharacterLayerMessage[], private position: PositionMessage) {
private constructor(public readonly userId: number, private name: string, private characterLayers: CharacterLayerMessage[], private position: PositionMessage, private companion?: CompanionMessage) {
if (!Number.isInteger(this.userId)) {
throw new Error('UserDescriptor.userId is not an integer: '+this.userId);
}
@ -41,7 +42,7 @@ export class UserDescriptor {
if (position === undefined) {
throw new Error('Missing position');
}
return new UserDescriptor(message.getUserid(), message.getName(), message.getCharacterlayersList(), position);
return new UserDescriptor(message.getUserid(), message.getName(), message.getCharacterlayersList(), position, message.getCompanion());
}
public update(userMovedMessage: UserMovedMessage) {
@ -59,6 +60,7 @@ export class UserDescriptor {
userJoinedMessage.setName(this.name);
userJoinedMessage.setCharacterlayersList(this.characterLayers);
userJoinedMessage.setPosition(this.position);
userJoinedMessage.setCompanion(this.companion)
return userJoinedMessage;
}

View File

@ -153,6 +153,8 @@ export class SocketManager implements ZoneEventListener {
joinRoomMessage.setName(client.name);
joinRoomMessage.setPositionmessage(ProtobufUtils.toPositionMessage(client.position));
joinRoomMessage.setTagList(client.tags);
joinRoomMessage.setCompanion(client.companion);
for (const characterLayer of client.characterLayers) {
const characterLayerMessage = new CharacterLayerMessage();
characterLayerMessage.setName(characterLayer.name);