Merge branch 'develop' of github.com:thecodingmachine/workadventure
This commit is contained in:
@@ -34,6 +34,8 @@ import type { ChangeZoneEvent } from "./ChangeZoneEvent";
|
||||
import type { CameraSetEvent } from "./CameraSetEvent";
|
||||
import type { CameraFollowPlayerEvent } from "./CameraFollowPlayerEvent";
|
||||
import { isColorEvent } from "./ColorEvent";
|
||||
import { isMovePlayerToEventConfig } from "./MovePlayerToEvent";
|
||||
import { isMovePlayerToEventAnswer } from "./MovePlayerToEventAnswer";
|
||||
import { isGetPropertyEvent } from "./GetPropertyEvent";
|
||||
|
||||
export interface TypedMessageEvent<T> extends MessageEvent {
|
||||
@@ -178,6 +180,10 @@ export const iframeQueryMapTypeGuards = {
|
||||
query: tg.isUndefined,
|
||||
answer: isPlayerPosition,
|
||||
},
|
||||
movePlayerTo: {
|
||||
query: isMovePlayerToEventConfig,
|
||||
answer: isMovePlayerToEventAnswer,
|
||||
},
|
||||
};
|
||||
|
||||
type GuardedType<T> = T extends (x: unknown) => x is infer T ? T : never;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
export const isMovePlayerToEventConfig = new tg.IsInterface()
|
||||
.withProperties({
|
||||
x: tg.isNumber,
|
||||
y: tg.isNumber,
|
||||
speed: tg.isOptional(tg.isNumber),
|
||||
})
|
||||
.get();
|
||||
|
||||
export type MovePlayerToEvent = tg.GuardedType<typeof isMovePlayerToEventConfig>;
|
||||
@@ -0,0 +1,11 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
export const isMovePlayerToEventAnswer = new tg.IsInterface()
|
||||
.withProperties({
|
||||
x: tg.isNumber,
|
||||
y: tg.isNumber,
|
||||
cancelled: tg.isBoolean,
|
||||
})
|
||||
.get();
|
||||
|
||||
export type MovePlayerToEventAnswer = tg.GuardedType<typeof isMovePlayerToEventAnswer>;
|
||||
@@ -84,6 +84,13 @@ export class WorkadventurePlayerCommands extends IframeApiContribution<Workadven
|
||||
});
|
||||
}
|
||||
|
||||
public async moveTo(x: number, y: number, speed?: number): Promise<{ x: number; y: number; cancelled: boolean }> {
|
||||
return await queryWorkadventure({
|
||||
type: "movePlayerTo",
|
||||
data: { x, y, speed },
|
||||
});
|
||||
}
|
||||
|
||||
get userRoomToken(): string | undefined {
|
||||
if (userRoomToken === undefined) {
|
||||
throw new Error(
|
||||
|
||||
@@ -586,7 +586,11 @@ export class GameScene extends DirtyScene {
|
||||
waScaleManager
|
||||
);
|
||||
|
||||
this.pathfindingManager = new PathfindingManager(this, this.gameMap.getCollisionsGrid());
|
||||
this.pathfindingManager = new PathfindingManager(
|
||||
this,
|
||||
this.gameMap.getCollisionsGrid(),
|
||||
this.gameMap.getTileDimensions()
|
||||
);
|
||||
biggestAvailableAreaStore.recompute();
|
||||
this.cameraManager.startFollowPlayer(this.CurrentPlayer);
|
||||
|
||||
@@ -1489,6 +1493,17 @@ export class GameScene extends DirtyScene {
|
||||
y: this.CurrentPlayer.y,
|
||||
};
|
||||
});
|
||||
|
||||
iframeListener.registerAnswerer("movePlayerTo", async (message) => {
|
||||
const index = this.getGameMap().getTileIndexAt(message.x, message.y);
|
||||
const startTile = this.getGameMap().getTileIndexAt(this.CurrentPlayer.x, this.CurrentPlayer.y);
|
||||
const path = await this.getPathfindingManager().findPath(startTile, index, true, true);
|
||||
path.shift();
|
||||
if (path.length === 0) {
|
||||
throw new Error("no path available");
|
||||
}
|
||||
return this.CurrentPlayer.setPathToFollow(path, message.speed);
|
||||
});
|
||||
}
|
||||
|
||||
private setPropertyLayer(
|
||||
|
||||
@@ -12,6 +12,8 @@ export const requestEmoteEventName = "requestEmote";
|
||||
|
||||
export class Player extends Character {
|
||||
private pathToFollow?: { x: number; y: number }[];
|
||||
private followingPathPromiseResolve?: (result: { x: number; y: number; cancelled: boolean }) => void;
|
||||
private pathWalkingSpeed?: number;
|
||||
|
||||
constructor(
|
||||
Scene: GameScene,
|
||||
@@ -43,7 +45,7 @@ export class Player extends Character {
|
||||
}
|
||||
|
||||
if (this.pathToFollow && activeUserInputEvents.anyExcept(UserInputEvent.SpeedUp)) {
|
||||
this.pathToFollow = undefined;
|
||||
this.finishFollowingPath(true);
|
||||
}
|
||||
|
||||
let x = 0;
|
||||
@@ -68,9 +70,22 @@ export class Player extends Character {
|
||||
this.scene.connection?.emitFollowConfirmation();
|
||||
}
|
||||
|
||||
public setPathToFollow(path: { x: number; y: number }[]): void {
|
||||
public async setPathToFollow(
|
||||
path: { x: number; y: number }[],
|
||||
speed?: number
|
||||
): Promise<{ x: number; y: number; cancelled: boolean }> {
|
||||
const isPreviousPathInProgress = this.pathToFollow !== undefined && this.pathToFollow.length > 0;
|
||||
// take collider offset into consideraton
|
||||
this.pathToFollow = this.adjustPathToFollowToColliderBounds(path);
|
||||
this.pathWalkingSpeed = speed;
|
||||
return new Promise((resolve) => {
|
||||
this.followingPathPromiseResolve?.call(this, { x: this.x, y: this.y, cancelled: isPreviousPathInProgress });
|
||||
this.followingPathPromiseResolve = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
private deduceSpeed(speedUp: boolean, followMode: boolean): number {
|
||||
return this.pathWalkingSpeed ? this.pathWalkingSpeed : speedUp && !followMode ? 25 : 9;
|
||||
}
|
||||
|
||||
private adjustPathToFollowToColliderBounds(path: { x: number; y: number }[]): { x: number; y: number }[] {
|
||||
@@ -95,8 +110,8 @@ export class Player extends Character {
|
||||
|
||||
// Compute movement deltas
|
||||
const followMode = get(followStateStore) !== "off";
|
||||
const speedup = activeEvents.get(UserInputEvent.SpeedUp) && !followMode ? 25 : 9;
|
||||
const moveAmount = speedup * 20;
|
||||
const speed = this.deduceSpeed(activeEvents.get(UserInputEvent.SpeedUp), followMode);
|
||||
const moveAmount = speed * 20;
|
||||
x = x * moveAmount;
|
||||
y = y * moveAmount;
|
||||
|
||||
@@ -148,8 +163,8 @@ export class Player extends Character {
|
||||
}
|
||||
|
||||
private computeFollowPathMovement(): number[] {
|
||||
if (this.pathToFollow?.length === 0) {
|
||||
this.pathToFollow = undefined;
|
||||
if (this.pathToFollow !== undefined && this.pathToFollow.length === 0) {
|
||||
this.finishFollowingPath();
|
||||
}
|
||||
if (!this.pathToFollow) {
|
||||
return [0, 0];
|
||||
@@ -166,6 +181,12 @@ export class Player extends Character {
|
||||
return this.getMovementDirection(xDistance, yDistance, distance);
|
||||
}
|
||||
|
||||
private finishFollowingPath(cancelled: boolean = false): void {
|
||||
this.pathToFollow = undefined;
|
||||
this.pathWalkingSpeed = undefined;
|
||||
this.followingPathPromiseResolve?.call(this, { x: this.x, y: this.y, cancelled });
|
||||
}
|
||||
|
||||
private getMovementDirection(xDistance: number, yDistance: number, distance: number): [number, number] {
|
||||
return [xDistance / Math.sqrt(distance), yDistance / Math.sqrt(distance)];
|
||||
}
|
||||
|
||||
@@ -31,18 +31,11 @@ export class GameSceneUserInputHandler implements UserInputHandlerInterface {
|
||||
.getTileIndexAt(this.gameScene.CurrentPlayer.x, this.gameScene.CurrentPlayer.y);
|
||||
this.gameScene
|
||||
.getPathfindingManager()
|
||||
.findPath(startTile, index, true)
|
||||
.findPath(startTile, index, true, true)
|
||||
.then((path) => {
|
||||
const tileDimensions = this.gameScene.getGameMap().getTileDimensions();
|
||||
const pixelPath = path.map((step) => {
|
||||
return {
|
||||
x: step.x * tileDimensions.width + tileDimensions.width * 0.5,
|
||||
y: step.y * tileDimensions.height + tileDimensions.height * 0.5,
|
||||
};
|
||||
});
|
||||
// Remove first step as it is for the tile we are currently standing on
|
||||
pixelPath.shift();
|
||||
this.gameScene.CurrentPlayer.setPathToFollow(pixelPath);
|
||||
path.shift();
|
||||
this.gameScene.CurrentPlayer.setPathToFollow(path).catch((reason) => {});
|
||||
})
|
||||
.catch((reason) => {
|
||||
console.warn(reason);
|
||||
|
||||
@@ -54,7 +54,7 @@ export class UserInputManager {
|
||||
private scene: Phaser.Scene;
|
||||
private isInputDisabled: boolean;
|
||||
|
||||
private joystick!: MobileJoystick;
|
||||
private joystick?: MobileJoystick;
|
||||
private joystickEvents = new ActiveEventList();
|
||||
private joystickForceThreshold = 60;
|
||||
private joystickForceAccuX = 0;
|
||||
@@ -81,9 +81,9 @@ export class UserInputManager {
|
||||
initVirtualJoystick() {
|
||||
this.joystick = new MobileJoystick(this.scene);
|
||||
this.joystick.on("update", () => {
|
||||
this.joystickForceAccuX = this.joystick.forceX ? this.joystickForceAccuX : 0;
|
||||
this.joystickForceAccuY = this.joystick.forceY ? this.joystickForceAccuY : 0;
|
||||
const cursorKeys = this.joystick.createCursorKeys();
|
||||
this.joystickForceAccuX = this.joystick?.forceX ? this.joystickForceAccuX : 0;
|
||||
this.joystickForceAccuY = this.joystick?.forceY ? this.joystickForceAccuY : 0;
|
||||
const cursorKeys = this.joystick?.createCursorKeys();
|
||||
for (const name in cursorKeys) {
|
||||
const key = cursorKeys[name as Direction];
|
||||
switch (name) {
|
||||
@@ -192,7 +192,7 @@ export class UserInputManager {
|
||||
return eventsMap;
|
||||
}
|
||||
this.joystickEvents.forEach((value, key) => {
|
||||
if (value) {
|
||||
if (value && this.joystick) {
|
||||
switch (key) {
|
||||
case UserInputEvent.MoveUp:
|
||||
case UserInputEvent.MoveDown:
|
||||
@@ -253,7 +253,7 @@ export class UserInputManager {
|
||||
this.scene.input.on(
|
||||
Phaser.Input.Events.POINTER_UP,
|
||||
(pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]) => {
|
||||
this.joystick.hide();
|
||||
this.joystick?.hide();
|
||||
this.userInputHandler.handlePointerUpEvent(pointer, gameObjects);
|
||||
}
|
||||
);
|
||||
@@ -267,9 +267,9 @@ export class UserInputManager {
|
||||
this.userInputHandler.handlePointerDownEvent(pointer, gameObjects);
|
||||
// Let's only display the joystick if there is one finger on the screen
|
||||
if ((pointer.event as TouchEvent).touches.length === 1) {
|
||||
this.joystick.showAt(pointer.x, pointer.y);
|
||||
this.joystick?.showAt(pointer.x, pointer.y);
|
||||
} else {
|
||||
this.joystick.hide();
|
||||
this.joystick?.hide();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -6,20 +6,23 @@ export class PathfindingManager {
|
||||
|
||||
private easyStar;
|
||||
private grid: number[][];
|
||||
private tileDimensions: { width: number; height: number };
|
||||
|
||||
constructor(scene: Phaser.Scene, collisionsGrid: number[][]) {
|
||||
constructor(scene: Phaser.Scene, collisionsGrid: number[][], tileDimensions: { width: number; height: number }) {
|
||||
this.scene = scene;
|
||||
|
||||
this.easyStar = new EasyStar.js();
|
||||
this.easyStar.enableDiagonals();
|
||||
|
||||
this.grid = collisionsGrid;
|
||||
this.tileDimensions = tileDimensions;
|
||||
this.setEasyStarGrid(collisionsGrid);
|
||||
}
|
||||
|
||||
public async findPath(
|
||||
start: { x: number; y: number },
|
||||
end: { x: number; y: number },
|
||||
measuredInPixels: boolean = true,
|
||||
tryFindingNearestAvailable: boolean = false
|
||||
): Promise<{ x: number; y: number }[]> {
|
||||
let endPoints: { x: number; y: number }[] = [end];
|
||||
@@ -48,12 +51,21 @@ export class PathfindingManager {
|
||||
// rejected Promise will return undefined for path
|
||||
path = await this.getPath(start, endPoint).catch();
|
||||
if (path && path.length > 0) {
|
||||
return path;
|
||||
return measuredInPixels ? this.mapTileUnitsToPixels(path) : path;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
private mapTileUnitsToPixels(path: { x: number; y: number }[]): { x: number; y: number }[] {
|
||||
return path.map((step) => {
|
||||
return {
|
||||
x: step.x * this.tileDimensions.width + this.tileDimensions.width * 0.5,
|
||||
y: step.y * this.tileDimensions.height + this.tileDimensions.height * 0.5,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private getNeighbouringTiles(tile: { x: number; y: number }): { x: number; y: number }[] {
|
||||
const xOffsets = [-1, 0, 1, 1, 1, 0, -1, -1];
|
||||
const yOffsets = [-1, -1, -1, 0, 1, 1, 1, 0];
|
||||
|
||||
Reference in New Issue
Block a user