diff --git a/back/tests/PositionNotifierTest.ts b/back/tests/PositionNotifierTest.ts index c081f1b4..bf7ddd6e 100644 --- a/back/tests/PositionNotifierTest.ts +++ b/back/tests/PositionNotifierTest.ts @@ -1,11 +1,10 @@ import "jasmine"; -import {PositionNotifier} from "../src/Model/PositionNotifier"; -import {User, UserSocket} from "../src/Model/User"; -import {Zone} from "_Model/Zone"; -import {Movable} from "_Model/Movable"; -import {PositionInterface} from "_Model/PositionInterface"; -import {ZoneSocket} from "../src/RoomManager"; - +import { PositionNotifier } from "../src/Model/PositionNotifier"; +import { User, UserSocket } from "../src/Model/User"; +import { Zone } from "_Model/Zone"; +import { Movable } from "_Model/Movable"; +import { PositionInterface } from "_Model/PositionInterface"; +import { ZoneSocket } from "../src/RoomManager"; describe("PositionNotifier", () => { it("should receive notifications when player moves", () => { @@ -13,28 +12,59 @@ describe("PositionNotifier", () => { let moveTriggered = false; let leaveTriggered = false; - const positionNotifier = new PositionNotifier(300, 300, (thing: Movable) => { - enterTriggered = true; - }, (thing: Movable, position: PositionInterface) => { - moveTriggered = true; - }, (thing: Movable) => { - leaveTriggered = true; - }, () => {}, - () => {}); + const positionNotifier = new PositionNotifier( + 300, + 300, + (thing: Movable) => { + enterTriggered = true; + }, + (thing: Movable, position: PositionInterface) => { + moveTriggered = true; + }, + (thing: Movable) => { + leaveTriggered = true; + }, + () => {}, + () => {} + ); - const user1 = new User(1, 'test', '10.0.0.2', { - x: 500, - y: 500, - moving: false, - direction: 'down' - }, false, positionNotifier, {} as UserSocket, [], null, 'foo', []); + const user1 = new User( + 1, + "test", + "10.0.0.2", + { + x: 500, + y: 500, + moving: false, + direction: "down", + }, + false, + positionNotifier, + {} as UserSocket, + [], + null, + "foo", + [] + ); - const user2 = new User(2, 'test', '10.0.0.2', { - x: -9999, - y: -9999, - moving: false, - direction: 'down' - }, false, positionNotifier, {} as UserSocket, [], null, 'foo', []); + const user2 = new User( + 2, + "test", + "10.0.0.2", + { + x: -9999, + y: -9999, + moving: false, + direction: "down", + }, + false, + positionNotifier, + {} as UserSocket, + [], + null, + "foo", + [] + ); positionNotifier.addZoneListener({} as ZoneSocket, 0, 0); positionNotifier.addZoneListener({} as ZoneSocket, 0, 1); @@ -47,21 +77,21 @@ describe("PositionNotifier", () => { bottom: 500 });*/ - user2.setPosition({x: 500, y: 500, direction: 'down', moving: false}); + user2.setPosition({ x: 500, y: 500, direction: "down", moving: false }); expect(enterTriggered).toBe(true); expect(moveTriggered).toBe(false); enterTriggered = false; // Move inside the zone - user2.setPosition({x:501, y:500, direction: 'down', moving: false}); + user2.setPosition({ x: 501, y: 500, direction: "down", moving: false }); expect(enterTriggered).toBe(false); expect(moveTriggered).toBe(true); moveTriggered = false; // Move out of the zone in a zone that we don't track - user2.setPosition({x: 901, y: 500, direction: 'down', moving: false}); + user2.setPosition({ x: 901, y: 500, direction: "down", moving: false }); expect(enterTriggered).toBe(false); expect(moveTriggered).toBe(false); @@ -69,7 +99,7 @@ describe("PositionNotifier", () => { leaveTriggered = false; // Move back in - user2.setPosition({x: 500, y: 500, direction: 'down', moving: false}); + user2.setPosition({ x: 500, y: 500, direction: "down", moving: false }); expect(enterTriggered).toBe(true); expect(moveTriggered).toBe(false); expect(leaveTriggered).toBe(false); @@ -89,28 +119,59 @@ describe("PositionNotifier", () => { let moveTriggered = false; let leaveTriggered = false; - const positionNotifier = new PositionNotifier(300, 300, (thing: Movable, fromZone: Zone|null ) => { - enterTriggered = true; - }, (thing: Movable, position: PositionInterface) => { - moveTriggered = true; - }, (thing: Movable) => { - leaveTriggered = true; - }, () => {}, - () => {}); + const positionNotifier = new PositionNotifier( + 300, + 300, + (thing: Movable, fromZone: Zone | null) => { + enterTriggered = true; + }, + (thing: Movable, position: PositionInterface) => { + moveTriggered = true; + }, + (thing: Movable) => { + leaveTriggered = true; + }, + () => {}, + () => {} + ); - const user1 = new User(1, 'test', '10.0.0.2', { - x: 500, - y: 500, - moving: false, - direction: 'down' - }, false, positionNotifier, {} as UserSocket, [], null, 'foo', []); + const user1 = new User( + 1, + "test", + "10.0.0.2", + { + x: 500, + y: 500, + moving: false, + direction: "down", + }, + false, + positionNotifier, + {} as UserSocket, + [], + null, + "foo", + [] + ); - const user2 = new User(2, 'test', '10.0.0.2', { - x: 0, - y: 0, - moving: false, - direction: 'down' - }, false, positionNotifier, {} as UserSocket, [], null, 'foo', []); + const user2 = new User( + 2, + "test", + "10.0.0.2", + { + x: 0, + y: 0, + moving: false, + direction: "down", + }, + false, + positionNotifier, + {} as UserSocket, + [], + null, + "foo", + [] + ); const listener = {} as ZoneSocket; positionNotifier.addZoneListener(listener, 0, 0); @@ -126,14 +187,12 @@ describe("PositionNotifier", () => { positionNotifier.enter(user1); positionNotifier.enter(user2); - //expect(newUsers.length).toBe(2); expect(enterTriggered).toBe(true); enterTriggered = false; - //positionNotifier.updatePosition(user2, {x:500, y:500}, {x:0, y: 0}) - user2.setPosition({x: 500, y: 500, direction: 'down', moving: false}); + user2.setPosition({ x: 500, y: 500, direction: "down", moving: false }); expect(enterTriggered).toBe(true); expect(moveTriggered).toBe(false); @@ -184,4 +243,4 @@ describe("PositionNotifier", () => { enterTriggered = false; //expect(newUsers.length).toBe(2); }); -}) +}); diff --git a/docs/maps/api-camera.md b/docs/maps/api-camera.md index cb1fe72d..6a7fa2c2 100644 --- a/docs/maps/api-camera.md +++ b/docs/maps/api-camera.md @@ -21,4 +21,4 @@ Example : ```javascript const subscription = WA.camera.onCameraUpdate().subscribe((worldView) => console.log(worldView)); //later... -subscription.unsubscribe(); \ No newline at end of file +subscription.unsubscribe(); diff --git a/docs/maps/api-player.md b/docs/maps/api-player.md index 58d5701a..d9a89bd1 100644 --- a/docs/maps/api-player.md +++ b/docs/maps/api-player.md @@ -58,6 +58,27 @@ WA.onInit().then(() => { }) ``` +### Get the position of the player +``` +WA.player.getPosition(): Promise +``` +The player's current position is available using the `WA.player.getPosition()` function. + +`Position` has the following attributes : +* **x (number) :** The coordinate x of the current player's position. +* **y (number) :** The coordinate y of the current player's position. + + +{.alert.alert-info} +You need to wait for the end of the initialization before calling `WA.player.getPosition()` + +```typescript +WA.onInit().then(() => { + console.log('Position: ', WA.player.getPosition()); +}) +``` + + ### Get the user-room token of the player ``` diff --git a/front/src/Api/Events/CameraFollowPlayerEvent.ts b/front/src/Api/Events/CameraFollowPlayerEvent.ts new file mode 100644 index 00000000..cf34e7fc --- /dev/null +++ b/front/src/Api/Events/CameraFollowPlayerEvent.ts @@ -0,0 +1,11 @@ +import * as tg from "generic-type-guard"; + +export const isCameraFollowPlayerEvent = new tg.IsInterface() + .withProperties({ + smooth: tg.isBoolean, + }) + .get(); +/** + * A message sent from the iFrame to the game to make the camera follow player. + */ +export type CameraFollowPlayerEvent = tg.GuardedType; diff --git a/front/src/Api/Events/CameraSetEvent.ts b/front/src/Api/Events/CameraSetEvent.ts new file mode 100644 index 00000000..a3da7c62 --- /dev/null +++ b/front/src/Api/Events/CameraSetEvent.ts @@ -0,0 +1,16 @@ +import * as tg from "generic-type-guard"; + +export const isCameraSetEvent = new tg.IsInterface() + .withProperties({ + x: tg.isNumber, + y: tg.isNumber, + width: tg.isOptional(tg.isNumber), + height: tg.isOptional(tg.isNumber), + lock: tg.isBoolean, + smooth: tg.isBoolean, + }) + .get(); +/** + * A message sent from the iFrame to the game to change the camera position. + */ +export type CameraSetEvent = tg.GuardedType; diff --git a/front/src/Api/Events/GameStateEvent.ts b/front/src/Api/Events/GameStateEvent.ts index 9755ba9e..6d20ac9e 100644 --- a/front/src/Api/Events/GameStateEvent.ts +++ b/front/src/Api/Events/GameStateEvent.ts @@ -9,8 +9,8 @@ export const isGameStateEvent = new tg.IsInterface() startLayerName: tg.isUnion(tg.isString, tg.isNull), tags: tg.isArray(tg.isString), variables: tg.isObject, - userRoomToken: tg.isUnion(tg.isString, tg.isUndefined), playerVariables: tg.isObject, + userRoomToken: tg.isUnion(tg.isString, tg.isUndefined), }) .get(); /** diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 8fb488dc..93d0735c 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -28,10 +28,12 @@ import type { MessageReferenceEvent } from "./ui/TriggerActionMessageEvent"; import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent"; import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent"; import type { ChangeLayerEvent } from "./ChangeLayerEvent"; -import type { ChangeZoneEvent } from "./ChangeZoneEvent"; -import { isColorEvent } from "./ColorEvent"; import { isPlayerPosition } from "./PlayerPosition"; import type { WasCameraUpdatedEvent } from "./WasCameraUpdatedEvent"; +import type { ChangeZoneEvent } from "./ChangeZoneEvent"; +import type { CameraSetEvent } from "./CameraSetEvent"; +import type { CameraFollowPlayerEvent } from "./CameraFollowPlayerEvent"; +import { isColorEvent } from "./ColorEvent"; export interface TypedMessageEvent extends MessageEvent { data: T; @@ -43,6 +45,8 @@ export interface TypedMessageEvent extends MessageEvent { export type IframeEventMap = { loadPage: LoadPageEvent; chat: ChatEvent; + cameraFollowPlayer: CameraFollowPlayerEvent; + cameraSet: CameraSetEvent; openPopup: OpenPopupEvent; closePopup: ClosePopupEvent; openTab: OpenTabEvent; diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 85e1373b..27486d5d 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -33,6 +33,8 @@ import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Store import type { ChangeLayerEvent } from "./Events/ChangeLayerEvent"; import type { WasCameraUpdatedEvent } from "./Events/WasCameraUpdatedEvent"; import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent"; +import { CameraSetEvent, isCameraSetEvent } from "./Events/CameraSetEvent"; +import { CameraFollowPlayerEvent, isCameraFollowPlayerEvent } from "./Events/CameraFollowPlayerEvent"; type AnswererCallback = ( query: IframeQueryMap[T]["query"], @@ -56,6 +58,12 @@ class IframeListener { private readonly _disablePlayerControlStream: Subject = new Subject(); public readonly disablePlayerControlStream = this._disablePlayerControlStream.asObservable(); + private readonly _cameraSetStream: Subject = new Subject(); + public readonly cameraSetStream = this._cameraSetStream.asObservable(); + + private readonly _cameraFollowPlayerStream: Subject = new Subject(); + public readonly cameraFollowPlayerStream = this._cameraFollowPlayerStream.asObservable(); + private readonly _enablePlayerControlStream: Subject = new Subject(); public readonly enablePlayerControlStream = this._enablePlayerControlStream.asObservable(); @@ -202,6 +210,10 @@ class IframeListener { this._hideLayerStream.next(payload.data); } else if (payload.type === "setProperty" && isSetPropertyEvent(payload.data)) { this._setPropertyStream.next(payload.data); + } else if (payload.type === "cameraSet" && isCameraSetEvent(payload.data)) { + this._cameraSetStream.next(payload.data); + } else if (payload.type === "cameraFollowPlayer" && isCameraFollowPlayerEvent(payload.data)) { + this._cameraFollowPlayerStream.next(payload.data); } else if (payload.type === "chat" && isChatEvent(payload.data)) { scriptUtils.sendAnonymousChat(payload.data); } else if (payload.type === "openPopup" && isOpenPopupEvent(payload.data)) { diff --git a/front/src/Api/iframe/camera.ts b/front/src/Api/iframe/camera.ts index a832290e..38199e0d 100644 --- a/front/src/Api/iframe/camera.ts +++ b/front/src/Api/iframe/camera.ts @@ -17,6 +17,27 @@ export class WorkAdventureCameraCommands extends IframeApiContribution { sendToWorkadventure({ type: "onCameraUpdate", diff --git a/front/src/Api/iframe/chat.ts b/front/src/Api/iframe/chat.ts index 5797df5a..c642b5a8 100644 --- a/front/src/Api/iframe/chat.ts +++ b/front/src/Api/iframe/chat.ts @@ -1,4 +1,3 @@ -import type { ChatEvent } from "../Events/ChatEvent"; import { isUserInputChatEvent, UserInputChatEvent } from "../Events/UserInputChatEvent"; import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; import { apiCallback } from "./registeredCallbacks"; diff --git a/front/src/Phaser/Game/CameraManager.ts b/front/src/Phaser/Game/CameraManager.ts index 55ff34de..3c4f6035 100644 --- a/front/src/Phaser/Game/CameraManager.ts +++ b/front/src/Phaser/Game/CameraManager.ts @@ -1,28 +1,49 @@ import { Easing } from "../../types"; import { HtmlUtils } from "../../WebRtc/HtmlUtils"; import type { Box } from "../../WebRtc/LayoutManager"; -import type { Player } from "../Player/Player"; -import type { WaScaleManager } from "../Services/WaScaleManager"; +import { hasMovedEventName, Player } from "../Player/Player"; +import { WaScaleManager, WaScaleManagerEvent, WaScaleManagerFocusTarget } from "../Services/WaScaleManager"; import type { GameScene } from "./GameScene"; export enum CameraMode { - Free = "Free", + /** + * Camera looks at certain point but is not locked and will start following the player on his movement + */ + Positioned = "Positioned", + /** + * Camera is actively following the player + */ Follow = "Follow", + /** + * Camera is focusing on certain point and will not break this focus even on player movement + */ Focus = "Focus", } +export enum CameraManagerEvent { + CameraUpdate = "CameraUpdate", +} + +export interface CameraManagerEventCameraUpdateData { + x: number; + y: number; + width: number; + height: number; + zoom: number; +} + export class CameraManager extends Phaser.Events.EventEmitter { private scene: GameScene; private camera: Phaser.Cameras.Scene2D.Camera; private cameraBounds: { x: number; y: number }; private waScaleManager: WaScaleManager; - private cameraMode: CameraMode = CameraMode.Free; + private cameraMode: CameraMode = CameraMode.Positioned; private restoreZoomTween?: Phaser.Tweens.Tween; private startFollowTween?: Phaser.Tweens.Tween; - private cameraFollowTarget?: { x: number; y: number }; + private playerToFollow?: Player; private cameraLocked: boolean; constructor(scene: GameScene, cameraBounds: { x: number; y: number }, waScaleManager: WaScaleManager) { @@ -41,7 +62,7 @@ export class CameraManager extends Phaser.Events.EventEmitter { } public destroy(): void { - this.scene.game.events.off("wa-scale-manager:refresh-focus-on-target"); + this.scene.game.events.off(WaScaleManagerEvent.RefreshFocusOnTarget); super.destroy(); } @@ -49,52 +70,96 @@ export class CameraManager extends Phaser.Events.EventEmitter { return this.camera; } - public enterFocusMode( - focusOn: { x: number; y: number; width: number; height: number }, - margin: number = 0, - duration: number = 1000 - ): void { + /** + * Set camera view to specific destination without changing current camera mode. Won't work if camera mode is set to Focus. + * @param setTo Viewport on which the camera should set the position + * @param duration Time for the transition im MS. If set to 0, transition will occur immediately + */ + public setPosition(setTo: WaScaleManagerFocusTarget, duration: number = 1000): void { + if (this.cameraMode === CameraMode.Focus) { + return; + } + this.setCameraMode(CameraMode.Positioned); + this.waScaleManager.saveZoom(); + this.camera.stopFollow(); + + const currentZoomModifier = this.waScaleManager.zoomModifier; + const zoomModifierChange = this.getZoomModifierChange(setTo.width, setTo.height); + + if (duration === 0) { + this.waScaleManager.zoomModifier = currentZoomModifier + zoomModifierChange; + this.camera.centerOn(setTo.x, setTo.y); + this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData()); + this.playerToFollow?.once(hasMovedEventName, () => { + if (this.playerToFollow) { + this.startFollowPlayer(this.playerToFollow, duration); + } + }); + return; + } + this.camera.pan(setTo.x, setTo.y, duration, Easing.SineEaseOut, true, (camera, progress, x, y) => { + if (this.cameraMode === CameraMode.Positioned) { + this.waScaleManager.zoomModifier = currentZoomModifier + progress * zoomModifierChange; + this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData()); + } + if (progress === 1) { + this.playerToFollow?.once(hasMovedEventName, () => { + if (this.playerToFollow) { + this.startFollowPlayer(this.playerToFollow, duration); + } + }); + } + }); + } + + /** + * Set camera to focus mode. As long as the camera is in the Focus mode, its view cannot be changed. + * @param setTo Viewport on which the camera should focus on + * @param duration Time for the transition im MS. If set to 0, transition will occur immediately + */ + public enterFocusMode(focusOn: WaScaleManagerFocusTarget, margin: number = 0, duration: number = 1000): void { this.setCameraMode(CameraMode.Focus); this.waScaleManager.saveZoom(); this.waScaleManager.setFocusTarget(focusOn); - this.cameraLocked = true; + this.unlockCameraWithDelay(duration); this.restoreZoomTween?.stop(); this.startFollowTween?.stop(); - const marginMult = 1 + margin; - const targetZoomModifier = this.waScaleManager.getTargetZoomModifierFor( - focusOn.width * marginMult, - focusOn.height * marginMult - ); - const currentZoomModifier = this.waScaleManager.zoomModifier; - const zoomModifierChange = targetZoomModifier - currentZoomModifier; this.camera.stopFollow(); - this.cameraFollowTarget = undefined; - this.camera.pan( - focusOn.x + focusOn.width * 0.5 * marginMult, - focusOn.y + focusOn.height * 0.5 * marginMult, - duration, - Easing.SineEaseOut, - true, - (camera, progress, x, y) => { - this.waScaleManager.zoomModifier = currentZoomModifier + progress * zoomModifierChange; + this.playerToFollow = undefined; + + const currentZoomModifier = this.waScaleManager.zoomModifier; + const zoomModifierChange = this.getZoomModifierChange(focusOn.width, focusOn.height, 1 + margin); + + if (duration === 0) { + this.waScaleManager.zoomModifier = currentZoomModifier + zoomModifierChange; + this.camera.centerOn(focusOn.x, focusOn.y); + this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData()); + return; + } + this.camera.pan(focusOn.x, focusOn.y, duration, Easing.SineEaseOut, true, (camera, progress, x, y) => { + this.waScaleManager.zoomModifier = currentZoomModifier + progress * zoomModifierChange; + if (progress === 1) { + // NOTE: Making sure the last action will be centering after zoom change + this.camera.centerOn(focusOn.x, focusOn.y); } - ); + this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData()); + }); } public leaveFocusMode(player: Player, duration = 0): void { this.waScaleManager.setFocusTarget(); this.unlockCameraWithDelay(duration); - this.startFollow(player, duration); + this.startFollowPlayer(player, duration); this.restoreZoom(duration); } - public startFollow(target: object | Phaser.GameObjects.GameObject, duration: number = 0): void { - this.cameraFollowTarget = target as { x: number; y: number }; + public startFollowPlayer(player: Player, duration: number = 0): void { + this.playerToFollow = player; this.setCameraMode(CameraMode.Follow); if (duration === 0) { - this.camera.startFollow(target, true); + this.camera.startFollow(player, true); return; } const oldPos = { x: this.camera.scrollX, y: this.camera.scrollY }; @@ -104,17 +169,18 @@ export class CameraManager extends Phaser.Events.EventEmitter { duration, ease: Easing.SineEaseOut, onUpdate: (tween: Phaser.Tweens.Tween) => { - if (!this.cameraFollowTarget) { + if (!this.playerToFollow) { return; } const shiftX = - (this.cameraFollowTarget.x - this.camera.worldView.width * 0.5 - oldPos.x) * tween.getValue(); + (this.playerToFollow.x - this.camera.worldView.width * 0.5 - oldPos.x) * tween.getValue(); const shiftY = - (this.cameraFollowTarget.y - this.camera.worldView.height * 0.5 - oldPos.y) * tween.getValue(); + (this.playerToFollow.y - this.camera.worldView.height * 0.5 - oldPos.y) * tween.getValue(); this.camera.setScroll(oldPos.x + shiftX, oldPos.y + shiftY); + this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData()); }, onComplete: () => { - this.camera.startFollow(target, true); + this.camera.startFollow(player, true); }, }); } @@ -140,6 +206,18 @@ export class CameraManager extends Phaser.Events.EventEmitter { return this.cameraLocked; } + private getZoomModifierChange(width?: number, height?: number, multiplier: number = 1): number { + if (!width || !height) { + return 0; + } + const targetZoomModifier = this.waScaleManager.getTargetZoomModifierFor( + width * multiplier, + height * multiplier + ); + const currentZoomModifier = this.waScaleManager.zoomModifier; + return targetZoomModifier - currentZoomModifier; + } + private unlockCameraWithDelay(delay: number): void { this.scene.time.delayedCall(delay, () => { this.cameraLocked = false; @@ -166,6 +244,7 @@ export class CameraManager extends Phaser.Events.EventEmitter { ease: Easing.SineEaseOut, onUpdate: (tween: Phaser.Tweens.Tween) => { this.waScaleManager.zoomModifier = tween.getValue(); + this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData()); }, }); } @@ -177,13 +256,28 @@ export class CameraManager extends Phaser.Events.EventEmitter { private bindEventHandlers(): void { this.scene.game.events.on( - "wa-scale-manager:refresh-focus-on-target", + WaScaleManagerEvent.RefreshFocusOnTarget, (focusOn: { x: number; y: number; width: number; height: number }) => { if (!focusOn) { return; } - this.camera.centerOn(focusOn.x + focusOn.width * 0.5, focusOn.y + focusOn.height * 0.5); + this.camera.centerOn(focusOn.x, focusOn.y); + this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData()); } ); + + this.camera.on("followupdate", () => { + this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData()); + }); + } + + private getCameraUpdateEventData(): CameraManagerEventCameraUpdateData { + return { + x: this.camera.worldView.x, + y: this.camera.worldView.y, + width: this.camera.worldView.width, + height: this.camera.worldView.height, + zoom: this.camera.scaleManager.zoom, + }; } } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index a00f637c..7b8314f0 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -64,7 +64,7 @@ import type { ActionableItem } from "../Items/ActionableItem"; import type { ItemFactoryInterface } from "../Items/ItemFactoryInterface"; import type { ITiledMap, ITiledMapLayer, ITiledMapProperty, ITiledMapObject, ITiledTileSet } from "../Map/ITiledMap"; import type { AddPlayerInterface } from "./AddPlayerInterface"; -import { CameraManager } from "./CameraManager"; +import { CameraManager, CameraManagerEvent, CameraManagerEventCameraUpdateData } from "./CameraManager"; import type { HasPlayerMovedEvent } from "../../Api/Events/HasPlayerMovedEvent"; import type { Character } from "../Entity/Character"; @@ -75,6 +75,7 @@ import { playersStore } from "../../Stores/PlayersStore"; import { emoteStore, emoteMenuStore } from "../../Stores/EmoteStore"; import { userIsAdminStore } from "../../Stores/GameStore"; import { contactPageStore } from "../../Stores/MenuStore"; +import type { WasCameraUpdatedEvent } from "../../Api/Events/WasCameraUpdatedEvent"; import { audioManagerFileStore, audioManagerVisibilityStore } from "../../Stores/AudioManagerStore"; import EVENT_TYPE = Phaser.Scenes.Events; @@ -91,7 +92,6 @@ import { MapStore } from "../../Stores/Utils/MapStore"; import { followUsersColorStore, followUsersStore } from "../../Stores/FollowStore"; import { getColorRgbFromHue } from "../../WebRtc/ColorGenerator"; import Camera = Phaser.Cameras.Scene2D.Camera; -import type { WasCameraUpdatedEvent } from "../../Api/Events/WasCameraUpdatedEvent"; export interface GameSceneInitInterface { initPosition: PointInterface | null; @@ -569,7 +569,7 @@ export class GameScene extends DirtyScene { waScaleManager ); biggestAvailableAreaStore.recompute(); - this.cameraManager.startFollow(this.CurrentPlayer); + this.cameraManager.startFollowPlayer(this.CurrentPlayer); this.animatedTiles.init(this.Map); this.events.on("tileanimationupdate", () => (this.dirty = true)); @@ -843,7 +843,12 @@ export class GameScene extends DirtyScene { if (focusable && focusable.value === true) { const zoomMargin = zone.properties?.find((property) => property.name === "zoom_margin"); this.cameraManager.enterFocusMode( - zone, + { + x: zone.x + zone.width * 0.5, + y: zone.y + zone.height * 0.5, + width: zone.width, + height: zone.height, + }, zoomMargin ? Math.max(0, Number(zoomMargin.value)) : undefined ); break; @@ -1122,6 +1127,21 @@ ${escapedMessage} }) ); + this.iframeSubscriptionList.push( + iframeListener.cameraSetStream.subscribe((cameraSetEvent) => { + const duration = cameraSetEvent.smooth ? 1000 : 0; + cameraSetEvent.lock + ? this.cameraManager.enterFocusMode({ ...cameraSetEvent }, undefined, duration) + : this.cameraManager.setPosition({ ...cameraSetEvent }, duration); + }) + ); + + this.iframeSubscriptionList.push( + iframeListener.cameraFollowPlayerStream.subscribe((cameraFollowPlayerEvent) => { + this.cameraManager.leaveFocusMode(this.CurrentPlayer, cameraFollowPlayerEvent.smooth ? 1000 : 0); + }) + ); + this.iframeSubscriptionList.push( iframeListener.playSoundStream.subscribe((playSoundEvent) => { const url = new URL(playSoundEvent.url, this.MapUrlFile); @@ -1134,30 +1154,31 @@ ${escapedMessage} this.iframeSubscriptionList.push( iframeListener.trackCameraUpdateStream.subscribe(() => { if (!this.firstCameraUpdateSent) { - this.cameras.main.on("followupdate", (camera: Camera) => { - const cameraEvent: WasCameraUpdatedEvent = { - x: camera.worldView.x, - y: camera.worldView.y, - width: camera.worldView.width, - height: camera.worldView.height, - zoom: camera.scaleManager.zoom, - }; - if ( - this.lastCameraEvent?.x == cameraEvent.x && - this.lastCameraEvent?.y == cameraEvent.y && - this.lastCameraEvent?.width == cameraEvent.width && - this.lastCameraEvent?.height == cameraEvent.height && - this.lastCameraEvent?.zoom == cameraEvent.zoom - ) { - return; + this.cameraManager.on( + CameraManagerEvent.CameraUpdate, + (data: CameraManagerEventCameraUpdateData) => { + const cameraEvent: WasCameraUpdatedEvent = { + x: data.x, + y: data.y, + width: data.width, + height: data.height, + zoom: data.zoom, + }; + if ( + this.lastCameraEvent?.x == cameraEvent.x && + this.lastCameraEvent?.y == cameraEvent.y && + this.lastCameraEvent?.width == cameraEvent.width && + this.lastCameraEvent?.height == cameraEvent.height && + this.lastCameraEvent?.zoom == cameraEvent.zoom + ) { + return; + } + + this.lastCameraEvent = cameraEvent; + iframeListener.sendCameraUpdated(cameraEvent); + this.firstCameraUpdateSent = true; } - - this.lastCameraEvent = cameraEvent; - iframeListener.sendCameraUpdated(cameraEvent); - this.firstCameraUpdateSent = true; - }); - - iframeListener.sendCameraUpdated(this.cameras.main); + ); } }) ); diff --git a/front/src/Phaser/Services/WaScaleManager.ts b/front/src/Phaser/Services/WaScaleManager.ts index 0cf189b1..f17e5326 100644 --- a/front/src/Phaser/Services/WaScaleManager.ts +++ b/front/src/Phaser/Services/WaScaleManager.ts @@ -9,6 +9,8 @@ export enum WaScaleManagerEvent { RefreshFocusOnTarget = "wa-scale-manager:refresh-focus-on-target", } +export type WaScaleManagerFocusTarget = { x: number; y: number; width?: number; height?: number }; + export class WaScaleManager { private hdpiManager: HdpiManager; private scaleManager!: ScaleManager; @@ -16,7 +18,7 @@ export class WaScaleManager { private actualZoom: number = 1; private _saveZoom: number = 1; - private focusTarget?: { x: number; y: number; width: number; height: number }; + private focusTarget?: WaScaleManagerFocusTarget; public constructor(private minGamePixelsNumber: number, private absoluteMinPixelNumber: number) { this.hdpiManager = new HdpiManager(minGamePixelsNumber, absoluteMinPixelNumber); @@ -72,11 +74,13 @@ export class WaScaleManager { if (!this.focusTarget) { return; } - this.zoomModifier = this.getTargetZoomModifierFor(this.focusTarget.width, this.focusTarget.height); + if (this.focusTarget.width && this.focusTarget.height) { + this.zoomModifier = this.getTargetZoomModifierFor(this.focusTarget.width, this.focusTarget.height); + } this.game.events.emit(WaScaleManagerEvent.RefreshFocusOnTarget, this.focusTarget); } - public setFocusTarget(targetDimensions?: { x: number; y: number; width: number; height: number }): void { + public setFocusTarget(targetDimensions?: WaScaleManagerFocusTarget): void { this.focusTarget = targetDimensions; } @@ -109,7 +113,7 @@ export class WaScaleManager { } } - public getFocusTarget(): { x: number; y: number; width: number; height: number } | undefined { + public getFocusTarget(): WaScaleManagerFocusTarget | undefined { return this.focusTarget; } diff --git a/front/src/iframe_api.ts b/front/src/iframe_api.ts index 66ee77c0..85517e6e 100644 --- a/front/src/iframe_api.ts +++ b/front/src/iframe_api.ts @@ -34,9 +34,9 @@ const initPromise = queryWorkadventure({ setMapURL(gameState.mapUrl); setTags(gameState.tags); setUuid(gameState.uuid); + setUserRoomToken(gameState.userRoomToken); globalState.initVariables(gameState.variables as Map); player.state.initVariables(gameState.playerVariables as Map); - setUserRoomToken(gameState.userRoomToken); }); const wa = { diff --git a/maps/tests/CameraApi/camera_api_test.json b/maps/tests/CameraApi/camera_api_test.json new file mode 100644 index 00000000..84cc33d6 --- /dev/null +++ b/maps/tests/CameraApi/camera_api_test.json @@ -0,0 +1,188 @@ +{ "compressionlevel":-1, + "height":30, + "infinite":false, + "layers":[ + { + "data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + "height":30, + "id":1, + "name":"floor", + "opacity":1, + "properties":[ + { + "name":"openWebsite", + "type":"string", + "value":"script.php" + }, + { + "name":"openWebsiteAllowApi", + "type":"bool", + "value":true + }], + "type":"tilelayer", + "visible":true, + "width":30, + "x":0, + "y":0 + }, + { + "data":[24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":30, + "id":6, + "name":"furnitures", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":30, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":30, + "id":2, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":30, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":3, + "name":"floorLayer", + "objects":[ + { + "height":115.775966464835, + "id":1, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"MS Shell Dlg 2", + "pixelsize":21, + "text":"Set Viewport - Move the camera to the given point. It will be locked if Lock checkbox is checked", + "wrap":true + }, + "type":"", + "visible":true, + "width":315.4375, + "x":68.4021076998051, + "y":160.733918128655 + }, + { + "height":115.776, + "id":4, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"MS Shell Dlg 2", + "pixelsize":21, + "text":"Follow Player - Camera will start following the player if doesn't do this already", + "wrap":true + }, + "type":"", + "visible":true, + "width":315.438, + "x":64.7309301350722, + "y":256.224715416861 + }, + { + "height":30.8202477876106, + "id":8, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"MS Shell Dlg 2", + "pixelsize":21, + "text":"x: 16 y: 16", + "wrap":true + }, + "type":"", + "visible":true, + "width":111.991330228225, + "x":39.0206367023754, + "y":2.47529762459247 + }, + { + "height":30.8202, + "id":9, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"MS Shell Dlg 2", + "pixelsize":21, + "text":"x: 464 y: 432", + "wrap":true + }, + "type":"", + "visible":true, + "width":149.997520726595, + "x":483.920662086633, + "y":417.193532976246 + }, + { + "height":113.333333333333, + "id":10, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"MS Shell Dlg 2", + "pixelsize":21, + "text":"Top: 256\/512 Center: 496\/655 Width: 480 Height: 286", + "wrap":true + }, + "type":"", + "visible":true, + "width":172, + "x":256, + "y":512 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":7, + "nextobjectid":11, + "orientation":"orthogonal", + "properties":[ + { + "name":"openWebsite", + "type":"string", + "value":"script.php" + }, + { + "name":"openWebsiteAllowApi", + "type":"bool", + "value":true + }], + "renderorder":"right-down", + "tiledversion":"1.7.2", + "tileheight":32, + "tilesets":[ + { + "columns":11, + "firstgid":1, + "image":"..\/tileset1.png", + "imageheight":352, + "imagewidth":352, + "margin":0, + "name":"tileset1", + "spacing":0, + "tilecount":121, + "tileheight":32, + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":"1.6", + "width":30 +} \ No newline at end of file diff --git a/maps/tests/CameraApi/script.php b/maps/tests/CameraApi/script.php new file mode 100644 index 00000000..24f7fff4 --- /dev/null +++ b/maps/tests/CameraApi/script.php @@ -0,0 +1,50 @@ + + + + + + + +X:
+Y:
+width:
+height:
+Smooth:
+Lock:
+ + + + + + diff --git a/maps/tests/index.html b/maps/tests/index.html index a3b937e3..4a80634c 100644 --- a/maps/tests/index.html +++ b/maps/tests/index.html @@ -219,6 +219,14 @@ Test set tiles + + + Success Failure Pending + + + Test camera API + + Success Failure Pending