{#each [...Array(NB_BARS).keys()] as i (i)}
{/each}
diff --git a/front/src/Components/MyCamera.svelte b/front/src/Components/MyCamera.svelte
index e84d763d..125b18eb 100644
--- a/front/src/Components/MyCamera.svelte
+++ b/front/src/Components/MyCamera.svelte
@@ -1,5 +1,5 @@
diff --git a/front/src/Components/Video/VideoMediaBox.svelte b/front/src/Components/Video/VideoMediaBox.svelte
index ffd6adcc..a7aff625 100644
--- a/front/src/Components/Video/VideoMediaBox.svelte
+++ b/front/src/Components/Video/VideoMediaBox.svelte
@@ -18,6 +18,7 @@
export let peer: VideoPeer;
let streamStore = peer.streamStore;
+ let volumeStore = peer.volumeStore;
let name = peer.userName;
let statusStore = peer.statusStore;
let constraintStore = peer.constraintsStore;
@@ -93,7 +94,7 @@
/>
{#if $constraintStore && $constraintStore.audio !== false}
-
+
{/if}
diff --git a/front/src/Phaser/Components/SoundMeter.ts b/front/src/Phaser/Components/SoundMeter.ts
index 1d97be50..6e12912f 100644
--- a/front/src/Phaser/Components/SoundMeter.ts
+++ b/front/src/Phaser/Components/SoundMeter.ts
@@ -1,4 +1,4 @@
-import type { IAnalyserNode, IAudioContext, IMediaStreamAudioSourceNode } from "standardized-audio-context";
+import { AudioContext, IAnalyserNode, IAudioContext, IMediaStreamAudioSourceNode } from "standardized-audio-context";
/**
* Class to measure the sound volume of a media stream
@@ -6,41 +6,15 @@ import type { IAnalyserNode, IAudioContext, IMediaStreamAudioSourceNode } from "
export class SoundMeter {
private instant: number;
private clip: number;
- //private script: ScriptProcessorNode;
private analyser: IAnalyserNode
| undefined;
private dataArray: Uint8Array | undefined;
private context: IAudioContext | undefined;
private source: IMediaStreamAudioSourceNode | undefined;
- constructor() {
+ constructor(mediaStream: MediaStream) {
this.instant = 0.0;
this.clip = 0.0;
- //this.script = context.createScriptProcessor(2048, 1, 1);
- }
-
- private init(context: IAudioContext) {
- this.context = context;
- this.analyser = this.context.createAnalyser();
-
- this.analyser.fftSize = 2048;
- const bufferLength = this.analyser.fftSize;
- this.dataArray = new Uint8Array(bufferLength);
- }
-
- public connectToSource(stream: MediaStream, context: IAudioContext): void {
- if (this.source !== undefined) {
- this.stop();
- }
-
- this.init(context);
-
- this.source = this.context?.createMediaStreamSource(stream);
- if (this.analyser !== undefined) {
- this.source?.connect(this.analyser);
- }
- //analyser.connect(distortion);
- //distortion.connect(this.context.destination);
- //this.analyser.connect(this.context.destination);
+ this.connectToSource(mediaStream, new AudioContext());
}
public getVolume(): number {
@@ -78,4 +52,29 @@ export class SoundMeter {
this.dataArray = undefined;
this.source = undefined;
}
+
+ private init(context: IAudioContext) {
+ this.context = context;
+ this.analyser = this.context.createAnalyser();
+
+ this.analyser.fftSize = 2048;
+ const bufferLength = this.analyser.fftSize;
+ this.dataArray = new Uint8Array(bufferLength);
+ }
+
+ private connectToSource(stream: MediaStream, context: IAudioContext): void {
+ if (this.source !== undefined) {
+ this.stop();
+ }
+
+ this.init(context);
+
+ this.source = this.context?.createMediaStreamSource(stream);
+ if (this.analyser !== undefined) {
+ this.source?.connect(this.analyser);
+ }
+ //analyser.connect(distortion);
+ //distortion.connect(this.context.destination);
+ //this.analyser.connect(this.context.destination);
+ }
}
diff --git a/front/src/Phaser/Components/TalkIcon.ts b/front/src/Phaser/Components/TalkIcon.ts
new file mode 100644
index 00000000..12b4ebe3
--- /dev/null
+++ b/front/src/Phaser/Components/TalkIcon.ts
@@ -0,0 +1,58 @@
+import { Easing } from "../../types";
+
+export class TalkIcon extends Phaser.GameObjects.Image {
+ private shown: boolean;
+ private showAnimationTween?: Phaser.Tweens.Tween;
+
+ private defaultPosition: { x: number; y: number };
+ private defaultScale: number;
+
+ constructor(scene: Phaser.Scene, x: number, y: number) {
+ super(scene, x, y, "iconTalk");
+
+ this.defaultPosition = { x, y };
+ this.defaultScale = 0.3;
+
+ this.shown = false;
+ this.setAlpha(0);
+ this.setScale(this.defaultScale);
+
+ this.scene.add.existing(this);
+ }
+
+ public show(show: boolean = true, forceClose: boolean = false): void {
+ if (this.shown === show && !forceClose) {
+ return;
+ }
+ this.showAnimation(show, forceClose);
+ }
+
+ private showAnimation(show: boolean = true, forceClose: boolean = false) {
+ if (forceClose && !show) {
+ this.showAnimationTween?.stop();
+ } else if (this.showAnimationTween?.isPlaying()) {
+ return;
+ }
+ this.shown = show;
+ if (show) {
+ this.y += 50;
+ this.scale = 0.05;
+ this.alpha = 0;
+ }
+ this.showAnimationTween = this.scene.tweens.add({
+ targets: [this],
+ duration: 350,
+ alpha: show ? 1 : 0,
+ y: this.defaultPosition.y,
+ scale: this.defaultScale,
+ ease: Easing.BackEaseOut,
+ onComplete: () => {
+ this.showAnimationTween = undefined;
+ },
+ });
+ }
+
+ public isShown(): boolean {
+ return this.shown;
+ }
+}
diff --git a/front/src/Phaser/Entity/Character.ts b/front/src/Phaser/Entity/Character.ts
index cafe353c..14c4de12 100644
--- a/front/src/Phaser/Entity/Character.ts
+++ b/front/src/Phaser/Entity/Character.ts
@@ -17,6 +17,7 @@ import { Unsubscriber, Writable, writable } from "svelte/store";
import { createColorStore } from "../../Stores/OutlineColorStore";
import type { OutlineableInterface } from "../Game/OutlineableInterface";
import type CancelablePromise from "cancelable-promise";
+import { TalkIcon } from "../Components/TalkIcon";
const playerNameY = -25;
@@ -33,6 +34,7 @@ const interactiveRadius = 35;
export abstract class Character extends Container implements OutlineableInterface {
private bubble: SpeechBubble | null = null;
private readonly playerNameText: Text;
+ private readonly talkIcon: TalkIcon;
public playerName: string;
public sprites: Map;
protected lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down;
@@ -102,6 +104,17 @@ export abstract class Character extends Container implements OutlineableInterfac
fontSize: 35,
},
});
+
+ this.talkIcon = new TalkIcon(scene, 0, -45);
+ this.add(this.talkIcon);
+
+ if (isClickable) {
+ this.setInteractive({
+ hitArea: new Phaser.Geom.Circle(0, 0, interactiveRadius),
+ hitAreaCallback: Phaser.Geom.Circle.Contains, //eslint-disable-line @typescript-eslint/unbound-method
+ useHandCursor: true,
+ });
+ }
this.playerNameText.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
this.add(this.playerNameText);
@@ -200,6 +213,10 @@ export abstract class Character extends Container implements OutlineableInterfac
});
}
+ public showTalkIcon(show: boolean = true, forceClose: boolean = false): void {
+ this.talkIcon.show(show, forceClose);
+ }
+
public addCompanion(name: string, texturePromise?: CancelablePromise): void {
if (typeof texturePromise !== "undefined") {
this.companion = new Companion(this.scene, this.x, this.y, name, texturePromise);
diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts
index a407db4e..7646c076 100644
--- a/front/src/Phaser/Game/GameMap.ts
+++ b/front/src/Phaser/Game/GameMap.ts
@@ -133,6 +133,18 @@ export class GameMap {
return grid;
}
+ public getWalkingCostGrid(): number[][] {
+ const grid: number[][] = [];
+ for (let y = 0; y < this.map.height; y += 1) {
+ const row: number[] = [];
+ for (let x = 0; x < this.map.width; x += 1) {
+ row.push(this.getWalkingCostAt(x, y));
+ }
+ grid.push(row);
+ }
+ return grid;
+ }
+
public getTileDimensions(): { width: number; height: number } {
return { width: this.map.tilewidth, height: this.map.tileheight };
}
@@ -356,6 +368,32 @@ export class GameMap {
return false;
}
+ private getWalkingCostAt(x: number, y: number): number {
+ const bigCost = 100;
+ for (const layer of this.phaserLayers) {
+ if (!layer.visible) {
+ continue;
+ }
+ const tile = layer.getTileAt(x, y);
+ if (!tile) {
+ continue;
+ }
+ if (
+ tile &&
+ (tile.properties[GameMapProperties.EXIT_URL] || tile.properties[GameMapProperties.EXIT_SCENE_URL])
+ ) {
+ return bigCost;
+ }
+ for (const property of layer.layer.properties) {
+ //@ts-ignore
+ if (property.name && property.name === "exitUrl") {
+ return bigCost;
+ }
+ }
+ }
+ return 0;
+ }
+
private triggerAllProperties(): void {
const newProps = this.getProperties(this.key ?? 0);
const oldProps = this.lastProperties;
diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts
index e0f2c8c5..ac2cbf64 100644
--- a/front/src/Phaser/Game/GameScene.ts
+++ b/front/src/Phaser/Game/GameScene.ts
@@ -92,6 +92,7 @@ import { followUsersColorStore } from "../../Stores/FollowStore";
import { GameSceneUserInputHandler } from "../UserInput/GameSceneUserInputHandler";
import { locale } from "../../i18n/i18n-svelte";
import { i18nJson } from "../../i18n/locales";
+import { localVolumeStore } from "../../Stores/MediaStore";
import { StringUtils } from "../../Utils/StringUtils";
import { startLayerNamesStore } from "../../Stores/StartLayerNamesStore";
import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite";
@@ -173,6 +174,9 @@ export class GameScene extends DirtyScene {
private peerStoreUnsubscribe!: Unsubscriber;
private emoteUnsubscribe!: Unsubscriber;
private emoteMenuUnsubscribe!: Unsubscriber;
+
+ private volumeStoreUnsubscribers: Map = new Map();
+ private localVolumeStoreUnsubscriber: Unsubscriber | undefined;
private followUsersColorStoreUnsubscribe!: Unsubscriber;
private biggestAvailableAreaStoreUnsubscribe!: () => void;
@@ -250,6 +254,7 @@ export class GameScene extends DirtyScene {
loadCustomTexture(this.load, texture).catch((e) => console.error(e));
}
}
+ this.load.image("iconTalk", "/resources/icons/icon_talking.png");
if (touchScreenManager.supportTouchScreen) {
this.load.image(joystickBaseKey, joystickBaseImg);
@@ -585,6 +590,7 @@ export class GameScene extends DirtyScene {
this.pathfindingManager = new PathfindingManager(
this,
this.gameMap.getCollisionGrid(),
+ this.gameMap.getWalkingCostGrid(),
this.gameMap.getTileDimensions()
);
@@ -600,12 +606,6 @@ export class GameScene extends DirtyScene {
waScaleManager
);
- this.pathfindingManager = new PathfindingManager(
- this,
- this.gameMap.getCollisionGrid(),
- this.gameMap.getTileDimensions()
- );
-
this.activatablesManager = new ActivatablesManager(this.CurrentPlayer);
biggestAvailableAreaStore.recompute();
@@ -659,14 +659,45 @@ export class GameScene extends DirtyScene {
this.connect();
}
+ const talkIconVolumeTreshold = 10;
let oldPeerNumber = 0;
this.peerStoreUnsubscribe = peerStore.subscribe((peers) => {
+ this.volumeStoreUnsubscribers.forEach((unsubscribe) => unsubscribe());
+ this.volumeStoreUnsubscribers.clear();
+
+ for (const [key, videoStream] of peers) {
+ this.volumeStoreUnsubscribers.set(
+ key,
+ videoStream.volumeStore.subscribe((volume) => {
+ if (volume) {
+ this.MapPlayersByKey.get(key)?.showTalkIcon(volume > talkIconVolumeTreshold);
+ }
+ })
+ );
+ }
+
const newPeerNumber = peers.size;
if (newPeerNumber > oldPeerNumber) {
this.playSound("audio-webrtc-in");
} else if (newPeerNumber < oldPeerNumber) {
this.playSound("audio-webrtc-out");
}
+ if (newPeerNumber > 0) {
+ if (!this.localVolumeStoreUnsubscriber) {
+ this.localVolumeStoreUnsubscriber = localVolumeStore.subscribe((volume) => {
+ if (volume) {
+ this.CurrentPlayer.showTalkIcon(volume > talkIconVolumeTreshold);
+ }
+ });
+ }
+ } else {
+ this.CurrentPlayer.showTalkIcon(false, true);
+ this.MapPlayersByKey.forEach((remotePlayer) => remotePlayer.showTalkIcon(false, true));
+ if (this.localVolumeStoreUnsubscriber) {
+ this.localVolumeStoreUnsubscriber();
+ this.localVolumeStoreUnsubscriber = undefined;
+ }
+ }
oldPeerNumber = newPeerNumber;
});
@@ -1464,7 +1495,7 @@ export class GameScene extends DirtyScene {
phaserLayers[i].setCollisionByProperty({ collides: true }, visible);
}
}
- this.pathfindingManager.setCollisionGrid(this.gameMap.getCollisionGrid());
+ this.pathfindingManager.setCollisionGrid(this.gameMap.getCollisionGrid(), this.gameMap.getWalkingCostGrid());
this.markDirty();
}
diff --git a/front/src/Stores/EmbedScreensStore.ts b/front/src/Stores/EmbedScreensStore.ts
index 724733b3..172ec45b 100644
--- a/front/src/Stores/EmbedScreensStore.ts
+++ b/front/src/Stores/EmbedScreensStore.ts
@@ -15,7 +15,7 @@ export type EmbedScreen =
};
function createHighlightedEmbedScreenStore() {
- const { subscribe, set, update } = writable(null);
+ const { subscribe, set, update } = writable(undefined);
return {
subscribe,
@@ -23,7 +23,7 @@ function createHighlightedEmbedScreenStore() {
set(embedScreen);
},
removeHighlight: () => {
- set(null);
+ set(undefined);
},
toggleHighlight: (embedScreen: EmbedScreen) => {
update((currentEmbedScreen) =>
@@ -36,7 +36,7 @@ function createHighlightedEmbedScreenStore() {
currentEmbedScreen.type === "streamable" &&
embedScreen.embed.uniqueId !== currentEmbedScreen.embed.uniqueId)
? embedScreen
- : null
+ : undefined
);
},
};
diff --git a/front/src/Stores/MediaStore.ts b/front/src/Stores/MediaStore.ts
index f1b41430..9494eb7e 100644
--- a/front/src/Stores/MediaStore.ts
+++ b/front/src/Stores/MediaStore.ts
@@ -10,6 +10,8 @@ import { myCameraVisibilityStore } from "./MyCameraStoreVisibility";
import { peerStore } from "./PeerStore";
import { privacyShutdownStore } from "./PrivacyShutdownStore";
import { MediaStreamConstraintsError } from "./Errors/MediaStreamConstraintsError";
+import { SoundMeter } from "../Phaser/Components/SoundMeter";
+import { AudioContext } from "standardized-audio-context";
/**
* A store that contains the camera state requested by the user (on or off).
@@ -541,6 +543,41 @@ export const obtainedMediaConstraintStore = derived(undefined, (set) => {
+ let timeout: ReturnType;
+ const unsubscribe = localStreamStore.subscribe((localStreamStoreValue) => {
+ clearInterval(timeout);
+ if (localStreamStoreValue.type === "error") {
+ set(undefined);
+ return;
+ }
+ const mediaStream = localStreamStoreValue.stream;
+
+ if (mediaStream === null || mediaStream.getAudioTracks().length <= 0) {
+ set(undefined);
+ return;
+ }
+ const soundMeter = new SoundMeter(mediaStream);
+ let error = false;
+
+ timeout = setInterval(() => {
+ try {
+ set(soundMeter.getVolume());
+ } catch (err) {
+ if (!error) {
+ console.error(err);
+ error = true;
+ }
+ }
+ }, 100);
+ });
+
+ return () => {
+ unsubscribe();
+ clearInterval(timeout);
+ };
+});
+
/**
* Device list
*/
diff --git a/front/src/Utils/PathfindingManager.ts b/front/src/Utils/PathfindingManager.ts
index c5057ed8..a129dae8 100644
--- a/front/src/Utils/PathfindingManager.ts
+++ b/front/src/Utils/PathfindingManager.ts
@@ -8,7 +8,12 @@ export class PathfindingManager {
private grid: number[][];
private tileDimensions: { width: number; height: number };
- constructor(scene: Phaser.Scene, collisionsGrid: number[][], tileDimensions: { width: number; height: number }) {
+ constructor(
+ scene: Phaser.Scene,
+ collisionsGrid: number[][],
+ walkingCostGrid: number[][],
+ tileDimensions: { width: number; height: number }
+ ) {
this.scene = scene;
this.easyStar = new EasyStar.js();
@@ -17,10 +22,12 @@ export class PathfindingManager {
this.grid = collisionsGrid;
this.tileDimensions = tileDimensions;
this.setEasyStarGrid(collisionsGrid);
+ this.setWalkingCostGrid(walkingCostGrid);
}
- public setCollisionGrid(collisionGrid: number[][]): void {
+ public setCollisionGrid(collisionGrid: number[][], walkingCostGrid: number[][]): void {
this.setEasyStarGrid(collisionGrid);
+ this.setWalkingCostGrid(walkingCostGrid);
}
public async findPath(
@@ -115,6 +122,14 @@ export class PathfindingManager {
this.easyStar.setAcceptableTiles([0]); // zeroes are walkable
}
+ private setWalkingCostGrid(grid: number[][]): void {
+ for (let y = 0; y < grid.length; y += 1) {
+ for (let x = 0; x < grid[y].length; x += 1) {
+ this.easyStar.setAdditionalPointCost(x, y, grid[y][x]);
+ }
+ }
+ }
+
private logGridToTheConsole(grid: number[][]): void {
let rowNumber = 0;
for (const row of grid) {
diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts
index a5c57ed6..a33432dc 100644
--- a/front/src/WebRtc/CoWebsiteManager.ts
+++ b/front/src/WebRtc/CoWebsiteManager.ts
@@ -159,9 +159,17 @@ class CoWebsiteManager {
});
buttonSwipe.addEventListener("click", () => {
+ const mainCoWebsite = this.getMainCoWebsite();
const highlightedEmbed = get(highlightedEmbedScreen);
if (highlightedEmbed?.type === "cowebsite") {
this.goToMain(highlightedEmbed.embed);
+
+ if (mainCoWebsite) {
+ highlightedEmbedScreen.toggleHighlight({
+ type: "cowebsite",
+ embed: mainCoWebsite,
+ });
+ }
}
});
}
@@ -553,6 +561,13 @@ class CoWebsiteManager {
coWebsites.remove(coWebsite);
coWebsites.add(coWebsite, 0);
+ if (mainCoWebsite) {
+ const iframe = mainCoWebsite.getIframe();
+ if (iframe) {
+ iframe.style.display = "block";
+ }
+ }
+
if (
isMediaBreakpointDown("lg") &&
get(embedScreenLayout) === LayoutMode.Presentation &&
@@ -596,12 +611,20 @@ class CoWebsiteManager {
.load()
.then(() => {
const mainCoWebsite = this.getMainCoWebsite();
- if (mainCoWebsite && mainCoWebsite.getId() === coWebsite.getId()) {
- this.openMain();
+ const highlightedEmbed = get(highlightedEmbedScreen);
+ if (mainCoWebsite) {
+ if (mainCoWebsite.getId() === coWebsite.getId()) {
+ this.openMain();
- setTimeout(() => {
- this.fire();
- }, animationTime);
+ setTimeout(() => {
+ this.fire();
+ }, animationTime);
+ } else if (!highlightedEmbed) {
+ highlightedEmbedScreen.toggleHighlight({
+ type: "cowebsite",
+ embed: coWebsite,
+ });
+ }
}
this.resizeAllIframes();
})
diff --git a/front/src/WebRtc/VideoPeer.ts b/front/src/WebRtc/VideoPeer.ts
index c3b6d45e..f0ebffdd 100644
--- a/front/src/WebRtc/VideoPeer.ts
+++ b/front/src/WebRtc/VideoPeer.ts
@@ -10,6 +10,9 @@ import { playersStore } from "../Stores/PlayersStore";
import { chatMessagesStore, newChatMessageSubject } from "../Stores/ChatStore";
import { getIceServersConfig } from "../Components/Video/utils";
import { isMediaBreakpointUp } from "../Utils/BreakpointsUtils";
+import { SoundMeter } from "../Phaser/Components/SoundMeter";
+import { AudioContext } from "standardized-audio-context";
+import { Console } from "console";
const Peer: SimplePeerNamespace.SimplePeer = require("simple-peer");
@@ -33,6 +36,7 @@ export class VideoPeer extends Peer {
private onBlockSubscribe: Subscription;
private onUnBlockSubscribe: Subscription;
public readonly streamStore: Readable;
+ public readonly volumeStore: Readable;
public readonly statusStore: Readable;
public readonly constraintsStore: Readable;
private newMessageSubscribtion: Subscription | undefined;
@@ -69,6 +73,34 @@ export class VideoPeer extends Peer {
};
});
+ this.volumeStore = readable(undefined, (set) => {
+ let timeout: ReturnType;
+ const unsubscribe = this.streamStore.subscribe((mediaStream) => {
+ if (mediaStream === null || mediaStream.getAudioTracks().length <= 0) {
+ set(undefined);
+ return;
+ }
+ const soundMeter = new SoundMeter(mediaStream);
+ let error = false;
+
+ timeout = setInterval(() => {
+ try {
+ set(soundMeter.getVolume());
+ } catch (err) {
+ if (!error) {
+ console.error(err);
+ error = true;
+ }
+ }
+ }, 100);
+ });
+
+ return () => {
+ unsubscribe();
+ clearInterval(timeout);
+ };
+ });
+
this.constraintsStore = readable(null, (set) => {
const onData = (chunk: Buffer) => {
const message = JSON.parse(chunk.toString("utf8"));
diff --git a/maps/tests/PathfinderAvoidExits/map1.json b/maps/tests/PathfinderAvoidExits/map1.json
new file mode 100644
index 00000000..51361ced
--- /dev/null
+++ b/maps/tests/PathfinderAvoidExits/map1.json
@@ -0,0 +1,177 @@
+{ "compressionlevel":-1,
+ "height":10,
+ "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],
+ "height":10,
+ "id":1,
+ "name":"floor",
+ "opacity":1,
+ "type":"tilelayer",
+ "visible":true,
+ "width":10,
+ "x":0,
+ "y":0
+ },
+ {
+ "data":[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],
+ "height":10,
+ "id":2,
+ "name":"start",
+ "opacity":0.9,
+ "type":"tilelayer",
+ "visible":true,
+ "width":10,
+ "x":0,
+ "y":0
+ },
+ {
+ "data":[17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17],
+ "height":10,
+ "id":7,
+ "name":"walls",
+ "opacity":1,
+ "type":"tilelayer",
+ "visible":true,
+ "width":10,
+ "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, 34, 34, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "height":10,
+ "id":8,
+ "name":"exit",
+ "opacity":1,
+ "properties":[
+ {
+ "name":"exitUrl",
+ "type":"string",
+ "value":"map2.json"
+ }],
+ "type":"tilelayer",
+ "visible":true,
+ "width":10,
+ "x":0,
+ "y":0
+ },
+ {
+ "data":[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],
+ "height":10,
+ "id":9,
+ "name":"from_exit2",
+ "opacity":1,
+ "properties":[
+ {
+ "name":"startLayer",
+ "type":"bool",
+ "value":true
+ }],
+ "type":"tilelayer",
+ "visible":true,
+ "width":10,
+ "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, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "height":10,
+ "id":10,
+ "name":"funritures",
+ "opacity":1,
+ "type":"tilelayer",
+ "visible":true,
+ "width":10,
+ "x":0,
+ "y":0
+ },
+ {
+ "draworder":"topdown",
+ "id":3,
+ "name":"floorLayer",
+ "objects":[
+ {
+ "height":58.0929629319722,
+ "id":2,
+ "name":"",
+ "rotation":0,
+ "text":
+ {
+ "text":"DO NOT fall into the water or you will drown! Use right-click to move",
+ "wrap":true
+ },
+ "type":"",
+ "visible":true,
+ "width":207.776169358213,
+ "x":78.9920090369007,
+ "y":34.4126432934483
+ }],
+ "opacity":1,
+ "type":"objectgroup",
+ "visible":true,
+ "x":0,
+ "y":0
+ }],
+ "nextlayerid":11,
+ "nextobjectid":3,
+ "orientation":"orthogonal",
+ "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,
+ "tiles":[
+ {
+ "id":16,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":17,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":18,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":19,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ }],
+ "tilewidth":32
+ }],
+ "tilewidth":32,
+ "type":"map",
+ "version":"1.6",
+ "width":10
+}
\ No newline at end of file
diff --git a/maps/tests/PathfinderAvoidExits/map2.json b/maps/tests/PathfinderAvoidExits/map2.json
new file mode 100644
index 00000000..449de40f
--- /dev/null
+++ b/maps/tests/PathfinderAvoidExits/map2.json
@@ -0,0 +1,184 @@
+{ "compressionlevel":-1,
+ "height":10,
+ "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],
+ "height":10,
+ "id":1,
+ "name":"floor",
+ "opacity":1,
+ "type":"tilelayer",
+ "visible":true,
+ "width":10,
+ "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, 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],
+ "height":10,
+ "id":2,
+ "name":"start",
+ "opacity":1,
+ "type":"tilelayer",
+ "visible":true,
+ "width":10,
+ "x":0,
+ "y":0
+ },
+ {
+ "data":[17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17],
+ "height":10,
+ "id":7,
+ "name":"walls",
+ "opacity":1,
+ "type":"tilelayer",
+ "visible":true,
+ "width":10,
+ "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, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":10,
+ "id":8,
+ "name":"exit",
+ "opacity":1,
+ "properties":[
+ {
+ "name":"exitUrl",
+ "type":"string",
+ "value":"map1.json#from_exit2"
+ }],
+ "type":"tilelayer",
+ "visible":true,
+ "width":10,
+ "x":0,
+ "y":0
+ },
+ {
+ "draworder":"topdown",
+ "id":3,
+ "name":"floorLayer",
+ "objects":[
+ {
+ "height":19,
+ "id":2,
+ "name":"",
+ "rotation":0,
+ "text":
+ {
+ "text":"YOU ",
+ "wrap":true
+ },
+ "type":"",
+ "visible":true,
+ "width":33.1966362647477,
+ "x":143.254413856581,
+ "y":65.4728056229604
+ },
+ {
+ "height":39.3497615262321,
+ "id":3,
+ "name":"",
+ "rotation":0,
+ "text":
+ {
+ "fontfamily":"MS Shell Dlg 2",
+ "pixelsize":32,
+ "text":"ARE",
+ "wrap":true
+ },
+ "type":"",
+ "visible":true,
+ "width":81.3934036147603,
+ "x":130.134191004937,
+ "y":102.423688394277
+ },
+ {
+ "height":124.497486386076,
+ "id":4,
+ "name":"",
+ "rotation":0,
+ "text":
+ {
+ "color":"#ff0000",
+ "fontfamily":"MS Shell Dlg 2",
+ "pixelsize":96,
+ "text":"DEAD",
+ "wrap":true
+ },
+ "type":"",
+ "visible":true,
+ "width":246.869092410677,
+ "x":41.7733861852564,
+ "y":157.582233294285
+ }],
+ "opacity":1,
+ "type":"objectgroup",
+ "visible":true,
+ "x":0,
+ "y":0
+ }],
+ "nextlayerid":9,
+ "nextobjectid":5,
+ "orientation":"orthogonal",
+ "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,
+ "tiles":[
+ {
+ "id":16,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":17,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":18,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":19,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ }],
+ "tilewidth":32
+ }],
+ "tilewidth":32,
+ "type":"map",
+ "version":"1.6",
+ "width":10
+}
\ No newline at end of file
diff --git a/maps/tests/index.html b/maps/tests/index.html
index e8bf4369..3b3c700a 100644
--- a/maps/tests/index.html
+++ b/maps/tests/index.html
@@ -88,6 +88,14 @@
Test exits
+
+
+ Success Failure Pending
+ |
+
+ Test Pathfinder Avoid Exits
+ |
+
Success Failure Pending
|