merged develop

This commit is contained in:
Hanusiak Piotr 2022-02-02 14:32:57 +01:00
commit 288f8ebb61
25 changed files with 624 additions and 153 deletions

View File

@ -47,6 +47,7 @@
"@types/simple-peer": "^9.11.1", "@types/simple-peer": "^9.11.1",
"@types/socket.io-client": "^1.4.32", "@types/socket.io-client": "^1.4.32",
"axios": "^0.21.2", "axios": "^0.21.2",
"cancelable-promise": "^4.2.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"deep-copy-ts": "^0.5.0", "deep-copy-ts": "^0.5.0",
"easystarjs": "^0.4.4", "easystarjs": "^0.4.4",

View File

@ -0,0 +1,99 @@
<script lang="typescript">
import { actionsMenuStore } from "../../Stores/ActionsMenuStore";
import { onDestroy } from "svelte";
import type { Unsubscriber } from "svelte/store";
import type { ActionsMenuData } from "../../Stores/ActionsMenuStore";
let actionsMenuData: ActionsMenuData | undefined;
let actionsMenuStoreUnsubscriber: Unsubscriber | null;
function onKeyDown(e: KeyboardEvent) {
if (e.key === "Escape") {
closeActionsMenu();
}
}
function closeActionsMenu() {
actionsMenuStore.clear();
}
actionsMenuStoreUnsubscriber = actionsMenuStore.subscribe((value) => {
actionsMenuData = value;
});
onDestroy(() => {
if (actionsMenuStoreUnsubscriber) {
actionsMenuStoreUnsubscriber();
}
});
</script>
<svelte:window on:keydown={onKeyDown} />
{#if actionsMenuData}
<div class="actions-menu nes-container is-rounded">
<button type="button" class="nes-btn is-error close" on:click={closeActionsMenu}>&times</button>
<h2>{actionsMenuData.playerName}</h2>
<div class="actions">
{#each [...actionsMenuData.actions] as { actionName, callback }}
<button
type="button"
class="nes-btn"
on:click|preventDefault={() => {
callback();
}}
>
{actionName}
</button>
{/each}
</div>
</div>
{/if}
<style lang="scss">
.actions-menu {
position: absolute;
left: 50%;
transform: translate(-50%, 0);
width: 260px !important;
height: max-content !important;
max-height: 40vh;
margin-top: 200px;
pointer-events: auto;
font-family: "Press Start 2P";
background-color: #333333;
color: whitesmoke;
.actions {
max-height: calc(100% - 50px);
width: 100%;
display: block;
overflow-x: hidden;
overflow-y: auto;
button {
width: calc(100% - 10px);
margin-bottom: 10px;
}
}
.actions::-webkit-scrollbar {
display: none;
}
h2 {
text-align: center;
margin-bottom: 20px;
font-family: "Press Start 2P";
}
.nes-btn.is-error.close {
position: absolute;
top: -20px;
right: -20px;
}
}
</style>

View File

@ -20,7 +20,7 @@
onMount(() => { onMount(() => {
icon.src = `${ICON_URL}/icon?url=${urlObject.hostname}&size=64..96..256&fallback_icon_color=14304c`; icon.src = `${ICON_URL}/icon?url=${urlObject.hostname}&size=64..96..256&fallback_icon_color=14304c`;
icon.alt = urlObject.hostname; icon.alt = coWebsite.altMessage ?? urlObject.hostname;
icon.onload = () => { icon.onload = () => {
iconLoaded = true; iconLoaded = true;
}; };
@ -204,6 +204,10 @@
border-image-outset: 1; border-image-outset: 1;
} }
&:not(.vertical) {
animation: bounce 0.35s ease 6 alternate;
}
&.vertical { &.vertical {
margin: 7px; margin: 7px;
@ -216,6 +220,8 @@
width: 40px; width: 40px;
height: 40px; height: 40px;
} }
animation: shake 0.35s ease-in-out;
} }
&.displayed { &.displayed {
@ -259,6 +265,41 @@
} }
} }
@keyframes bounce {
from {
transform: translateY(0);
}
to {
transform: translateY(-15px);
}
}
@keyframes shake {
0% {
transform: translateX(0);
}
20% {
transform: translateX(-10px);
}
40% {
transform: translateX(10px);
}
60% {
transform: translateX(-10px);
}
80% {
transform: translateX(10px);
}
100% {
transform: translateX(0);
}
}
.cowebsite-icon { .cowebsite-icon {
width: 50px; width: 50px;
height: 50px; height: 50px;

View File

@ -7,7 +7,7 @@
function name(userId: number): string { function name(userId: number): string {
const user = gameScene.MapPlayersByKey.get(userId); const user = gameScene.MapPlayersByKey.get(userId);
return user ? user.PlayerValue : ""; return user ? user.playerName : "";
} }
function acceptFollowRequest() { function acceptFollowRequest() {

View File

@ -36,6 +36,8 @@
import LimitRoomModal from "./Modal/LimitRoomModal.svelte"; import LimitRoomModal from "./Modal/LimitRoomModal.svelte";
import ShareLinkMapModal from "./Modal/ShareLinkMapModal.svelte"; import ShareLinkMapModal from "./Modal/ShareLinkMapModal.svelte";
import { LayoutMode } from "../WebRtc/LayoutManager"; import { LayoutMode } from "../WebRtc/LayoutManager";
import { actionsMenuStore } from "../Stores/ActionsMenuStore";
import ActionsMenu from "./ActionsMenu/ActionsMenu.svelte";
let mainLayout: HTMLDivElement; let mainLayout: HTMLDivElement;
@ -106,6 +108,10 @@
<FollowMenu /> <FollowMenu />
{/if} {/if}
{#if $actionsMenuStore}
<ActionsMenu />
{/if}
{#if $requestVisitCardsStore} {#if $requestVisitCardsStore}
<VisitCard visitCardUrl={$requestVisitCardsStore} /> <VisitCard visitCardUrl={$requestVisitCardsStore} />
{/if} {/if}

View File

@ -44,7 +44,7 @@
pointer-events: auto; pointer-events: auto;
padding: 0; padding: 0;
max-height: 85%; max-height: 200px;
max-width: 85%; max-width: 85%;
&:hover { &:hover {

View File

@ -15,6 +15,8 @@ import { TexturesHelper } from "../Helpers/TexturesHelper";
import type { PictureStore } from "../../Stores/PictureStore"; import type { PictureStore } from "../../Stores/PictureStore";
import { Unsubscriber, Writable, writable } from "svelte/store"; import { Unsubscriber, Writable, writable } from "svelte/store";
import { createColorStore } from "../../Stores/OutlineColorStore"; import { createColorStore } from "../../Stores/OutlineColorStore";
import type { OutlineableInterface } from "../Game/OutlineableInterface";
import type CancelablePromise from "cancelable-promise";
const playerNameY = -25; const playerNameY = -25;
@ -28,15 +30,16 @@ interface AnimationData {
const interactiveRadius = 35; const interactiveRadius = 35;
export abstract class Character extends Container { export abstract class Character extends Container implements OutlineableInterface {
private bubble: SpeechBubble | null = null; private bubble: SpeechBubble | null = null;
private readonly playerName: Text; private readonly playerNameText: Text;
private readonly iconTalk: Phaser.GameObjects.Image; private readonly iconTalk: Phaser.GameObjects.Image;
public PlayerValue: string; public playerName: string;
public sprites: Map<string, Sprite>; public sprites: Map<string, Sprite>;
protected lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down; protected lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down;
//private teleportation: Sprite; //private teleportation: Sprite;
private invisible: boolean; private invisible: boolean;
private clickable: boolean;
public companion?: Companion; public companion?: Companion;
private emote: Phaser.GameObjects.DOMElement | null = null; private emote: Phaser.GameObjects.DOMElement | null = null;
private emoteTween: Phaser.Tweens.Tween | null = null; private emoteTween: Phaser.Tweens.Tween | null = null;
@ -44,12 +47,13 @@ export abstract class Character extends Container {
private readonly _pictureStore: Writable<string | undefined>; private readonly _pictureStore: Writable<string | undefined>;
private readonly outlineColorStore = createColorStore(); private readonly outlineColorStore = createColorStore();
private readonly outlineColorStoreUnsubscribe: Unsubscriber; private readonly outlineColorStoreUnsubscribe: Unsubscriber;
private texturePromise: CancelablePromise<string[] | void> | undefined;
constructor( constructor(
scene: GameScene, scene: GameScene,
x: number, x: number,
y: number, y: number,
texturesPromise: Promise<string[]>, texturesPromise: CancelablePromise<string[]>,
name: string, name: string,
direction: PlayerAnimationDirections, direction: PlayerAnimationDirections,
moving: boolean, moving: boolean,
@ -60,14 +64,15 @@ export abstract class Character extends Container {
) { ) {
super(scene, x, y /*, texture, frame*/); super(scene, x, y /*, texture, frame*/);
this.scene = scene; this.scene = scene;
this.PlayerValue = name; this.playerName = name;
this.invisible = true; this.invisible = true;
this.clickable = false;
this.sprites = new Map<string, Sprite>(); this.sprites = new Map<string, Sprite>();
this._pictureStore = writable(undefined); this._pictureStore = writable(undefined);
//textures are inside a Promise in case they need to be lazyloaded before use. //textures are inside a Promise in case they need to be lazyloaded before use.
texturesPromise this.texturePromise = texturesPromise
.then((textures) => { .then((textures) => {
this.addTextures(textures, frame); this.addTextures(textures, frame);
this.invisible = false; this.invisible = false;
@ -82,9 +87,12 @@ export abstract class Character extends Container {
this.invisible = false; this.invisible = false;
this.playAnimation(direction, moving); 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"', fontFamily: '"Press Start 2P"',
fontSize: "8px", fontSize: "8px",
strokeThickness: 2, strokeThickness: 2,
@ -95,8 +103,6 @@ export abstract class Character extends Container {
fontSize: 35, fontSize: 35,
}, },
}); });
this.playerName.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
this.add(this.playerName);
this.iconTalk = new Phaser.GameObjects.Image(scene, 0, -45, 'iconTalk') this.iconTalk = new Phaser.GameObjects.Image(scene, 0, -45, 'iconTalk')
.setScale(0.15) .setScale(0.15)
@ -109,21 +115,18 @@ export abstract class Character extends Container {
hitAreaCallback: Phaser.Geom.Circle.Contains, //eslint-disable-line @typescript-eslint/unbound-method hitAreaCallback: Phaser.Geom.Circle.Contains, //eslint-disable-line @typescript-eslint/unbound-method
useHandCursor: true, useHandCursor: true,
}); });
this.on("pointerover", () => {
this.outlineColorStore.pointerOver();
});
this.on("pointerout", () => {
this.outlineColorStore.pointerOut();
});
} }
this.playerNameText.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
this.add(this.playerNameText);
this.setClickable(isClickable);
this.outlineColorStoreUnsubscribe = this.outlineColorStore.subscribe((color) => { this.outlineColorStoreUnsubscribe = this.outlineColorStore.subscribe((color) => {
if (color === undefined) { if (color === undefined) {
this.getOutlinePlugin()?.remove(this.playerName); this.getOutlinePlugin()?.remove(this.playerNameText);
} else { } else {
this.getOutlinePlugin()?.remove(this.playerName); this.getOutlinePlugin()?.remove(this.playerNameText);
this.getOutlinePlugin()?.add(this.playerName, { this.getOutlinePlugin()?.add(this.playerNameText, {
thickness: 2, thickness: 2,
outlineColor: color, outlineColor: color,
}); });
@ -146,6 +149,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> { private async getSnapshot(): Promise<string> {
const sprites = Array.from(this.sprites.values()).map((sprite) => { const sprites = Array.from(this.sprites.values()).map((sprite) => {
return { sprite, frame: 1 }; return { sprite, frame: 1 };
@ -336,6 +367,7 @@ export abstract class Character extends Container {
this.scene.sys.updateList.remove(sprite); this.scene.sys.updateList.remove(sprite);
} }
} }
this.texturePromise?.cancel();
this.list.forEach((objectContaining) => objectContaining.destroy()); this.list.forEach((objectContaining) => objectContaining.destroy());
this.outlineColorStoreUnsubscribe(); this.outlineColorStoreUnsubscribe();
super.destroy(); super.destroy();
@ -418,18 +450,42 @@ export abstract class Character extends Container {
private destroyEmote() { private destroyEmote() {
this.emote?.destroy(); this.emote?.destroy();
this.emote = null; this.emote = null;
this.playerName.setVisible(true); this.playerNameText.setVisible(true);
} }
public get pictureStore(): PictureStore { public get pictureStore(): PictureStore {
return this._pictureStore; return this._pictureStore;
} }
public setOutlineColor(color: number): void { public setFollowOutlineColor(color: number): void {
this.outlineColorStore.setColor(color); this.outlineColorStore.setFollowColor(color);
} }
public removeOutlineColor(): void { public removeFollowOutlineColor(): void {
this.outlineColorStore.removeColor(); 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();
} }
} }

View File

@ -1,6 +1,7 @@
import LoaderPlugin = Phaser.Loader.LoaderPlugin; import LoaderPlugin = Phaser.Loader.LoaderPlugin;
import type { CharacterTexture } from "../../Connexion/LocalUser"; import type { CharacterTexture } from "../../Connexion/LocalUser";
import { BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES } from "./PlayerTextures"; import { BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES } from "./PlayerTextures";
import CancelablePromise from "cancelable-promise";
export interface FrameConfig { export interface FrameConfig {
frameWidth: number; frameWidth: number;
@ -30,7 +31,7 @@ export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptio
export const loadCustomTexture = ( export const loadCustomTexture = (
loaderPlugin: LoaderPlugin, loaderPlugin: LoaderPlugin,
texture: CharacterTexture texture: CharacterTexture
): Promise<BodyResourceDescriptionInterface> => { ): CancelablePromise<BodyResourceDescriptionInterface> => {
const name = "customCharacterTexture" + texture.id; const name = "customCharacterTexture" + texture.id;
const playerResourceDescriptor: BodyResourceDescriptionInterface = { name, img: texture.url, level: texture.level }; const playerResourceDescriptor: BodyResourceDescriptionInterface = { name, img: texture.url, level: texture.level };
return createLoadingPromise(loaderPlugin, playerResourceDescriptor, { return createLoadingPromise(loaderPlugin, playerResourceDescriptor, {
@ -42,8 +43,8 @@ export const loadCustomTexture = (
export const lazyLoadPlayerCharacterTextures = ( export const lazyLoadPlayerCharacterTextures = (
loadPlugin: LoaderPlugin, loadPlugin: LoaderPlugin,
texturekeys: Array<string | BodyResourceDescriptionInterface> texturekeys: Array<string | BodyResourceDescriptionInterface>
): Promise<string[]> => { ): CancelablePromise<string[]> => {
const promisesList: Promise<unknown>[] = []; const promisesList: CancelablePromise<unknown>[] = [];
texturekeys.forEach((textureKey: string | BodyResourceDescriptionInterface) => { texturekeys.forEach((textureKey: string | BodyResourceDescriptionInterface) => {
try { try {
//TODO refactor //TODO refactor
@ -60,12 +61,12 @@ export const lazyLoadPlayerCharacterTextures = (
console.error(err); console.error(err);
} }
}); });
let returnPromise: Promise<Array<string | BodyResourceDescriptionInterface>>; let returnPromise: CancelablePromise<Array<string | BodyResourceDescriptionInterface>>;
if (promisesList.length > 0) { if (promisesList.length > 0) {
loadPlugin.start(); loadPlugin.start();
returnPromise = Promise.all(promisesList).then(() => texturekeys); returnPromise = CancelablePromise.all(promisesList).then(() => texturekeys);
} else { } else {
returnPromise = Promise.resolve(texturekeys); returnPromise = CancelablePromise.resolve(texturekeys);
} }
//If the loading fail, we render the default model instead. //If the loading fail, we render the default model instead.
@ -98,10 +99,17 @@ export const createLoadingPromise = (
playerResourceDescriptor: BodyResourceDescriptionInterface, playerResourceDescriptor: BodyResourceDescriptionInterface,
frameConfig: FrameConfig frameConfig: FrameConfig
) => { ) => {
return new Promise<BodyResourceDescriptionInterface>((res, rej) => { return new CancelablePromise<BodyResourceDescriptionInterface>((res, rej, cancel) => {
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) { if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
return res(playerResourceDescriptor); return res(playerResourceDescriptor);
} }
cancel(() => {
loadPlugin.off("loaderror");
loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.name);
return;
});
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, frameConfig); loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, frameConfig);
const errorCallback = (file: { src: string }) => { const errorCallback = (file: { src: string }) => {
if (file.src !== playerResourceDescriptor.img) return; if (file.src !== playerResourceDescriptor.img) return;

View File

@ -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 { GameScene } from "../Game/GameScene";
import type { PointInterface } from "../../Connexion/ConnexionModels"; import type { PointInterface } from "../../Connexion/ConnexionModels";
import { Character } from "../Entity/Character";
import type { PlayerAnimationDirections } from "../Player/Animation"; 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) * Class representing the sprite of a remote player (a player that plays on another computer)
*/ */
export class RemotePlayer extends Character { export class RemotePlayer extends Character implements ActivatableInterface {
userId: number; public userId: number;
public readonly activationRadius: number;
private registeredActions: { actionName: string; callback: Function }[];
private visitCardUrl: string | null; private visitCardUrl: string | null;
private isActionsMenuInitialized: boolean = false;
private actionsMenuStoreUnsubscriber: Unsubscriber;
constructor( constructor(
userId: number, userId: number,
@ -17,39 +26,31 @@ export class RemotePlayer extends Character {
x: number, x: number,
y: number, y: number,
name: string, name: string,
texturesPromise: Promise<string[]>, texturesPromise: CancelablePromise<string[]>,
direction: PlayerAnimationDirections, direction: PlayerAnimationDirections,
moving: boolean, moving: boolean,
visitCardUrl: string | null, visitCardUrl: string | null,
companion: string | null, companion: string | null,
companionTexturePromise?: Promise<string> companionTexturePromise?: Promise<string>,
activationRadius?: number
) { ) {
super( super(Scene, x, y, texturesPromise, name, direction, moving, 1, true, companion, companionTexturePromise);
Scene,
x,
y,
texturesPromise,
name,
direction,
moving,
1,
!!visitCardUrl,
companion,
companionTexturePromise
);
//set data //set data
this.userId = userId; this.userId = userId;
this.registeredActions = [];
this.registerDefaultActionsMenuActions();
this.setClickable(this.registeredActions.length > 0);
this.activationRadius = activationRadius ?? 96;
this.visitCardUrl = visitCardUrl; this.visitCardUrl = visitCardUrl;
this.actionsMenuStoreUnsubscriber = actionsMenuStore.subscribe((value: ActionsMenuData | undefined) => {
this.on("pointerdown", (event: Phaser.Input.Pointer) => { this.isActionsMenuInitialized = value ? true : false;
if (event.downElement.nodeName === "CANVAS") {
requestVisitCardsStore.set(this.visitCardUrl);
}
}); });
this.bindEventHandlers();
} }
updatePosition(position: PointInterface): void { public updatePosition(position: PointInterface): void {
this.playAnimation(position.direction as PlayerAnimationDirections, position.moving); this.playAnimation(position.direction as PlayerAnimationDirections, position.moving);
this.setX(position.x); this.setX(position.x);
this.setY(position.y); this.setY(position.y);
@ -60,4 +61,66 @@ export class RemotePlayer extends Character {
this.companion.setTarget(position.x, position.y, position.direction as PlayerAnimationDirections); 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();
}
});
}
} }

View File

@ -0,0 +1,6 @@
export interface ActivatableInterface {
readonly activationRadius: number;
isActivatable: () => boolean;
activate: () => void;
getPosition: () => { x: number; y: number };
}

View File

@ -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())
);
}
}

View File

@ -66,6 +66,7 @@ export class GameMapPropertiesListener {
let websitePolicyProperty: string | undefined; let websitePolicyProperty: string | undefined;
let websitePositionProperty: number | undefined; let websitePositionProperty: number | undefined;
let websiteTriggerProperty: string | undefined; let websiteTriggerProperty: string | undefined;
let websiteTriggerMessageProperty: string | undefined;
layer.properties.forEach((property) => { layer.properties.forEach((property) => {
switch (property.name) { switch (property.name) {
@ -84,6 +85,9 @@ export class GameMapPropertiesListener {
case GameMapProperties.OPEN_WEBSITE_TRIGGER: case GameMapProperties.OPEN_WEBSITE_TRIGGER:
websiteTriggerProperty = property.value as string | undefined; websiteTriggerProperty = property.value as string | undefined;
break; break;
case GameMapProperties.OPEN_WEBSITE_TRIGGER_MESSAGE:
websiteTriggerMessageProperty = property.value as string | undefined;
break;
} }
}); });

View File

@ -12,7 +12,7 @@ import { UserInputManager } from "../UserInput/UserInputManager";
import { gameManager } from "./GameManager"; import { gameManager } from "./GameManager";
import { touchScreenManager } from "../../Touch/TouchScreenManager"; import { touchScreenManager } from "../../Touch/TouchScreenManager";
import { PinchManager } from "../UserInput/PinchManager"; import { PinchManager } from "../UserInput/PinchManager";
import { waScaleManager, WaScaleManagerEvent } from "../Services/WaScaleManager"; import { waScaleManager } from "../Services/WaScaleManager";
import { EmoteManager } from "./EmoteManager"; import { EmoteManager } from "./EmoteManager";
import { soundManager } from "./SoundManager"; import { soundManager } from "./SoundManager";
import { SharedVariablesManager } from "./SharedVariablesManager"; import { SharedVariablesManager } from "./SharedVariablesManager";
@ -49,6 +49,7 @@ import { GameMapPropertiesListener } from "./GameMapPropertiesListener";
import { analyticsClient } from "../../Administration/AnalyticsClient"; import { analyticsClient } from "../../Administration/AnalyticsClient";
import { GameMapProperties } from "./GameMapProperties"; import { GameMapProperties } from "./GameMapProperties";
import { PathfindingManager } from "../../Utils/PathfindingManager"; import { PathfindingManager } from "../../Utils/PathfindingManager";
import { ActivatablesManager } from "./ActivatablesManager";
import type { import type {
GroupCreatedUpdatedMessageInterface, GroupCreatedUpdatedMessageInterface,
MessageUserMovedInterface, MessageUserMovedInterface,
@ -89,10 +90,8 @@ import { deepCopy } from "deep-copy-ts";
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR; import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
import { MapStore } from "../../Stores/Utils/MapStore"; import { MapStore } from "../../Stores/Utils/MapStore";
import { followUsersColorStore } from "../../Stores/FollowStore"; import { followUsersColorStore } from "../../Stores/FollowStore";
import Camera = Phaser.Cameras.Scene2D.Camera;
import { GameSceneUserInputHandler } from "../UserInput/GameSceneUserInputHandler"; import { GameSceneUserInputHandler } from "../UserInput/GameSceneUserInputHandler";
import { locale } from "../../i18n/i18n-svelte"; import { locale } from "../../i18n/i18n-svelte";
export interface GameSceneInitInterface { export interface GameSceneInitInterface {
initPosition: PointInterface | null; initPosition: PointInterface | null;
reconnecting: boolean; reconnecting: boolean;
@ -189,8 +188,6 @@ export class GameScene extends DirtyScene {
private gameMap!: GameMap; private gameMap!: GameMap;
private actionableItems: Map<number, ActionableItem> = new Map<number, ActionableItem>(); 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; public userInputManager!: UserInputManager;
private isReconnecting: boolean | undefined = undefined; private isReconnecting: boolean | undefined = undefined;
private playerName!: string; private playerName!: string;
@ -204,6 +201,7 @@ export class GameScene extends DirtyScene {
private emoteManager!: EmoteManager; private emoteManager!: EmoteManager;
private cameraManager!: CameraManager; private cameraManager!: CameraManager;
private pathfindingManager!: PathfindingManager; private pathfindingManager!: PathfindingManager;
private activatablesManager!: ActivatablesManager;
private preloading: boolean = true; private preloading: boolean = true;
private startPositionCalculator!: StartPositionCalculator; private startPositionCalculator!: StartPositionCalculator;
private sharedVariablesManager!: SharedVariablesManager; private sharedVariablesManager!: SharedVariablesManager;
@ -577,6 +575,14 @@ export class GameScene extends DirtyScene {
waScaleManager waScaleManager
); );
this.pathfindingManager = new PathfindingManager(
this,
this.gameMap.getCollisionsGrid(),
this.gameMap.getTileDimensions()
);
this.activatablesManager = new ActivatablesManager(this.CurrentPlayer);
biggestAvailableAreaStore.recompute(); biggestAvailableAreaStore.recompute();
this.cameraManager.startFollowPlayer(this.CurrentPlayer); this.cameraManager.startFollowPlayer(this.CurrentPlayer);
@ -659,10 +665,10 @@ export class GameScene extends DirtyScene {
this.followUsersColorStoreUnsubscribe = followUsersColorStore.subscribe((color) => { this.followUsersColorStoreUnsubscribe = followUsersColorStore.subscribe((color) => {
if (color !== undefined) { if (color !== undefined) {
this.CurrentPlayer.setOutlineColor(color); this.CurrentPlayer.setFollowOutlineColor(color);
this.connection?.emitPlayerOutlineColor(color); this.connection?.emitPlayerOutlineColor(color);
} else { } else {
this.CurrentPlayer.removeOutlineColor(); this.CurrentPlayer.removeFollowOutlineColor();
this.connection?.emitPlayerOutlineColor(null); this.connection?.emitPlayerOutlineColor(null);
} }
}); });
@ -679,10 +685,6 @@ export class GameScene extends DirtyScene {
); );
} }
public activateOutlinedItem(): void {
this.outlinedItem?.activate();
}
/** /**
* Initializes the connection to Pusher. * Initializes the connection to Pusher.
*/ */
@ -805,11 +807,8 @@ export class GameScene extends DirtyScene {
this.simplePeer = new SimplePeer(this.connection); this.simplePeer = new SimplePeer(this.connection);
userMessageManager.setReceiveBanListener(this.bannedUser.bind(this)); 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.CurrentPlayer.on(hasMovedEventName, (event: HasPlayerMovedEvent) => {
this.gameMap.setPosition(event.x, event.y); this.handleCurrentPlayerHasMovedEvent(event);
}); });
// Set up variables manager // Set up variables manager
@ -1272,7 +1271,7 @@ ${escapedMessage}
openCoWebsite.closable ?? true openCoWebsite.closable ?? true
); );
if (openCoWebsite.lazy !== undefined && !openCoWebsite.lazy) { if (openCoWebsite.lazy === undefined || !openCoWebsite.lazy) {
await coWebsiteManager.loadCoWebsite(coWebsite); await coWebsiteManager.loadCoWebsite(coWebsite);
} }
@ -1445,12 +1444,12 @@ ${escapedMessage}
const green = normalizeColor(message.green); const green = normalizeColor(message.green);
const blue = normalizeColor(message.blue); const blue = normalizeColor(message.blue);
const color = (red << 16) | (green << 8) | blue; const color = (red << 16) | (green << 8) | blue;
this.CurrentPlayer.setOutlineColor(color); this.CurrentPlayer.setApiOutlineColor(color);
this.connection?.emitPlayerOutlineColor(color); this.connection?.emitPlayerOutlineColor(color);
}); });
iframeListener.registerAnswerer("removePlayerOutline", (message) => { iframeListener.registerAnswerer("removePlayerOutline", (message) => {
this.CurrentPlayer.removeOutlineColor(); this.CurrentPlayer.removeApiOutlineColor();
this.connection?.emitPlayerOutlineColor(null); this.connection?.emitPlayerOutlineColor(null);
}); });
@ -1683,7 +1682,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 //add collision layer
for (const phaserLayer of this.gameMap.phaserLayers) { for (const phaserLayer of this.gameMap.phaserLayers) {
this.physics.add.collider(this.CurrentPlayer, phaserLayer, (object1: GameObject, object2: GameObject) => { this.physics.add.collider(this.CurrentPlayer, phaserLayer, (object1: GameObject, object2: GameObject) => {
@ -1702,7 +1712,7 @@ ${escapedMessage}
} }
} }
createCurrentPlayer() { private createCurrentPlayer() {
//TODO create animation moving between exit and start //TODO create animation moving between exit and start
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.characterLayers); const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.characterLayers);
try { try {
@ -1717,7 +1727,7 @@ ${escapedMessage}
this.companion, this.companion,
this.companion !== null ? lazyLoadCompanionResource(this.load, this.companion) : undefined 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) { 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. return; //we don't want the menu to open when pinching on a touch screen.
} }
@ -1760,7 +1770,7 @@ ${escapedMessage}
this.createCollisionWithPlayer(); this.createCollisionWithPlayer();
} }
pushPlayerPosition(event: HasPlayerMovedEvent) { private pushPlayerPosition(event: HasPlayerMovedEvent) {
if (this.lastMoveEventSent === event) { if (this.lastMoveEventSent === event) {
return; return;
} }
@ -1786,49 +1796,6 @@ ${escapedMessage}
// Otherwise, do nothing. // 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 { private doPushPlayerPosition(event: HasPlayerMovedEvent): void {
this.lastMoveEventSent = event; this.lastMoveEventSent = event;
this.lastSentTick = this.currentTick; this.lastSentTick = this.currentTick;
@ -1846,7 +1813,7 @@ ${escapedMessage}
* @param time * @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. * @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.dirty = false;
this.currentTick = time; this.currentTick = time;
this.CurrentPlayer.moveUser(delta, this.userInputManager.getEventListForGameTick()); this.CurrentPlayer.moveUser(delta, this.userInputManager.getEventListForGameTick());
@ -1865,9 +1832,15 @@ ${escapedMessage}
case "RemovePlayerEvent": case "RemovePlayerEvent":
this.doRemovePlayer(event.userId); this.doRemovePlayer(event.userId);
break; break;
case "UserMovedEvent": case "UserMovedEvent": {
this.doUpdatePlayerPosition(event.event); this.doUpdatePlayerPosition(event.event);
const remotePlayer = this.MapPlayersByKey.get(event.event.userId);
if (remotePlayer) {
this.activatablesManager.updateDistanceForSingleActivatableObject(remotePlayer);
this.activatablesManager.deduceSelectedActivatableObjectByDistance();
}
break; break;
}
case "GroupCreatedUpdatedEvent": case "GroupCreatedUpdatedEvent":
this.doShareGroupPosition(event.event); this.doShareGroupPosition(event.event);
break; break;
@ -1954,11 +1927,21 @@ ${escapedMessage}
addPlayerData.companion !== null ? lazyLoadCompanionResource(this.load, addPlayerData.companion) : undefined addPlayerData.companion !== null ? lazyLoadCompanionResource(this.load, addPlayerData.companion) : undefined
); );
if (addPlayerData.outlineColor !== undefined) { if (addPlayerData.outlineColor !== undefined) {
player.setOutlineColor(addPlayerData.outlineColor); player.setApiOutlineColor(addPlayerData.outlineColor);
} }
this.MapPlayers.add(player); this.MapPlayers.add(player);
this.MapPlayersByKey.set(player.userId, player); this.MapPlayersByKey.set(player.userId, player);
player.updatePosition(addPlayerData.position); 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();
});
} }
/** /**
@ -1988,7 +1971,7 @@ ${escapedMessage}
this.playersPositionInterpolator.removePlayer(userId); this.playersPositionInterpolator.removePlayer(userId);
} }
public updatePlayerPosition(message: MessageUserMovedInterface): void { private updatePlayerPosition(message: MessageUserMovedInterface): void {
this.pendingEvents.enqueue({ this.pendingEvents.enqueue({
type: "UserMovedEvent", type: "UserMovedEvent",
event: message, event: message,
@ -2018,7 +2001,7 @@ ${escapedMessage}
this.playersPositionInterpolator.updatePlayerPosition(player.userId, playerMovement); this.playersPositionInterpolator.updatePlayerPosition(player.userId, playerMovement);
} }
public shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface) { private shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface) {
this.pendingEvents.enqueue({ this.pendingEvents.enqueue({
type: "GroupCreatedUpdatedEvent", type: "GroupCreatedUpdatedEvent",
event: groupPositionMessage, event: groupPositionMessage,
@ -2070,9 +2053,9 @@ ${escapedMessage}
return; return;
} }
if (message.removeOutlineColor) { if (message.removeOutlineColor) {
character.removeOutlineColor(); character.removeApiOutlineColor();
} else { } else {
character.setOutlineColor(message.outlineColor); character.setApiOutlineColor(message.outlineColor);
} }
} }
@ -2229,4 +2212,8 @@ ${escapedMessage}
public getPathfindingManager(): PathfindingManager { public getPathfindingManager(): PathfindingManager {
return this.pathfindingManager; return this.pathfindingManager;
} }
public getActivatablesManager(): ActivatablesManager {
return this.activatablesManager;
}
} }

View File

@ -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;
}

View File

@ -5,10 +5,11 @@
import Sprite = Phaser.GameObjects.Sprite; import Sprite = Phaser.GameObjects.Sprite;
import type { GameScene } from "../Game/GameScene"; import type { GameScene } from "../Game/GameScene";
import type OutlinePipelinePlugin from "phaser3-rex-plugins/plugins/outlinepipeline-plugin.js"; import type OutlinePipelinePlugin from "phaser3-rex-plugins/plugins/outlinepipeline-plugin.js";
import type { ActivatableInterface } from "../Game/ActivatableInterface";
type EventCallback = (state: unknown, parameters: unknown) => void; type EventCallback = (state: unknown, parameters: unknown) => void;
export class ActionableItem { export class ActionableItem implements ActivatableInterface {
private readonly activationRadiusSquared: number; private readonly activationRadiusSquared: number;
private isSelectable: boolean = false; private isSelectable: boolean = false;
private callbacks: Map<string, Array<EventCallback>> = new Map<string, Array<EventCallback>>(); private callbacks: Map<string, Array<EventCallback>> = new Map<string, Array<EventCallback>>();
@ -17,7 +18,7 @@ export class ActionableItem {
private id: number, private id: number,
private sprite: Sprite, private sprite: Sprite,
private eventHandler: GameScene, private eventHandler: GameScene,
private activationRadius: number, public readonly activationRadius: number,
private onActivateCallback: (item: ActionableItem) => void private onActivateCallback: (item: ActionableItem) => void
) { ) {
this.activationRadiusSquared = activationRadius * activationRadius; 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. * 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; return this.sprite.scene.plugins.get("rexOutlinePipeline") as unknown as OutlinePipelinePlugin | undefined;
} }
/** public isActivatable(): boolean {
* Triggered when the "space" key is pressed and the object is in range of being activated. return this.isSelectable;
*/ }
public activate(): void { public activate(): void {
this.onActivateCallback(this); this.onActivateCallback(this);
} }

View File

@ -3,11 +3,12 @@ import { localUserStore } from "../../Connexion/LocalUserStore";
import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures"; import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
import { loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager"; import { loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
import type { CharacterTexture } from "../../Connexion/LocalUser"; import type { CharacterTexture } from "../../Connexion/LocalUser";
import type CancelablePromise from "cancelable-promise";
export abstract class AbstractCharacterScene extends ResizableScene { export abstract class AbstractCharacterScene extends ResizableScene {
loadCustomSceneSelectCharacters(): Promise<BodyResourceDescriptionInterface[]> { loadCustomSceneSelectCharacters(): Promise<BodyResourceDescriptionInterface[]> {
const textures = this.getTextures(); const textures = this.getTextures();
const promises: Promise<BodyResourceDescriptionInterface>[] = []; const promises: CancelablePromise<BodyResourceDescriptionInterface>[] = [];
if (textures) { if (textures) {
for (const texture of textures) { for (const texture of textures) {
if (texture.level === -1) { if (texture.level === -1) {
@ -21,7 +22,7 @@ export abstract class AbstractCharacterScene extends ResizableScene {
loadSelectSceneCharacters(): Promise<BodyResourceDescriptionInterface[]> { loadSelectSceneCharacters(): Promise<BodyResourceDescriptionInterface[]> {
const textures = this.getTextures(); const textures = this.getTextures();
const promises: Promise<BodyResourceDescriptionInterface>[] = []; const promises: CancelablePromise<BodyResourceDescriptionInterface>[] = [];
if (textures) { if (textures) {
for (const texture of textures) { for (const texture of textures) {
if (texture.level !== -1) { if (texture.level !== -1) {

View File

@ -289,7 +289,6 @@ export class CustomizeScene extends AbstractCharacterScene {
gameManager.setCharacterLayers(layers); gameManager.setCharacterLayers(layers);
this.scene.sleep(CustomizeSceneName); this.scene.sleep(CustomizeSceneName);
waScaleManager.restoreZoom(); waScaleManager.restoreZoom();
this.events.removeListener("wake");
gameManager.tryResumingGame(EnableCameraSceneName); gameManager.tryResumingGame(EnableCameraSceneName);
customCharacterSceneVisibleStore.set(false); customCharacterSceneVisibleStore.set(false);
} }

View File

@ -6,6 +6,7 @@ import { Character } from "../Entity/Character";
import { get } from "svelte/store"; import { get } from "svelte/store";
import { userMovingStore } from "../../Stores/GameStore"; import { userMovingStore } from "../../Stores/GameStore";
import { followStateStore, followRoleStore, followUsersStore } from "../../Stores/FollowStore"; import { followStateStore, followRoleStore, followUsersStore } from "../../Stores/FollowStore";
import type CancelablePromise from "cancelable-promise";
export const hasMovedEventName = "hasMoved"; export const hasMovedEventName = "hasMoved";
export const requestEmoteEventName = "requestEmote"; export const requestEmoteEventName = "requestEmote";
@ -20,7 +21,7 @@ export class Player extends Character {
x: number, x: number,
y: number, y: number,
name: string, name: string,
texturesPromise: Promise<string[]>, texturesPromise: CancelablePromise<string[]>,
direction: PlayerAnimationDirections, direction: PlayerAnimationDirections,
moving: boolean, moving: boolean,
companion: string | null, companion: string | null,

View File

@ -41,8 +41,8 @@ export class WaScaleManager {
this.actualZoom = realSize.width / gameSize.width / devicePixelRatio; this.actualZoom = realSize.width / gameSize.width / devicePixelRatio;
} }
this.scaleManager.setZoom(this.actualZoom);
this.scaleManager.resize(gameSize.width, gameSize.height); 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 // Override bug in canvas resizing in Phaser. Let's resize the canvas ourselves
const style = this.scaleManager.canvas.style; const style = this.scaleManager.canvas.style;

View File

@ -1,3 +1,6 @@
import { Player } from "../Player/Player";
import { RemotePlayer } from "../Entity/RemotePlayer";
import type { UserInputHandlerInterface } from "../../Interfaces/UserInputHandlerInterface"; import type { UserInputHandlerInterface } from "../../Interfaces/UserInputHandlerInterface";
import type { GameScene } from "../Game/GameScene"; import type { GameScene } from "../Game/GameScene";
@ -22,6 +25,11 @@ export class GameSceneUserInputHandler implements UserInputHandlerInterface {
if (pointer.rightButtonReleased() || pointer.getDuration() > 250) { if (pointer.rightButtonReleased() || pointer.getDuration() > 250) {
return; return;
} }
for (const object of gameObjects) {
if (object instanceof Player || object instanceof RemotePlayer) {
return;
}
}
const camera = this.gameScene.getCameraManager().getCamera(); const camera = this.gameScene.getCameraManager().getCamera();
const index = this.gameScene const index = this.gameScene
.getGameMap() .getGameMap()
@ -45,7 +53,10 @@ export class GameSceneUserInputHandler implements UserInputHandlerInterface {
public handlePointerDownEvent(pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]): void {} public handlePointerDownEvent(pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]): void {}
public handleSpaceKeyUpEvent(event: Event): Event { public handleSpaceKeyUpEvent(event: Event): Event {
this.gameScene.activateOutlinedItem(); const activatable = this.gameScene.getActivatablesManager().getSelectedActivatableObject();
if (activatable && activatable.isActivatable()) {
activatable.activate();
}
return event; return event;
} }
} }

View File

@ -0,0 +1,43 @@
import { writable } from "svelte/store";
export interface ActionsMenuData {
playerName: string;
actions: { actionName: string; callback: Function }[];
}
function createActionsMenuStore() {
const { subscribe, update, set } = writable<ActionsMenuData | undefined>(undefined);
return {
subscribe,
initialize: (playerName: string) => {
set({
playerName,
actions: [],
});
},
addAction: (actionName: string, callback: Function) => {
update((data) => {
data?.actions.push({ actionName, callback });
return data;
});
},
removeAction: (actionName: string) => {
update((data) => {
const actionIndex = data?.actions.findIndex((action) => action.actionName === actionName);
if (actionIndex !== undefined && actionIndex != -1) {
data?.actions.splice(actionIndex, 1);
}
return data;
});
},
/**
* Hides menu
*/
clear: () => {
set(undefined);
},
};
}
export const actionsMenuStore = createActionsMenuStore();

View File

@ -3,14 +3,17 @@ import { writable } from "svelte/store";
export function createColorStore() { export function createColorStore() {
const { subscribe, set } = writable<number | undefined>(undefined); const { subscribe, set } = writable<number | undefined>(undefined);
let color: number | undefined = undefined; let followColor: number | undefined = undefined;
let focused: boolean = false; let apiColor: number | undefined = undefined;
let pointedByPointer: boolean = false;
let pointedByCharacter: boolean = false;
const updateColor = () => { const updateColor = () => {
if (focused) { if (pointedByPointer || pointedByCharacter) {
set(0xffff00); set(0xffff00);
} else { } else {
set(color); set(followColor ?? apiColor);
} }
}; };
@ -18,22 +21,42 @@ export function createColorStore() {
subscribe, subscribe,
pointerOver() { pointerOver() {
focused = true; pointedByPointer = true;
updateColor(); updateColor();
}, },
pointerOut() { pointerOut() {
focused = false; pointedByPointer = false;
updateColor(); updateColor();
}, },
setColor(newColor: number) { characterCloseBy() {
color = newColor; pointedByCharacter = true;
updateColor(); updateColor();
}, },
removeColor() { characterFarAway() {
color = undefined; pointedByCharacter = false;
updateColor();
},
setFollowColor(newColor: number) {
followColor = newColor;
updateColor();
},
removeFollowColor() {
followColor = undefined;
updateColor();
},
setApiColor(newColor: number) {
apiColor = newColor;
updateColor();
},
removeApiColor() {
apiColor = undefined;
updateColor(); updateColor();
}, },
}; };

View File

@ -0,0 +1,5 @@
import type { OutlineableInterface } from "../Phaser/Game/OutlineableInterface";
export function isOutlineable(object: unknown): object is OutlineableInterface {
return (object as OutlineableInterface)?.pointerOverOutline !== undefined;
}

View File

@ -44,6 +44,7 @@ export type CoWebsite = {
allowPolicy: string | undefined; allowPolicy: string | undefined;
allowApi: boolean | undefined; allowApi: boolean | undefined;
jitsi?: boolean; jitsi?: boolean;
altMessage?: string;
}; };
class CoWebsiteManager { class CoWebsiteManager {
@ -533,7 +534,8 @@ class CoWebsiteManager {
allowApi?: boolean, allowApi?: boolean,
allowPolicy?: string, allowPolicy?: string,
position?: number, position?: number,
closable?: boolean closable?: boolean,
altMessage?: string
): CoWebsite { ): CoWebsite {
const iframe = document.createElement("iframe"); const iframe = document.createElement("iframe");
const fullUrl = new URL(url, base); const fullUrl = new URL(url, base);
@ -547,6 +549,7 @@ class CoWebsiteManager {
closable: closable ?? false, closable: closable ?? false,
allowPolicy, allowPolicy,
allowApi, allowApi,
altMessage,
}; };
this.initialiseCowebsite(newCoWebsite, position); this.initialiseCowebsite(newCoWebsite, position);

View File

@ -1352,6 +1352,11 @@ camelcase@^6.2.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
cancelable-promise@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/cancelable-promise/-/cancelable-promise-4.2.1.tgz#b02f79c5dde2704acfff1bc1ac2b4090f55541fe"
integrity sha512-PJZ/000ocWhPZQBAuNewAOMA2WEkJ8RhXI6AxeGLiGdW8EYDmumzo9wKyNgjDgxc1q/HbXuTdlcI+wXrOe/jMw==
caniuse-api@^3.0.0: caniuse-api@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0"