Merge branch 'develop' into vite
This commit is contained in:
@@ -15,6 +15,8 @@ import { TexturesHelper } from "../Helpers/TexturesHelper";
|
||||
import type { PictureStore } from "../../Stores/PictureStore";
|
||||
import { Unsubscriber, Writable, writable } from "svelte/store";
|
||||
import { createColorStore } from "../../Stores/OutlineColorStore";
|
||||
import type { OutlineableInterface } from "../Game/OutlineableInterface";
|
||||
import type CancelablePromise from "cancelable-promise";
|
||||
|
||||
const playerNameY = -25;
|
||||
|
||||
@@ -28,14 +30,15 @@ interface AnimationData {
|
||||
|
||||
const interactiveRadius = 35;
|
||||
|
||||
export abstract class Character extends Container {
|
||||
export abstract class Character extends Container implements OutlineableInterface {
|
||||
private bubble: SpeechBubble | null = null;
|
||||
private readonly playerName: Text;
|
||||
public PlayerValue: string;
|
||||
private readonly playerNameText: Text;
|
||||
public playerName: string;
|
||||
public sprites: Map<string, Sprite>;
|
||||
protected lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down;
|
||||
//private teleportation: Sprite;
|
||||
private invisible: boolean;
|
||||
private clickable: boolean;
|
||||
public companion?: Companion;
|
||||
private emote: Phaser.GameObjects.DOMElement | null = null;
|
||||
private emoteTween: Phaser.Tweens.Tween | null = null;
|
||||
@@ -43,12 +46,13 @@ export abstract class Character extends Container {
|
||||
private readonly _pictureStore: Writable<string | undefined>;
|
||||
private readonly outlineColorStore = createColorStore();
|
||||
private readonly outlineColorStoreUnsubscribe: Unsubscriber;
|
||||
private texturePromise: CancelablePromise<string[] | void> | undefined;
|
||||
|
||||
constructor(
|
||||
scene: GameScene,
|
||||
x: number,
|
||||
y: number,
|
||||
texturesPromise: Promise<string[]>,
|
||||
texturesPromise: CancelablePromise<string[]>,
|
||||
name: string,
|
||||
direction: PlayerAnimationDirections,
|
||||
moving: boolean,
|
||||
@@ -59,14 +63,15 @@ export abstract class Character extends Container {
|
||||
) {
|
||||
super(scene, x, y /*, texture, frame*/);
|
||||
this.scene = scene;
|
||||
this.PlayerValue = name;
|
||||
this.playerName = name;
|
||||
this.invisible = true;
|
||||
this.clickable = false;
|
||||
|
||||
this.sprites = new Map<string, Sprite>();
|
||||
this._pictureStore = writable(undefined);
|
||||
|
||||
//textures are inside a Promise in case they need to be lazyloaded before use.
|
||||
texturesPromise
|
||||
this.texturePromise = texturesPromise
|
||||
.then((textures) => {
|
||||
this.addTextures(textures, frame);
|
||||
this.invisible = false;
|
||||
@@ -81,9 +86,12 @@ export abstract class Character extends Container {
|
||||
this.invisible = false;
|
||||
this.playAnimation(direction, moving);
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
this.texturePromise = undefined;
|
||||
});
|
||||
|
||||
this.playerName = new Text(scene, 0, playerNameY, name, {
|
||||
this.playerNameText = new Text(scene, 0, playerNameY, name, {
|
||||
fontFamily: '"Press Start 2P"',
|
||||
fontSize: "8px",
|
||||
strokeThickness: 2,
|
||||
@@ -94,30 +102,17 @@ export abstract class Character extends Container {
|
||||
fontSize: 35,
|
||||
},
|
||||
});
|
||||
this.playerName.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||
this.add(this.playerName);
|
||||
this.playerNameText.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||
this.add(this.playerNameText);
|
||||
|
||||
if (isClickable) {
|
||||
this.setInteractive({
|
||||
hitArea: new Phaser.Geom.Circle(0, 0, interactiveRadius),
|
||||
hitAreaCallback: Phaser.Geom.Circle.Contains, //eslint-disable-line @typescript-eslint/unbound-method
|
||||
useHandCursor: true,
|
||||
});
|
||||
|
||||
this.on("pointerover", () => {
|
||||
this.outlineColorStore.pointerOver();
|
||||
});
|
||||
this.on("pointerout", () => {
|
||||
this.outlineColorStore.pointerOut();
|
||||
});
|
||||
}
|
||||
this.setClickable(isClickable);
|
||||
|
||||
this.outlineColorStoreUnsubscribe = this.outlineColorStore.subscribe((color) => {
|
||||
if (color === undefined) {
|
||||
this.getOutlinePlugin()?.remove(this.playerName);
|
||||
this.getOutlinePlugin()?.remove(this.playerNameText);
|
||||
} else {
|
||||
this.getOutlinePlugin()?.remove(this.playerName);
|
||||
this.getOutlinePlugin()?.add(this.playerName, {
|
||||
this.getOutlinePlugin()?.remove(this.playerNameText);
|
||||
this.getOutlinePlugin()?.add(this.playerNameText, {
|
||||
thickness: 2,
|
||||
outlineColor: color,
|
||||
});
|
||||
@@ -140,6 +135,34 @@ export abstract class Character extends Container {
|
||||
}
|
||||
}
|
||||
|
||||
public setClickable(clickable: boolean = true): void {
|
||||
if (this.clickable === clickable) {
|
||||
return;
|
||||
}
|
||||
this.clickable = clickable;
|
||||
if (clickable) {
|
||||
this.setInteractive({
|
||||
hitArea: new Phaser.Geom.Circle(0, 0, interactiveRadius),
|
||||
hitAreaCallback: Phaser.Geom.Circle.Contains, //eslint-disable-line @typescript-eslint/unbound-method
|
||||
useHandCursor: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.disableInteractive();
|
||||
}
|
||||
|
||||
public isClickable() {
|
||||
return this.clickable;
|
||||
}
|
||||
|
||||
public getPosition(): { x: number; y: number } {
|
||||
return { x: this.x, y: this.y };
|
||||
}
|
||||
|
||||
public getObjectToOutline(): Phaser.GameObjects.GameObject {
|
||||
return this.playerNameText;
|
||||
}
|
||||
|
||||
private async getSnapshot(): Promise<string> {
|
||||
const sprites = Array.from(this.sprites.values()).map((sprite) => {
|
||||
return { sprite, frame: 1 };
|
||||
@@ -326,6 +349,7 @@ export abstract class Character extends Container {
|
||||
this.scene.sys.updateList.remove(sprite);
|
||||
}
|
||||
}
|
||||
this.texturePromise?.cancel();
|
||||
this.list.forEach((objectContaining) => objectContaining.destroy());
|
||||
this.outlineColorStoreUnsubscribe();
|
||||
super.destroy();
|
||||
@@ -408,18 +432,42 @@ export abstract class Character extends Container {
|
||||
private destroyEmote() {
|
||||
this.emote?.destroy();
|
||||
this.emote = null;
|
||||
this.playerName.setVisible(true);
|
||||
this.playerNameText.setVisible(true);
|
||||
}
|
||||
|
||||
public get pictureStore(): PictureStore {
|
||||
return this._pictureStore;
|
||||
}
|
||||
|
||||
public setOutlineColor(color: number): void {
|
||||
this.outlineColorStore.setColor(color);
|
||||
public setFollowOutlineColor(color: number): void {
|
||||
this.outlineColorStore.setFollowColor(color);
|
||||
}
|
||||
|
||||
public removeOutlineColor(): void {
|
||||
this.outlineColorStore.removeColor();
|
||||
public removeFollowOutlineColor(): void {
|
||||
this.outlineColorStore.removeFollowColor();
|
||||
}
|
||||
|
||||
public setApiOutlineColor(color: number): void {
|
||||
this.outlineColorStore.setApiColor(color);
|
||||
}
|
||||
|
||||
public removeApiOutlineColor(): void {
|
||||
this.outlineColorStore.removeApiColor();
|
||||
}
|
||||
|
||||
public pointerOverOutline(): void {
|
||||
this.outlineColorStore.pointerOver();
|
||||
}
|
||||
|
||||
public pointerOutOutline(): void {
|
||||
this.outlineColorStore.pointerOut();
|
||||
}
|
||||
|
||||
public characterCloseByOutline(): void {
|
||||
this.outlineColorStore.characterCloseBy();
|
||||
}
|
||||
|
||||
public characterFarAwayOutline(): void {
|
||||
this.outlineColorStore.characterFarAway();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
||||
import type { CharacterTexture } from "../../Connexion/LocalUser";
|
||||
import { BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES } from "./PlayerTextures";
|
||||
import CancelablePromise from "cancelable-promise";
|
||||
|
||||
export interface FrameConfig {
|
||||
frameWidth: number;
|
||||
@@ -30,7 +31,7 @@ export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptio
|
||||
export const loadCustomTexture = (
|
||||
loaderPlugin: LoaderPlugin,
|
||||
texture: CharacterTexture
|
||||
): Promise<BodyResourceDescriptionInterface> => {
|
||||
): CancelablePromise<BodyResourceDescriptionInterface> => {
|
||||
const name = "customCharacterTexture" + texture.id;
|
||||
const playerResourceDescriptor: BodyResourceDescriptionInterface = { name, img: texture.url, level: texture.level };
|
||||
return createLoadingPromise(loaderPlugin, playerResourceDescriptor, {
|
||||
@@ -42,8 +43,8 @@ export const loadCustomTexture = (
|
||||
export const lazyLoadPlayerCharacterTextures = (
|
||||
loadPlugin: LoaderPlugin,
|
||||
texturekeys: Array<string | BodyResourceDescriptionInterface>
|
||||
): Promise<string[]> => {
|
||||
const promisesList: Promise<unknown>[] = [];
|
||||
): CancelablePromise<string[]> => {
|
||||
const promisesList: CancelablePromise<unknown>[] = [];
|
||||
texturekeys.forEach((textureKey: string | BodyResourceDescriptionInterface) => {
|
||||
try {
|
||||
//TODO refactor
|
||||
@@ -60,12 +61,12 @@ export const lazyLoadPlayerCharacterTextures = (
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
let returnPromise: Promise<Array<string | BodyResourceDescriptionInterface>>;
|
||||
let returnPromise: CancelablePromise<Array<string | BodyResourceDescriptionInterface>>;
|
||||
if (promisesList.length > 0) {
|
||||
loadPlugin.start();
|
||||
returnPromise = Promise.all(promisesList).then(() => texturekeys);
|
||||
returnPromise = CancelablePromise.all(promisesList).then(() => texturekeys);
|
||||
} else {
|
||||
returnPromise = Promise.resolve(texturekeys);
|
||||
returnPromise = CancelablePromise.resolve(texturekeys);
|
||||
}
|
||||
|
||||
//If the loading fail, we render the default model instead.
|
||||
@@ -98,10 +99,17 @@ export const createLoadingPromise = (
|
||||
playerResourceDescriptor: BodyResourceDescriptionInterface,
|
||||
frameConfig: FrameConfig
|
||||
) => {
|
||||
return new Promise<BodyResourceDescriptionInterface>((res, rej) => {
|
||||
return new CancelablePromise<BodyResourceDescriptionInterface>((res, rej, cancel) => {
|
||||
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
||||
return res(playerResourceDescriptor);
|
||||
}
|
||||
|
||||
cancel(() => {
|
||||
loadPlugin.off("loaderror");
|
||||
loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.name);
|
||||
return;
|
||||
});
|
||||
|
||||
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, frameConfig);
|
||||
const errorCallback = (file: { src: string }) => {
|
||||
if (file.src !== playerResourceDescriptor.img) return;
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
import { requestVisitCardsStore } from "../../Stores/GameStore";
|
||||
import { ActionsMenuData, actionsMenuStore } from "../../Stores/ActionsMenuStore";
|
||||
import { Character } from "../Entity/Character";
|
||||
import type { GameScene } from "../Game/GameScene";
|
||||
import type { PointInterface } from "../../Connexion/ConnexionModels";
|
||||
import { Character } from "../Entity/Character";
|
||||
import type { PlayerAnimationDirections } from "../Player/Animation";
|
||||
import { requestVisitCardsStore } from "../../Stores/GameStore";
|
||||
import type { Unsubscriber } from "svelte/store";
|
||||
import type { ActivatableInterface } from "../Game/ActivatableInterface";
|
||||
import type CancelablePromise from "cancelable-promise";
|
||||
|
||||
/**
|
||||
* Class representing the sprite of a remote player (a player that plays on another computer)
|
||||
*/
|
||||
export class RemotePlayer extends Character {
|
||||
userId: number;
|
||||
export class RemotePlayer extends Character implements ActivatableInterface {
|
||||
public userId: number;
|
||||
public readonly activationRadius: number;
|
||||
|
||||
private registeredActions: { actionName: string; callback: Function }[];
|
||||
private visitCardUrl: string | null;
|
||||
private isActionsMenuInitialized: boolean = false;
|
||||
private actionsMenuStoreUnsubscriber: Unsubscriber;
|
||||
|
||||
constructor(
|
||||
userId: number,
|
||||
@@ -17,39 +26,31 @@ export class RemotePlayer extends Character {
|
||||
x: number,
|
||||
y: number,
|
||||
name: string,
|
||||
texturesPromise: Promise<string[]>,
|
||||
texturesPromise: CancelablePromise<string[]>,
|
||||
direction: PlayerAnimationDirections,
|
||||
moving: boolean,
|
||||
visitCardUrl: string | null,
|
||||
companion: string | null,
|
||||
companionTexturePromise?: Promise<string>
|
||||
companionTexturePromise?: Promise<string>,
|
||||
activationRadius?: number
|
||||
) {
|
||||
super(
|
||||
Scene,
|
||||
x,
|
||||
y,
|
||||
texturesPromise,
|
||||
name,
|
||||
direction,
|
||||
moving,
|
||||
1,
|
||||
!!visitCardUrl,
|
||||
companion,
|
||||
companionTexturePromise
|
||||
);
|
||||
super(Scene, x, y, texturesPromise, name, direction, moving, 1, true, companion, companionTexturePromise);
|
||||
|
||||
//set data
|
||||
this.userId = userId;
|
||||
this.registeredActions = [];
|
||||
this.registerDefaultActionsMenuActions();
|
||||
this.setClickable(this.registeredActions.length > 0);
|
||||
this.activationRadius = activationRadius ?? 96;
|
||||
this.visitCardUrl = visitCardUrl;
|
||||
|
||||
this.on("pointerdown", (event: Phaser.Input.Pointer) => {
|
||||
if (event.downElement.nodeName === "CANVAS") {
|
||||
requestVisitCardsStore.set(this.visitCardUrl);
|
||||
}
|
||||
this.actionsMenuStoreUnsubscriber = actionsMenuStore.subscribe((value: ActionsMenuData | undefined) => {
|
||||
this.isActionsMenuInitialized = value ? true : false;
|
||||
});
|
||||
|
||||
this.bindEventHandlers();
|
||||
}
|
||||
|
||||
updatePosition(position: PointInterface): void {
|
||||
public updatePosition(position: PointInterface): void {
|
||||
this.playAnimation(position.direction as PlayerAnimationDirections, position.moving);
|
||||
this.setX(position.x);
|
||||
this.setY(position.y);
|
||||
@@ -60,4 +61,66 @@ export class RemotePlayer extends Character {
|
||||
this.companion.setTarget(position.x, position.y, position.direction as PlayerAnimationDirections);
|
||||
}
|
||||
}
|
||||
|
||||
public registerActionsMenuAction(action: { actionName: string; callback: Function }): void {
|
||||
this.registeredActions.push(action);
|
||||
this.updateIsClickable();
|
||||
}
|
||||
|
||||
public unregisterActionsMenuAction(actionName: string) {
|
||||
const index = this.registeredActions.findIndex((action) => action.actionName === actionName);
|
||||
if (index !== -1) {
|
||||
this.registeredActions.splice(index, 1);
|
||||
}
|
||||
this.updateIsClickable();
|
||||
}
|
||||
|
||||
public activate(): void {
|
||||
this.toggleActionsMenu();
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this.actionsMenuStoreUnsubscriber();
|
||||
actionsMenuStore.clear();
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
public isActivatable(): boolean {
|
||||
return this.isClickable();
|
||||
}
|
||||
|
||||
private updateIsClickable(): void {
|
||||
this.setClickable(this.registeredActions.length > 0);
|
||||
}
|
||||
|
||||
private toggleActionsMenu(): void {
|
||||
if (this.isActionsMenuInitialized) {
|
||||
actionsMenuStore.clear();
|
||||
return;
|
||||
}
|
||||
actionsMenuStore.initialize(this.playerName);
|
||||
for (const action of this.registeredActions) {
|
||||
actionsMenuStore.addAction(action.actionName, action.callback);
|
||||
}
|
||||
}
|
||||
|
||||
private registerDefaultActionsMenuActions(): void {
|
||||
if (this.visitCardUrl) {
|
||||
this.registeredActions.push({
|
||||
actionName: "Visiting Card",
|
||||
callback: () => {
|
||||
requestVisitCardsStore.set(this.visitCardUrl);
|
||||
actionsMenuStore.clear();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private bindEventHandlers(): void {
|
||||
this.on(Phaser.Input.Events.POINTER_DOWN, (event: Phaser.Input.Pointer) => {
|
||||
if (event.downElement.nodeName === "CANVAS") {
|
||||
this.toggleActionsMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface ActivatableInterface {
|
||||
readonly activationRadius: number;
|
||||
isActivatable: () => boolean;
|
||||
activate: () => void;
|
||||
getPosition: () => { x: number; y: number };
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import { isOutlineable } from "../../Utils/CustomTypeGuards";
|
||||
import { MathUtils } from "../../Utils/MathUtils";
|
||||
import type { Player } from "../Player/Player";
|
||||
import type { ActivatableInterface } from "./ActivatableInterface";
|
||||
|
||||
export class ActivatablesManager {
|
||||
// The item that can be selected by pressing the space key.
|
||||
private selectedActivatableObjectByDistance?: ActivatableInterface;
|
||||
private selectedActivatableObjectByPointer?: ActivatableInterface;
|
||||
private activatableObjectsDistances: Map<ActivatableInterface, number> = new Map<ActivatableInterface, number>();
|
||||
|
||||
private currentPlayer: Player;
|
||||
|
||||
constructor(currentPlayer: Player) {
|
||||
this.currentPlayer = currentPlayer;
|
||||
}
|
||||
|
||||
public handlePointerOverActivatableObject(object: ActivatableInterface): void {
|
||||
if (this.selectedActivatableObjectByPointer === object) {
|
||||
return;
|
||||
}
|
||||
if (isOutlineable(this.selectedActivatableObjectByDistance)) {
|
||||
this.selectedActivatableObjectByDistance?.characterFarAwayOutline();
|
||||
}
|
||||
if (isOutlineable(this.selectedActivatableObjectByPointer)) {
|
||||
this.selectedActivatableObjectByPointer?.pointerOutOutline();
|
||||
}
|
||||
this.selectedActivatableObjectByPointer = object;
|
||||
if (isOutlineable(this.selectedActivatableObjectByPointer)) {
|
||||
this.selectedActivatableObjectByPointer?.pointerOverOutline();
|
||||
}
|
||||
}
|
||||
|
||||
public handlePointerOutActivatableObject(): void {
|
||||
if (isOutlineable(this.selectedActivatableObjectByPointer)) {
|
||||
this.selectedActivatableObjectByPointer?.pointerOutOutline();
|
||||
}
|
||||
this.selectedActivatableObjectByPointer = undefined;
|
||||
if (isOutlineable(this.selectedActivatableObjectByDistance)) {
|
||||
this.selectedActivatableObjectByDistance?.characterCloseByOutline();
|
||||
}
|
||||
}
|
||||
|
||||
public getSelectedActivatableObject(): ActivatableInterface | undefined {
|
||||
return this.selectedActivatableObjectByPointer ?? this.selectedActivatableObjectByDistance;
|
||||
}
|
||||
|
||||
public deduceSelectedActivatableObjectByDistance(): void {
|
||||
const newNearestObject = this.findNearestActivatableObject();
|
||||
if (this.selectedActivatableObjectByDistance === newNearestObject) {
|
||||
return;
|
||||
}
|
||||
// update value but do not change the outline
|
||||
if (this.selectedActivatableObjectByPointer) {
|
||||
this.selectedActivatableObjectByDistance = newNearestObject;
|
||||
return;
|
||||
}
|
||||
if (isOutlineable(this.selectedActivatableObjectByDistance)) {
|
||||
this.selectedActivatableObjectByDistance?.characterFarAwayOutline();
|
||||
}
|
||||
this.selectedActivatableObjectByDistance = newNearestObject;
|
||||
if (isOutlineable(this.selectedActivatableObjectByDistance)) {
|
||||
this.selectedActivatableObjectByDistance?.characterCloseByOutline();
|
||||
}
|
||||
}
|
||||
|
||||
private findNearestActivatableObject(): ActivatableInterface | undefined {
|
||||
let shortestDistance: number = Infinity;
|
||||
let closestObject: ActivatableInterface | undefined = undefined;
|
||||
|
||||
for (const [object, distance] of this.activatableObjectsDistances.entries()) {
|
||||
if (object.isActivatable() && object.activationRadius > distance && shortestDistance > distance) {
|
||||
shortestDistance = distance;
|
||||
closestObject = object;
|
||||
}
|
||||
}
|
||||
return closestObject;
|
||||
}
|
||||
public updateActivatableObjectsDistances(objects: ActivatableInterface[]): void {
|
||||
const currentPlayerPos = this.currentPlayer.getPosition();
|
||||
for (const object of objects) {
|
||||
const distance = MathUtils.distanceBetween(currentPlayerPos, object.getPosition());
|
||||
this.activatableObjectsDistances.set(object, distance);
|
||||
}
|
||||
}
|
||||
|
||||
public updateDistanceForSingleActivatableObject(object: ActivatableInterface): void {
|
||||
this.activatableObjectsDistances.set(
|
||||
object,
|
||||
MathUtils.distanceBetween(this.currentPlayer.getPosition(), object.getPosition())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,7 @@ export class GameMapPropertiesListener {
|
||||
let websitePolicyProperty: string | undefined;
|
||||
let websitePositionProperty: number | undefined;
|
||||
let websiteTriggerProperty: string | undefined;
|
||||
let websiteTriggerMessageProperty: string | undefined;
|
||||
|
||||
layer.properties.forEach((property) => {
|
||||
switch (property.name) {
|
||||
@@ -84,6 +85,9 @@ export class GameMapPropertiesListener {
|
||||
case GameMapProperties.OPEN_WEBSITE_TRIGGER:
|
||||
websiteTriggerProperty = property.value as string | undefined;
|
||||
break;
|
||||
case GameMapProperties.OPEN_WEBSITE_TRIGGER_MESSAGE:
|
||||
websiteTriggerMessageProperty = property.value as string | undefined;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { UserInputManager } from "../UserInput/UserInputManager";
|
||||
import { gameManager } from "./GameManager";
|
||||
import { touchScreenManager } from "../../Touch/TouchScreenManager";
|
||||
import { PinchManager } from "../UserInput/PinchManager";
|
||||
import { waScaleManager, WaScaleManagerEvent } from "../Services/WaScaleManager";
|
||||
import { waScaleManager } from "../Services/WaScaleManager";
|
||||
import { EmoteManager } from "./EmoteManager";
|
||||
import { soundManager } from "./SoundManager";
|
||||
import { SharedVariablesManager } from "./SharedVariablesManager";
|
||||
@@ -49,6 +49,7 @@ import { GameMapPropertiesListener } from "./GameMapPropertiesListener";
|
||||
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||
import { GameMapProperties } from "./GameMapProperties";
|
||||
import { PathfindingManager } from "../../Utils/PathfindingManager";
|
||||
import { ActivatablesManager } from "./ActivatablesManager";
|
||||
import type {
|
||||
GroupCreatedUpdatedMessageInterface,
|
||||
MessageUserMovedInterface,
|
||||
@@ -89,10 +90,8 @@ import { deepCopy } from "deep-copy-ts";
|
||||
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
|
||||
import { MapStore } from "../../Stores/Utils/MapStore";
|
||||
import { followUsersColorStore } from "../../Stores/FollowStore";
|
||||
import Camera = Phaser.Cameras.Scene2D.Camera;
|
||||
import { GameSceneUserInputHandler } from "../UserInput/GameSceneUserInputHandler";
|
||||
import { locale } from "../../i18n/i18n-svelte";
|
||||
|
||||
export interface GameSceneInitInterface {
|
||||
initPosition: PointInterface | null;
|
||||
reconnecting: boolean;
|
||||
@@ -189,8 +188,6 @@ export class GameScene extends DirtyScene {
|
||||
|
||||
private gameMap!: GameMap;
|
||||
private actionableItems: Map<number, ActionableItem> = new Map<number, ActionableItem>();
|
||||
// The item that can be selected by pressing the space key.
|
||||
private outlinedItem: ActionableItem | null = null;
|
||||
public userInputManager!: UserInputManager;
|
||||
private isReconnecting: boolean | undefined = undefined;
|
||||
private playerName!: string;
|
||||
@@ -204,6 +201,7 @@ export class GameScene extends DirtyScene {
|
||||
private emoteManager!: EmoteManager;
|
||||
private cameraManager!: CameraManager;
|
||||
private pathfindingManager!: PathfindingManager;
|
||||
private activatablesManager!: ActivatablesManager;
|
||||
private preloading: boolean = true;
|
||||
private startPositionCalculator!: StartPositionCalculator;
|
||||
private sharedVariablesManager!: SharedVariablesManager;
|
||||
@@ -575,6 +573,14 @@ export class GameScene extends DirtyScene {
|
||||
waScaleManager
|
||||
);
|
||||
|
||||
this.pathfindingManager = new PathfindingManager(
|
||||
this,
|
||||
this.gameMap.getCollisionsGrid(),
|
||||
this.gameMap.getTileDimensions()
|
||||
);
|
||||
|
||||
this.activatablesManager = new ActivatablesManager(this.CurrentPlayer);
|
||||
|
||||
biggestAvailableAreaStore.recompute();
|
||||
this.cameraManager.startFollowPlayer(this.CurrentPlayer);
|
||||
|
||||
@@ -656,10 +662,10 @@ export class GameScene extends DirtyScene {
|
||||
|
||||
this.followUsersColorStoreUnsubscribe = followUsersColorStore.subscribe((color) => {
|
||||
if (color !== undefined) {
|
||||
this.CurrentPlayer.setOutlineColor(color);
|
||||
this.CurrentPlayer.setFollowOutlineColor(color);
|
||||
this.connection?.emitPlayerOutlineColor(color);
|
||||
} else {
|
||||
this.CurrentPlayer.removeOutlineColor();
|
||||
this.CurrentPlayer.removeFollowOutlineColor();
|
||||
this.connection?.emitPlayerOutlineColor(null);
|
||||
}
|
||||
});
|
||||
@@ -676,10 +682,6 @@ export class GameScene extends DirtyScene {
|
||||
);
|
||||
}
|
||||
|
||||
public activateOutlinedItem(): void {
|
||||
this.outlinedItem?.activate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the connection to Pusher.
|
||||
*/
|
||||
@@ -802,11 +804,8 @@ export class GameScene extends DirtyScene {
|
||||
this.simplePeer = new SimplePeer(this.connection);
|
||||
userMessageManager.setReceiveBanListener(this.bannedUser.bind(this));
|
||||
|
||||
//listen event to share position of user
|
||||
this.CurrentPlayer.on(hasMovedEventName, this.pushPlayerPosition.bind(this));
|
||||
this.CurrentPlayer.on(hasMovedEventName, this.outlineItem.bind(this));
|
||||
this.CurrentPlayer.on(hasMovedEventName, (event: HasPlayerMovedEvent) => {
|
||||
this.gameMap.setPosition(event.x, event.y);
|
||||
this.handleCurrentPlayerHasMovedEvent(event);
|
||||
});
|
||||
|
||||
// Set up variables manager
|
||||
@@ -1269,7 +1268,7 @@ ${escapedMessage}
|
||||
openCoWebsite.closable ?? true
|
||||
);
|
||||
|
||||
if (openCoWebsite.lazy !== undefined && !openCoWebsite.lazy) {
|
||||
if (openCoWebsite.lazy === undefined || !openCoWebsite.lazy) {
|
||||
await coWebsiteManager.loadCoWebsite(coWebsite);
|
||||
}
|
||||
|
||||
@@ -1442,12 +1441,12 @@ ${escapedMessage}
|
||||
const green = normalizeColor(message.green);
|
||||
const blue = normalizeColor(message.blue);
|
||||
const color = (red << 16) | (green << 8) | blue;
|
||||
this.CurrentPlayer.setOutlineColor(color);
|
||||
this.CurrentPlayer.setApiOutlineColor(color);
|
||||
this.connection?.emitPlayerOutlineColor(color);
|
||||
});
|
||||
|
||||
iframeListener.registerAnswerer("removePlayerOutline", (message) => {
|
||||
this.CurrentPlayer.removeOutlineColor();
|
||||
this.CurrentPlayer.removeApiOutlineColor();
|
||||
this.connection?.emitPlayerOutlineColor(null);
|
||||
});
|
||||
|
||||
@@ -1680,7 +1679,18 @@ ${escapedMessage}
|
||||
}
|
||||
}
|
||||
|
||||
createCollisionWithPlayer() {
|
||||
private handleCurrentPlayerHasMovedEvent(event: HasPlayerMovedEvent): void {
|
||||
//listen event to share position of user
|
||||
this.pushPlayerPosition(event);
|
||||
this.gameMap.setPosition(event.x, event.y);
|
||||
this.activatablesManager.updateActivatableObjectsDistances([
|
||||
...Array.from(this.MapPlayersByKey.values()),
|
||||
...this.actionableItems.values(),
|
||||
]);
|
||||
this.activatablesManager.deduceSelectedActivatableObjectByDistance();
|
||||
}
|
||||
|
||||
private createCollisionWithPlayer() {
|
||||
//add collision layer
|
||||
for (const phaserLayer of this.gameMap.phaserLayers) {
|
||||
this.physics.add.collider(this.CurrentPlayer, phaserLayer, (object1: GameObject, object2: GameObject) => {
|
||||
@@ -1699,7 +1709,7 @@ ${escapedMessage}
|
||||
}
|
||||
}
|
||||
|
||||
createCurrentPlayer() {
|
||||
private createCurrentPlayer() {
|
||||
//TODO create animation moving between exit and start
|
||||
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.characterLayers);
|
||||
try {
|
||||
@@ -1714,7 +1724,7 @@ ${escapedMessage}
|
||||
this.companion,
|
||||
this.companion !== null ? lazyLoadCompanionResource(this.load, this.companion) : undefined
|
||||
);
|
||||
this.CurrentPlayer.on("pointerdown", (pointer: Phaser.Input.Pointer) => {
|
||||
this.CurrentPlayer.on(Phaser.Input.Events.POINTER_DOWN, (pointer: Phaser.Input.Pointer) => {
|
||||
if (pointer.wasTouch && (pointer.event as TouchEvent).touches.length > 1) {
|
||||
return; //we don't want the menu to open when pinching on a touch screen.
|
||||
}
|
||||
@@ -1757,7 +1767,7 @@ ${escapedMessage}
|
||||
this.createCollisionWithPlayer();
|
||||
}
|
||||
|
||||
pushPlayerPosition(event: HasPlayerMovedEvent) {
|
||||
private pushPlayerPosition(event: HasPlayerMovedEvent) {
|
||||
if (this.lastMoveEventSent === event) {
|
||||
return;
|
||||
}
|
||||
@@ -1783,49 +1793,6 @@ ${escapedMessage}
|
||||
// Otherwise, do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the correct item to outline and outline it (if there is an item to be outlined)
|
||||
* @param event
|
||||
*/
|
||||
private outlineItem(event: HasPlayerMovedEvent): void {
|
||||
let x = event.x;
|
||||
let y = event.y;
|
||||
switch (event.direction) {
|
||||
case PlayerAnimationDirections.Up:
|
||||
y -= 32;
|
||||
break;
|
||||
case PlayerAnimationDirections.Down:
|
||||
y += 32;
|
||||
break;
|
||||
case PlayerAnimationDirections.Left:
|
||||
x -= 32;
|
||||
break;
|
||||
case PlayerAnimationDirections.Right:
|
||||
x += 32;
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unexpected direction "' + event.direction + '"');
|
||||
}
|
||||
|
||||
let shortestDistance: number = Infinity;
|
||||
let selectedItem: ActionableItem | null = null;
|
||||
for (const item of this.actionableItems.values()) {
|
||||
const distance = item.actionableDistance(x, y);
|
||||
if (distance !== null && distance < shortestDistance) {
|
||||
shortestDistance = distance;
|
||||
selectedItem = item;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.outlinedItem === selectedItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.outlinedItem?.notSelectable();
|
||||
this.outlinedItem = selectedItem;
|
||||
this.outlinedItem?.selectable();
|
||||
}
|
||||
|
||||
private doPushPlayerPosition(event: HasPlayerMovedEvent): void {
|
||||
this.lastMoveEventSent = event;
|
||||
this.lastSentTick = this.currentTick;
|
||||
@@ -1843,7 +1810,7 @@ ${escapedMessage}
|
||||
* @param time
|
||||
* @param delta The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate.
|
||||
*/
|
||||
update(time: number, delta: number): void {
|
||||
public update(time: number, delta: number): void {
|
||||
this.dirty = false;
|
||||
this.currentTick = time;
|
||||
this.CurrentPlayer.moveUser(delta, this.userInputManager.getEventListForGameTick());
|
||||
@@ -1862,9 +1829,15 @@ ${escapedMessage}
|
||||
case "RemovePlayerEvent":
|
||||
this.doRemovePlayer(event.userId);
|
||||
break;
|
||||
case "UserMovedEvent":
|
||||
case "UserMovedEvent": {
|
||||
this.doUpdatePlayerPosition(event.event);
|
||||
const remotePlayer = this.MapPlayersByKey.get(event.event.userId);
|
||||
if (remotePlayer) {
|
||||
this.activatablesManager.updateDistanceForSingleActivatableObject(remotePlayer);
|
||||
this.activatablesManager.deduceSelectedActivatableObjectByDistance();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "GroupCreatedUpdatedEvent":
|
||||
this.doShareGroupPosition(event.event);
|
||||
break;
|
||||
@@ -1951,11 +1924,21 @@ ${escapedMessage}
|
||||
addPlayerData.companion !== null ? lazyLoadCompanionResource(this.load, addPlayerData.companion) : undefined
|
||||
);
|
||||
if (addPlayerData.outlineColor !== undefined) {
|
||||
player.setOutlineColor(addPlayerData.outlineColor);
|
||||
player.setApiOutlineColor(addPlayerData.outlineColor);
|
||||
}
|
||||
this.MapPlayers.add(player);
|
||||
this.MapPlayersByKey.set(player.userId, player);
|
||||
player.updatePosition(addPlayerData.position);
|
||||
|
||||
player.on(Phaser.Input.Events.POINTER_OVER, () => {
|
||||
this.activatablesManager.handlePointerOverActivatableObject(player);
|
||||
this.markDirty();
|
||||
});
|
||||
|
||||
player.on(Phaser.Input.Events.POINTER_OUT, () => {
|
||||
this.activatablesManager.handlePointerOutActivatableObject();
|
||||
this.markDirty();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1985,7 +1968,7 @@ ${escapedMessage}
|
||||
this.playersPositionInterpolator.removePlayer(userId);
|
||||
}
|
||||
|
||||
public updatePlayerPosition(message: MessageUserMovedInterface): void {
|
||||
private updatePlayerPosition(message: MessageUserMovedInterface): void {
|
||||
this.pendingEvents.enqueue({
|
||||
type: "UserMovedEvent",
|
||||
event: message,
|
||||
@@ -2015,7 +1998,7 @@ ${escapedMessage}
|
||||
this.playersPositionInterpolator.updatePlayerPosition(player.userId, playerMovement);
|
||||
}
|
||||
|
||||
public shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface) {
|
||||
private shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface) {
|
||||
this.pendingEvents.enqueue({
|
||||
type: "GroupCreatedUpdatedEvent",
|
||||
event: groupPositionMessage,
|
||||
@@ -2067,9 +2050,9 @@ ${escapedMessage}
|
||||
return;
|
||||
}
|
||||
if (message.removeOutlineColor) {
|
||||
character.removeOutlineColor();
|
||||
character.removeApiOutlineColor();
|
||||
} else {
|
||||
character.setOutlineColor(message.outlineColor);
|
||||
character.setApiOutlineColor(message.outlineColor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2226,4 +2209,8 @@ ${escapedMessage}
|
||||
public getPathfindingManager(): PathfindingManager {
|
||||
return this.pathfindingManager;
|
||||
}
|
||||
|
||||
public getActivatablesManager(): ActivatablesManager {
|
||||
return this.activatablesManager;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
export interface OutlineableInterface {
|
||||
setFollowOutlineColor(color: number): void;
|
||||
removeFollowOutlineColor(): void;
|
||||
setApiOutlineColor(color: number): void;
|
||||
removeApiOutlineColor(): void;
|
||||
pointerOverOutline(): void;
|
||||
pointerOutOutline(): void;
|
||||
characterCloseByOutline(): void;
|
||||
characterFarAwayOutline(): void;
|
||||
}
|
||||
@@ -5,10 +5,11 @@
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
import type { GameScene } from "../Game/GameScene";
|
||||
import type OutlinePipelinePlugin from "phaser3-rex-plugins/plugins/outlinepipeline-plugin.js";
|
||||
import type { ActivatableInterface } from "../Game/ActivatableInterface";
|
||||
|
||||
type EventCallback = (state: unknown, parameters: unknown) => void;
|
||||
|
||||
export class ActionableItem {
|
||||
export class ActionableItem implements ActivatableInterface {
|
||||
private readonly activationRadiusSquared: number;
|
||||
private isSelectable: boolean = false;
|
||||
private callbacks: Map<string, Array<EventCallback>> = new Map<string, Array<EventCallback>>();
|
||||
@@ -17,7 +18,7 @@ export class ActionableItem {
|
||||
private id: number,
|
||||
private sprite: Sprite,
|
||||
private eventHandler: GameScene,
|
||||
private activationRadius: number,
|
||||
public readonly activationRadius: number,
|
||||
private onActivateCallback: (item: ActionableItem) => void
|
||||
) {
|
||||
this.activationRadiusSquared = activationRadius * activationRadius;
|
||||
@@ -40,6 +41,10 @@ export class ActionableItem {
|
||||
}
|
||||
}
|
||||
|
||||
public getPosition(): { x: number; y: number } {
|
||||
return { x: this.sprite.x, y: this.sprite.y };
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the outline of the sprite.
|
||||
*/
|
||||
@@ -70,9 +75,10 @@ export class ActionableItem {
|
||||
return this.sprite.scene.plugins.get("rexOutlinePipeline") as unknown as OutlinePipelinePlugin | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when the "space" key is pressed and the object is in range of being activated.
|
||||
*/
|
||||
public isActivatable(): boolean {
|
||||
return this.isSelectable;
|
||||
}
|
||||
|
||||
public activate(): void {
|
||||
this.onActivateCallback(this);
|
||||
}
|
||||
|
||||
@@ -3,11 +3,12 @@ import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
|
||||
import { loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
|
||||
import type { CharacterTexture } from "../../Connexion/LocalUser";
|
||||
import type CancelablePromise from "cancelable-promise";
|
||||
|
||||
export abstract class AbstractCharacterScene extends ResizableScene {
|
||||
loadCustomSceneSelectCharacters(): Promise<BodyResourceDescriptionInterface[]> {
|
||||
const textures = this.getTextures();
|
||||
const promises: Promise<BodyResourceDescriptionInterface>[] = [];
|
||||
const promises: CancelablePromise<BodyResourceDescriptionInterface>[] = [];
|
||||
if (textures) {
|
||||
for (const texture of textures) {
|
||||
if (texture.level === -1) {
|
||||
@@ -21,7 +22,7 @@ export abstract class AbstractCharacterScene extends ResizableScene {
|
||||
|
||||
loadSelectSceneCharacters(): Promise<BodyResourceDescriptionInterface[]> {
|
||||
const textures = this.getTextures();
|
||||
const promises: Promise<BodyResourceDescriptionInterface>[] = [];
|
||||
const promises: CancelablePromise<BodyResourceDescriptionInterface>[] = [];
|
||||
if (textures) {
|
||||
for (const texture of textures) {
|
||||
if (texture.level !== -1) {
|
||||
|
||||
@@ -289,7 +289,6 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
gameManager.setCharacterLayers(layers);
|
||||
this.scene.sleep(CustomizeSceneName);
|
||||
waScaleManager.restoreZoom();
|
||||
this.events.removeListener("wake");
|
||||
gameManager.tryResumingGame(EnableCameraSceneName);
|
||||
customCharacterSceneVisibleStore.set(false);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Character } from "../Entity/Character";
|
||||
import { get } from "svelte/store";
|
||||
import { userMovingStore } from "../../Stores/GameStore";
|
||||
import { followStateStore, followRoleStore, followUsersStore } from "../../Stores/FollowStore";
|
||||
import type CancelablePromise from "cancelable-promise";
|
||||
|
||||
export const hasMovedEventName = "hasMoved";
|
||||
export const requestEmoteEventName = "requestEmote";
|
||||
@@ -20,7 +21,7 @@ export class Player extends Character {
|
||||
x: number,
|
||||
y: number,
|
||||
name: string,
|
||||
texturesPromise: Promise<string[]>,
|
||||
texturesPromise: CancelablePromise<string[]>,
|
||||
direction: PlayerAnimationDirections,
|
||||
moving: boolean,
|
||||
companion: string | null,
|
||||
|
||||
@@ -41,8 +41,8 @@ export class WaScaleManager {
|
||||
this.actualZoom = realSize.width / gameSize.width / devicePixelRatio;
|
||||
}
|
||||
|
||||
this.scaleManager.setZoom(this.actualZoom);
|
||||
this.scaleManager.resize(gameSize.width, gameSize.height);
|
||||
this.scaleManager.setZoom(this.actualZoom);
|
||||
|
||||
// Override bug in canvas resizing in Phaser. Let's resize the canvas ourselves
|
||||
const style = this.scaleManager.canvas.style;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { Player } from "../Player/Player";
|
||||
import { RemotePlayer } from "../Entity/RemotePlayer";
|
||||
|
||||
import type { UserInputHandlerInterface } from "../../Interfaces/UserInputHandlerInterface";
|
||||
import type { GameScene } from "../Game/GameScene";
|
||||
|
||||
@@ -19,9 +22,14 @@ export class GameSceneUserInputHandler implements UserInputHandlerInterface {
|
||||
}
|
||||
|
||||
public handlePointerUpEvent(pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]): void {
|
||||
if (pointer.rightButtonReleased() || pointer.getDuration() > 250) {
|
||||
if ((!pointer.wasTouch && pointer.leftButtonReleased()) || pointer.getDuration() > 250) {
|
||||
return;
|
||||
}
|
||||
for (const object of gameObjects) {
|
||||
if (object instanceof Player || object instanceof RemotePlayer) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const camera = this.gameScene.getCameraManager().getCamera();
|
||||
const index = this.gameScene
|
||||
.getGameMap()
|
||||
@@ -45,7 +53,10 @@ export class GameSceneUserInputHandler implements UserInputHandlerInterface {
|
||||
public handlePointerDownEvent(pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]): void {}
|
||||
|
||||
public handleSpaceKeyUpEvent(event: Event): Event {
|
||||
this.gameScene.activateOutlinedItem();
|
||||
const activatable = this.gameScene.getActivatablesManager().getSelectedActivatableObject();
|
||||
if (activatable && activatable.isActivatable()) {
|
||||
activatable.activate();
|
||||
}
|
||||
return event;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user