Merge branch 'develop' of github.com:thecodingmachine/workadventure into develop
This commit is contained in:
commit
301eaba90e
99
front/src/Components/ActionsMenu/ActionsMenu.svelte
Normal file
99
front/src/Components/ActionsMenu/ActionsMenu.svelte
Normal 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}>×</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>
|
@ -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() {
|
||||||
|
@ -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}
|
||||||
|
@ -15,6 +15,7 @@ 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";
|
import type CancelablePromise from "cancelable-promise";
|
||||||
|
|
||||||
const playerNameY = -25;
|
const playerNameY = -25;
|
||||||
@ -29,14 +30,15 @@ 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;
|
||||||
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.Text | null = null;
|
private emote: Phaser.GameObjects.Text | null = null;
|
||||||
private emoteTween: Phaser.Tweens.Tween | null = null;
|
private emoteTween: Phaser.Tweens.Tween | null = null;
|
||||||
@ -61,8 +63,9 @@ 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);
|
||||||
@ -88,7 +91,7 @@ export abstract class Character extends Container {
|
|||||||
this.texturePromise = undefined;
|
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,
|
||||||
@ -99,30 +102,17 @@ export abstract class Character extends Container {
|
|||||||
fontSize: 35,
|
fontSize: 35,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.playerName.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
|
this.playerNameText.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||||
this.add(this.playerName);
|
this.add(this.playerNameText);
|
||||||
|
|
||||||
if (isClickable) {
|
this.setClickable(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.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,
|
||||||
});
|
});
|
||||||
@ -145,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> {
|
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 };
|
||||||
@ -411,18 +429,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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +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";
|
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,
|
||||||
@ -23,34 +31,26 @@ export class RemotePlayer extends Character {
|
|||||||
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);
|
||||||
@ -61,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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
6
front/src/Phaser/Game/ActivatableInterface.ts
Normal file
6
front/src/Phaser/Game/ActivatableInterface.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export interface ActivatableInterface {
|
||||||
|
readonly activationRadius: number;
|
||||||
|
isActivatable: () => boolean;
|
||||||
|
activate: () => void;
|
||||||
|
getPosition: () => { x: number; y: number };
|
||||||
|
}
|
93
front/src/Phaser/Game/ActivatablesManager.ts
Normal file
93
front/src/Phaser/Game/ActivatablesManager.ts
Normal 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())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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,7 +90,6 @@ 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, LL } from "../../i18n/i18n-svelte";
|
import { locale, LL } from "../../i18n/i18n-svelte";
|
||||||
import { i18nJson } from "../../i18n/locales";
|
import { i18nJson } from "../../i18n/locales";
|
||||||
@ -190,8 +190,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;
|
||||||
@ -205,6 +203,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;
|
||||||
@ -594,6 +593,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);
|
||||||
|
|
||||||
@ -675,10 +682,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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -695,10 +702,6 @@ export class GameScene extends DirtyScene {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public activateOutlinedItem(): void {
|
|
||||||
this.outlinedItem?.activate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the connection to Pusher.
|
* Initializes the connection to Pusher.
|
||||||
*/
|
*/
|
||||||
@ -821,11 +824,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
|
||||||
@ -1488,12 +1488,12 @@ export class GameScene extends DirtyScene {
|
|||||||
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1726,7 +1726,18 @@ export class GameScene extends DirtyScene {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) => {
|
||||||
@ -1745,7 +1756,7 @@ export class GameScene extends DirtyScene {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@ -1760,7 +1771,7 @@ export class GameScene extends DirtyScene {
|
|||||||
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.
|
||||||
}
|
}
|
||||||
@ -1803,7 +1814,7 @@ export class GameScene extends DirtyScene {
|
|||||||
this.createCollisionWithPlayer();
|
this.createCollisionWithPlayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
pushPlayerPosition(event: HasPlayerMovedEvent) {
|
private pushPlayerPosition(event: HasPlayerMovedEvent) {
|
||||||
if (this.lastMoveEventSent === event) {
|
if (this.lastMoveEventSent === event) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1829,49 +1840,6 @@ export class GameScene extends DirtyScene {
|
|||||||
// 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;
|
||||||
@ -1889,7 +1857,7 @@ export class GameScene extends DirtyScene {
|
|||||||
* @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());
|
||||||
@ -1908,9 +1876,15 @@ export class GameScene extends DirtyScene {
|
|||||||
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;
|
||||||
@ -1997,11 +1971,21 @@ export class GameScene extends DirtyScene {
|
|||||||
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();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2031,7 +2015,7 @@ export class GameScene extends DirtyScene {
|
|||||||
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,
|
||||||
@ -2061,7 +2045,7 @@ export class GameScene extends DirtyScene {
|
|||||||
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,
|
||||||
@ -2113,9 +2097,9 @@ export class GameScene extends DirtyScene {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (message.removeOutlineColor) {
|
if (message.removeOutlineColor) {
|
||||||
character.removeOutlineColor();
|
character.removeApiOutlineColor();
|
||||||
} else {
|
} else {
|
||||||
character.setOutlineColor(message.outlineColor);
|
character.setApiOutlineColor(message.outlineColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2277,4 +2261,8 @@ export class GameScene extends DirtyScene {
|
|||||||
public getPathfindingManager(): PathfindingManager {
|
public getPathfindingManager(): PathfindingManager {
|
||||||
return this.pathfindingManager;
|
return this.pathfindingManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getActivatablesManager(): ActivatablesManager {
|
||||||
|
return this.activatablesManager;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
10
front/src/Phaser/Game/OutlineableInterface.ts
Normal file
10
front/src/Phaser/Game/OutlineableInterface.ts
Normal 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;
|
||||||
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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";
|
||||||
|
|
||||||
@ -27,6 +30,12 @@ export class GameSceneUserInputHandler implements UserInputHandlerInterface {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const object of gameObjects) {
|
||||||
|
if (object instanceof Player || object instanceof RemotePlayer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.lastTime > 0 &&
|
this.lastTime > 0 &&
|
||||||
pointer.time - this.lastTime < 500 &&
|
pointer.time - this.lastTime < 500 &&
|
||||||
@ -61,7 +70,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
43
front/src/Stores/ActionsMenuStore.ts
Normal file
43
front/src/Stores/ActionsMenuStore.ts
Normal 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();
|
@ -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();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
5
front/src/Utils/CustomTypeGuards.ts
Normal file
5
front/src/Utils/CustomTypeGuards.ts
Normal 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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user