@rpgjs/tiledmap 5.0.0-alpha.42 → 5.0.0-alpha.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,7 @@ import { createModule } from "@rpgjs/common";
3
3
  import { provideLoadMap } from "@rpgjs/client";
4
4
  import { TiledParser } from "./index3.js";
5
5
  import component from "./index4.js";
6
+ import { prepareTiledPhysicsData } from "./index5.js";
6
7
  const server = null;
7
8
  function provideTiledMap(options) {
8
9
  return createModule("TiledMap", [
@@ -39,6 +40,7 @@ function provideTiledMap(options) {
39
40
  basePath: options.basePath
40
41
  }
41
42
  };
43
+ prepareTiledPhysicsData(obj, obj);
42
44
  if (options.onLoadMap) {
43
45
  await options.onLoadMap(map);
44
46
  }
@@ -1,19 +1,10 @@
1
1
  import { defineModule } from "@rpgjs/common";
2
- import { resetTiledCollisionHandlers, detachTiledCollisionFromEntity, attachTiledCollisionToEntity, prepareTiledPhysicsData } from "./index5.js";
2
+ import { prepareTiledPhysicsData } from "./index5.js";
3
3
  const client = defineModule({
4
4
  componentAnimations: [],
5
5
  sceneMap: {
6
6
  onPhysicsInit(map, context) {
7
7
  prepareTiledPhysicsData(context?.mapData, map);
8
- },
9
- onPhysicsEntityAdd(map, context) {
10
- attachTiledCollisionToEntity(context?.owner, map);
11
- },
12
- onPhysicsEntityRemove(map, context) {
13
- detachTiledCollisionFromEntity(context?.owner, map);
14
- },
15
- onPhysicsReset(map) {
16
- resetTiledCollisionHandlers(map);
17
8
  }
18
9
  }
19
10
  });
@@ -1,72 +1,18 @@
1
1
  import { MapClass } from "./index3.js";
2
+ const TILED_HITBOX_ID_PREFIX = "__tiled_collision__:";
2
3
  function prepareTiledPhysicsData(mapData, map) {
3
4
  if (!mapData?.parsedMap) {
4
5
  return;
5
6
  }
6
7
  const tiledMap = new MapClass(mapData.parsedMap);
7
8
  map.tiled = tiledMap;
8
- mapData.hitboxes = mapData.hitboxes || [];
9
+ const tiledHitboxes = collectBlockedTileHitboxes(tiledMap);
10
+ mapData.hitboxes = mergeTiledHitboxes(mapData.hitboxes, tiledHitboxes);
9
11
  mapData.width = tiledMap.widthPx;
10
12
  mapData.height = tiledMap.heightPx;
11
- map._tiledTileWidth = tiledMap.tilewidth;
12
- map._tiledTileHeight = tiledMap.tileheight;
13
- map._blockedTiles = collectBlockedTiles(tiledMap);
14
13
  }
15
- function attachTiledCollisionToEntity(owner, map) {
16
- if (!owner?.id || !map?._blockedTiles) {
17
- return;
18
- }
19
- const entity = map.physic?.getEntityByUUID(owner.id);
20
- if (!entity || typeof entity.canEnterTile !== "function") {
21
- return;
22
- }
23
- const unsubscribers = ensureUnsubscribers(map);
24
- const previousUnsubscribe = unsubscribers.get(owner.id);
25
- if (previousUnsubscribe) {
26
- previousUnsubscribe();
27
- unsubscribers.delete(owner.id);
28
- }
29
- const blockedTiles = map._blockedTiles;
30
- const tiledTileWidth = map._tiledTileWidth ?? 32;
31
- const tiledTileHeight = map._tiledTileHeight ?? 32;
32
- const physicsTileWidth = 32;
33
- const physicsTileHeight = 32;
34
- const unsubscribe = entity.canEnterTile(({ x, y }) => {
35
- const tiledX = Math.floor(x * physicsTileWidth / tiledTileWidth);
36
- const tiledY = Math.floor(y * physicsTileHeight / tiledTileHeight);
37
- return !blockedTiles.has(`${tiledX},${tiledY}`);
38
- });
39
- unsubscribers.set(owner.id, unsubscribe);
40
- }
41
- function detachTiledCollisionFromEntity(owner, map) {
42
- if (!owner?.id) {
43
- return;
44
- }
45
- const unsubscribers = map._tiledCollisionUnsubscribers;
46
- if (!unsubscribers) {
47
- return;
48
- }
49
- const unsubscribe = unsubscribers.get(owner.id);
50
- if (!unsubscribe) {
51
- return;
52
- }
53
- unsubscribe();
54
- unsubscribers.delete(owner.id);
55
- }
56
- function resetTiledCollisionHandlers(map) {
57
- const unsubscribers = map._tiledCollisionUnsubscribers;
58
- if (unsubscribers) {
59
- for (const unsubscribe of unsubscribers.values()) {
60
- unsubscribe();
61
- }
62
- unsubscribers.clear();
63
- }
64
- map._blockedTiles = void 0;
65
- map._tiledTileWidth = void 0;
66
- map._tiledTileHeight = void 0;
67
- }
68
- function collectBlockedTiles(tiledMap) {
69
- const blockedTiles = /* @__PURE__ */ new Set();
14
+ function collectBlockedTileHitboxes(tiledMap) {
15
+ const hitboxes = [];
70
16
  const mapWidth = tiledMap.width;
71
17
  const mapHeight = tiledMap.height;
72
18
  const tileWidth = tiledMap.tilewidth;
@@ -77,19 +23,28 @@ function collectBlockedTiles(tiledMap) {
77
23
  populateTiles: true
78
24
  });
79
25
  if (tileInfo.hasCollision) {
80
- blockedTiles.add(`${x},${y}`);
26
+ hitboxes.push({
27
+ id: createTiledHitboxId(x, y),
28
+ x: x * tileWidth,
29
+ y: y * tileHeight,
30
+ width: tileWidth,
31
+ height: tileHeight
32
+ });
81
33
  }
82
34
  }
83
35
  }
84
- return blockedTiles;
36
+ return hitboxes;
37
+ }
38
+ function mergeTiledHitboxes(existingHitboxes, tiledHitboxes) {
39
+ const preservedHitboxes = Array.isArray(existingHitboxes) ? existingHitboxes.filter((hitbox) => !isGeneratedTiledHitbox(hitbox)) : [];
40
+ return [...preservedHitboxes, ...tiledHitboxes];
41
+ }
42
+ function isGeneratedTiledHitbox(hitbox) {
43
+ return typeof hitbox?.id === "string" && hitbox.id.startsWith(TILED_HITBOX_ID_PREFIX);
85
44
  }
86
- function ensureUnsubscribers(map) {
87
- map._tiledCollisionUnsubscribers = map._tiledCollisionUnsubscribers || /* @__PURE__ */ new Map();
88
- return map._tiledCollisionUnsubscribers;
45
+ function createTiledHitboxId(x, y) {
46
+ return `${TILED_HITBOX_ID_PREFIX}${x},${y}`;
89
47
  }
90
48
  export {
91
- attachTiledCollisionToEntity,
92
- detachTiledCollisionFromEntity,
93
- prepareTiledPhysicsData,
94
- resetTiledCollisionHandlers
49
+ prepareTiledPhysicsData
95
50
  };
package/dist/physics.d.ts CHANGED
@@ -1,17 +1,7 @@
1
1
  import { MapClass } from '@canvasengine/tiled';
2
2
  type AnyMap = {
3
3
  tiled?: MapClass;
4
- physic?: {
5
- getEntityByUUID(id: string): any;
6
- };
7
- _blockedTiles?: Set<string>;
8
- _tiledTileWidth?: number;
9
- _tiledTileHeight?: number;
10
- _tiledCollisionUnsubscribers?: Map<string, () => void>;
11
4
  };
12
5
  export declare function prepareTiledPhysicsData(mapData: any, map: AnyMap): void;
13
6
  export declare function applyTiledPointEvents(mapData: any): void;
14
- export declare function attachTiledCollisionToEntity(owner: any, map: AnyMap): void;
15
- export declare function detachTiledCollisionFromEntity(owner: any, map: AnyMap): void;
16
- export declare function resetTiledCollisionHandlers(map: AnyMap): void;
17
7
  export {};
@@ -1,5 +1,5 @@
1
1
  import { defineModule } from "@rpgjs/common";
2
- import { resetTiledCollisionHandlers, detachTiledCollisionFromEntity, attachTiledCollisionToEntity, prepareTiledPhysicsData, applyTiledPointEvents } from "./index4.js";
2
+ import { prepareTiledPhysicsData, applyTiledPointEvents } from "./index4.js";
3
3
  const server = defineModule({
4
4
  map: {
5
5
  onBeforeUpdate(mapData, map) {
@@ -8,18 +8,7 @@ const server = defineModule({
8
8
  return map;
9
9
  },
10
10
  onPhysicsInit(map, context) {
11
- if (!map?._blockedTiles || !map?.tiled) {
12
- prepareTiledPhysicsData(context?.mapData, map);
13
- }
14
- },
15
- onPhysicsEntityAdd(map, context) {
16
- attachTiledCollisionToEntity(context?.owner, map);
17
- },
18
- onPhysicsEntityRemove(map, context) {
19
- detachTiledCollisionFromEntity(context?.owner, map);
20
- },
21
- onPhysicsReset(map) {
22
- resetTiledCollisionHandlers(map);
11
+ prepareTiledPhysicsData(context?.mapData, map);
23
12
  }
24
13
  }
25
14
  });
@@ -1,16 +1,15 @@
1
1
  import { MapClass } from "./index3.js";
2
+ const TILED_HITBOX_ID_PREFIX = "__tiled_collision__:";
2
3
  function prepareTiledPhysicsData(mapData, map) {
3
4
  if (!mapData?.parsedMap) {
4
5
  return;
5
6
  }
6
7
  const tiledMap = new MapClass(mapData.parsedMap);
7
8
  map.tiled = tiledMap;
8
- mapData.hitboxes = mapData.hitboxes || [];
9
+ const tiledHitboxes = collectBlockedTileHitboxes(tiledMap);
10
+ mapData.hitboxes = mergeTiledHitboxes(mapData.hitboxes, tiledHitboxes);
9
11
  mapData.width = tiledMap.widthPx;
10
12
  mapData.height = tiledMap.heightPx;
11
- map._tiledTileWidth = tiledMap.tilewidth;
12
- map._tiledTileHeight = tiledMap.tileheight;
13
- map._blockedTiles = collectBlockedTiles(tiledMap);
14
13
  }
15
14
  function applyTiledPointEvents(mapData) {
16
15
  const objects = mapData?.parsedMap?.objects;
@@ -33,61 +32,8 @@ function applyTiledPointEvents(mapData) {
33
32
  }).filter((eventEntry) => eventEntry !== null);
34
33
  }
35
34
  }
36
- function attachTiledCollisionToEntity(owner, map) {
37
- if (!owner?.id || !map?._blockedTiles) {
38
- return;
39
- }
40
- const entity = map.physic?.getEntityByUUID(owner.id);
41
- if (!entity || typeof entity.canEnterTile !== "function") {
42
- return;
43
- }
44
- const unsubscribers = ensureUnsubscribers(map);
45
- const previousUnsubscribe = unsubscribers.get(owner.id);
46
- if (previousUnsubscribe) {
47
- previousUnsubscribe();
48
- unsubscribers.delete(owner.id);
49
- }
50
- const blockedTiles = map._blockedTiles;
51
- const tiledTileWidth = map._tiledTileWidth ?? 32;
52
- const tiledTileHeight = map._tiledTileHeight ?? 32;
53
- const physicsTileWidth = 32;
54
- const physicsTileHeight = 32;
55
- const unsubscribe = entity.canEnterTile(({ x, y }) => {
56
- const tiledX = Math.floor(x * physicsTileWidth / tiledTileWidth);
57
- const tiledY = Math.floor(y * physicsTileHeight / tiledTileHeight);
58
- return !blockedTiles.has(`${tiledX},${tiledY}`);
59
- });
60
- unsubscribers.set(owner.id, unsubscribe);
61
- }
62
- function detachTiledCollisionFromEntity(owner, map) {
63
- if (!owner?.id) {
64
- return;
65
- }
66
- const unsubscribers = map._tiledCollisionUnsubscribers;
67
- if (!unsubscribers) {
68
- return;
69
- }
70
- const unsubscribe = unsubscribers.get(owner.id);
71
- if (!unsubscribe) {
72
- return;
73
- }
74
- unsubscribe();
75
- unsubscribers.delete(owner.id);
76
- }
77
- function resetTiledCollisionHandlers(map) {
78
- const unsubscribers = map._tiledCollisionUnsubscribers;
79
- if (unsubscribers) {
80
- for (const unsubscribe of unsubscribers.values()) {
81
- unsubscribe();
82
- }
83
- unsubscribers.clear();
84
- }
85
- map._blockedTiles = void 0;
86
- map._tiledTileWidth = void 0;
87
- map._tiledTileHeight = void 0;
88
- }
89
- function collectBlockedTiles(tiledMap) {
90
- const blockedTiles = /* @__PURE__ */ new Set();
35
+ function collectBlockedTileHitboxes(tiledMap) {
36
+ const hitboxes = [];
91
37
  const mapWidth = tiledMap.width;
92
38
  const mapHeight = tiledMap.height;
93
39
  const tileWidth = tiledMap.tilewidth;
@@ -98,20 +44,29 @@ function collectBlockedTiles(tiledMap) {
98
44
  populateTiles: true
99
45
  });
100
46
  if (tileInfo.hasCollision) {
101
- blockedTiles.add(`${x},${y}`);
47
+ hitboxes.push({
48
+ id: createTiledHitboxId(x, y),
49
+ x: x * tileWidth,
50
+ y: y * tileHeight,
51
+ width: tileWidth,
52
+ height: tileHeight
53
+ });
102
54
  }
103
55
  }
104
56
  }
105
- return blockedTiles;
57
+ return hitboxes;
58
+ }
59
+ function mergeTiledHitboxes(existingHitboxes, tiledHitboxes) {
60
+ const preservedHitboxes = Array.isArray(existingHitboxes) ? existingHitboxes.filter((hitbox) => !isGeneratedTiledHitbox(hitbox)) : [];
61
+ return [...preservedHitboxes, ...tiledHitboxes];
62
+ }
63
+ function isGeneratedTiledHitbox(hitbox) {
64
+ return typeof hitbox?.id === "string" && hitbox.id.startsWith(TILED_HITBOX_ID_PREFIX);
106
65
  }
107
- function ensureUnsubscribers(map) {
108
- map._tiledCollisionUnsubscribers = map._tiledCollisionUnsubscribers || /* @__PURE__ */ new Map();
109
- return map._tiledCollisionUnsubscribers;
66
+ function createTiledHitboxId(x, y) {
67
+ return `${TILED_HITBOX_ID_PREFIX}${x},${y}`;
110
68
  }
111
69
  export {
112
70
  applyTiledPointEvents,
113
- attachTiledCollisionToEntity,
114
- detachTiledCollisionFromEntity,
115
- prepareTiledPhysicsData,
116
- resetTiledCollisionHandlers
71
+ prepareTiledPhysicsData
117
72
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpgjs/tiledmap",
3
- "version": "5.0.0-alpha.42",
3
+ "version": "5.0.0-alpha.44",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "exports": {
@@ -23,10 +23,10 @@
23
23
  "description": "RPGJS is a framework for creating RPG/MMORPG games",
24
24
  "peerDependencies": {
25
25
  "@canvasengine/presets": "*",
26
- "@rpgjs/client": "5.0.0-alpha.42",
27
- "@rpgjs/common": "5.0.0-alpha.42",
28
- "@rpgjs/server": "5.0.0-alpha.42",
29
- "@rpgjs/vite": "5.0.0-alpha.42",
26
+ "@rpgjs/client": "5.0.0-alpha.44",
27
+ "@rpgjs/common": "5.0.0-alpha.44",
28
+ "@rpgjs/server": "5.0.0-alpha.44",
29
+ "@rpgjs/vite": "5.0.0-alpha.44",
30
30
  "canvasengine": "*"
31
31
  },
32
32
  "publishConfig": {
package/src/client.ts CHANGED
@@ -1,11 +1,6 @@
1
1
  import { RpgClient } from "@rpgjs/client";
2
2
  import { defineModule } from "@rpgjs/common";
3
- import {
4
- attachTiledCollisionToEntity,
5
- detachTiledCollisionFromEntity,
6
- prepareTiledPhysicsData,
7
- resetTiledCollisionHandlers,
8
- } from "./physics";
3
+ import { prepareTiledPhysicsData } from "./physics";
9
4
 
10
5
  export default defineModule<RpgClient>({
11
6
  componentAnimations: [],
@@ -13,14 +8,5 @@ export default defineModule<RpgClient>({
13
8
  onPhysicsInit(map: any, context: { mapData: any }) {
14
9
  prepareTiledPhysicsData(context?.mapData, map);
15
10
  },
16
- onPhysicsEntityAdd(map: any, context: { owner: any }) {
17
- attachTiledCollisionToEntity(context?.owner, map);
18
- },
19
- onPhysicsEntityRemove(map: any, context: { owner: any }) {
20
- detachTiledCollisionFromEntity(context?.owner, map);
21
- },
22
- onPhysicsReset(map: any) {
23
- resetTiledCollisionHandlers(map);
24
- },
25
11
  },
26
12
  });
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ import { createModule } from "@rpgjs/common";
4
4
  import { provideLoadMap } from "@rpgjs/client";
5
5
  import { TiledParser } from "@canvasengine/tiled";
6
6
  import Tiled from "./tiled.ce";
7
+ import { prepareTiledPhysicsData } from "./physics";
7
8
 
8
9
  export function provideTiledMap(options: {
9
10
  basePath: string;
@@ -35,7 +36,7 @@ export function provideTiledMap(options: {
35
36
  }
36
37
  parsedMap.tilesets = tilesets;
37
38
 
38
- const obj = {
39
+ const obj: any = {
39
40
  data: mapData,
40
41
  component: Tiled,
41
42
  parsedMap,
@@ -45,6 +46,9 @@ export function provideTiledMap(options: {
45
46
  },
46
47
  };
47
48
 
49
+ // Populate dimensions and static hitboxes before the first loadPhysic() call.
50
+ prepareTiledPhysicsData(obj, obj);
51
+
48
52
  if (options.onLoadMap) {
49
53
  await options.onLoadMap(map);
50
54
  }
@@ -0,0 +1,90 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+
3
+ vi.mock("@canvasengine/tiled", () => {
4
+ class MapClass {
5
+ width: number;
6
+ height: number;
7
+ tilewidth: number;
8
+ tileheight: number;
9
+ widthPx: number;
10
+ heightPx: number;
11
+ private blockedTiles: Set<string>;
12
+
13
+ constructor(parsedMap: any) {
14
+ this.width = parsedMap.width ?? 0;
15
+ this.height = parsedMap.height ?? 0;
16
+ this.tilewidth = parsedMap.tilewidth ?? 32;
17
+ this.tileheight = parsedMap.tileheight ?? 32;
18
+ this.widthPx = this.width * this.tilewidth;
19
+ this.heightPx = this.height * this.tileheight;
20
+ this.blockedTiles = new Set(parsedMap.blockedTiles ?? []);
21
+ }
22
+
23
+ getTileByPosition(x: number, y: number) {
24
+ const tileX = Math.floor(x / this.tilewidth);
25
+ const tileY = Math.floor(y / this.tileheight);
26
+ return {
27
+ hasCollision: this.blockedTiles.has(`${tileX},${tileY}`),
28
+ };
29
+ }
30
+ }
31
+
32
+ return { MapClass };
33
+ });
34
+
35
+ import { prepareTiledPhysicsData } from "./physics";
36
+
37
+ describe("prepareTiledPhysicsData", () => {
38
+ it("adds tiled collision hitboxes without duplicating them on repeated preparation", () => {
39
+ const mapData = {
40
+ parsedMap: {
41
+ width: 3,
42
+ height: 2,
43
+ tilewidth: 16,
44
+ tileheight: 20,
45
+ blockedTiles: ["1,0", "2,1"],
46
+ },
47
+ hitboxes: [{ id: "custom-hitbox", x: 4, y: 5, width: 6, height: 7 }],
48
+ };
49
+ const map: any = {};
50
+ const expectedHitboxes = [
51
+ { id: "custom-hitbox", x: 4, y: 5, width: 6, height: 7 },
52
+ { id: "__tiled_collision__:1,0", x: 16, y: 0, width: 16, height: 20 },
53
+ { id: "__tiled_collision__:2,1", x: 32, y: 20, width: 16, height: 20 },
54
+ ];
55
+
56
+ prepareTiledPhysicsData(mapData, map);
57
+
58
+ expect(mapData.width).toBe(48);
59
+ expect(mapData.height).toBe(40);
60
+ expect(mapData.hitboxes).toEqual(expectedHitboxes);
61
+
62
+ prepareTiledPhysicsData(mapData, map);
63
+
64
+ expect(mapData.hitboxes).toEqual(expectedHitboxes);
65
+ });
66
+
67
+ it("removes stale generated tiled hitboxes when blocked tiles change", () => {
68
+ const mapData = {
69
+ parsedMap: {
70
+ width: 2,
71
+ height: 1,
72
+ tilewidth: 32,
73
+ tileheight: 32,
74
+ blockedTiles: ["0,0"],
75
+ },
76
+ hitboxes: [],
77
+ };
78
+ const map: any = {};
79
+
80
+ prepareTiledPhysicsData(mapData, map);
81
+ expect(mapData.hitboxes).toEqual([
82
+ { id: "__tiled_collision__:0,0", x: 0, y: 0, width: 32, height: 32 },
83
+ ]);
84
+
85
+ mapData.parsedMap.blockedTiles = [];
86
+ prepareTiledPhysicsData(mapData, map);
87
+
88
+ expect(mapData.hitboxes).toEqual([]);
89
+ });
90
+ });
package/src/physics.ts CHANGED
@@ -2,15 +2,18 @@ import { MapClass } from "@canvasengine/tiled";
2
2
 
3
3
  type AnyMap = {
4
4
  tiled?: MapClass;
5
- physic?: {
6
- getEntityByUUID(id: string): any;
7
- };
8
- _blockedTiles?: Set<string>;
9
- _tiledTileWidth?: number;
10
- _tiledTileHeight?: number;
11
- _tiledCollisionUnsubscribers?: Map<string, () => void>;
12
5
  };
13
6
 
7
+ type RectHitbox = {
8
+ id: string;
9
+ x: number;
10
+ y: number;
11
+ width: number;
12
+ height: number;
13
+ };
14
+
15
+ const TILED_HITBOX_ID_PREFIX = "__tiled_collision__:";
16
+
14
17
  export function prepareTiledPhysicsData(mapData: any, map: AnyMap): void {
15
18
  if (!mapData?.parsedMap) {
16
19
  return;
@@ -19,13 +22,10 @@ export function prepareTiledPhysicsData(mapData: any, map: AnyMap): void {
19
22
  const tiledMap = new MapClass(mapData.parsedMap);
20
23
  map.tiled = tiledMap;
21
24
 
22
- mapData.hitboxes = mapData.hitboxes || [];
25
+ const tiledHitboxes = collectBlockedTileHitboxes(tiledMap);
26
+ mapData.hitboxes = mergeTiledHitboxes(mapData.hitboxes, tiledHitboxes);
23
27
  mapData.width = tiledMap.widthPx;
24
28
  mapData.height = tiledMap.heightPx;
25
-
26
- map._tiledTileWidth = tiledMap.tilewidth;
27
- map._tiledTileHeight = tiledMap.tileheight;
28
- map._blockedTiles = collectBlockedTiles(tiledMap);
29
29
  }
30
30
 
31
31
  export function applyTiledPointEvents(mapData: any): void {
@@ -54,70 +54,8 @@ export function applyTiledPointEvents(mapData: any): void {
54
54
  }
55
55
  }
56
56
 
57
- export function attachTiledCollisionToEntity(owner: any, map: AnyMap): void {
58
- if (!owner?.id || !map?._blockedTiles) {
59
- return;
60
- }
61
-
62
- const entity = map.physic?.getEntityByUUID(owner.id);
63
- if (!entity || typeof entity.canEnterTile !== "function") {
64
- return;
65
- }
66
-
67
- const unsubscribers = ensureUnsubscribers(map);
68
- const previousUnsubscribe = unsubscribers.get(owner.id);
69
- if (previousUnsubscribe) {
70
- previousUnsubscribe();
71
- unsubscribers.delete(owner.id);
72
- }
73
-
74
- const blockedTiles = map._blockedTiles;
75
- const tiledTileWidth = map._tiledTileWidth ?? 32;
76
- const tiledTileHeight = map._tiledTileHeight ?? 32;
77
- const physicsTileWidth = 32;
78
- const physicsTileHeight = 32;
79
-
80
- const unsubscribe = entity.canEnterTile(({ x, y }) => {
81
- const tiledX = Math.floor((x * physicsTileWidth) / tiledTileWidth);
82
- const tiledY = Math.floor((y * physicsTileHeight) / tiledTileHeight);
83
- return !blockedTiles.has(`${tiledX},${tiledY}`);
84
- });
85
-
86
- unsubscribers.set(owner.id, unsubscribe);
87
- }
88
-
89
- export function detachTiledCollisionFromEntity(owner: any, map: AnyMap): void {
90
- if (!owner?.id) {
91
- return;
92
- }
93
- const unsubscribers = map._tiledCollisionUnsubscribers;
94
- if (!unsubscribers) {
95
- return;
96
- }
97
- const unsubscribe = unsubscribers.get(owner.id);
98
- if (!unsubscribe) {
99
- return;
100
- }
101
- unsubscribe();
102
- unsubscribers.delete(owner.id);
103
- }
104
-
105
- export function resetTiledCollisionHandlers(map: AnyMap): void {
106
- const unsubscribers = map._tiledCollisionUnsubscribers;
107
- if (unsubscribers) {
108
- for (const unsubscribe of unsubscribers.values()) {
109
- unsubscribe();
110
- }
111
- unsubscribers.clear();
112
- }
113
-
114
- map._blockedTiles = undefined;
115
- map._tiledTileWidth = undefined;
116
- map._tiledTileHeight = undefined;
117
- }
118
-
119
- function collectBlockedTiles(tiledMap: MapClass): Set<string> {
120
- const blockedTiles = new Set<string>();
57
+ function collectBlockedTileHitboxes(tiledMap: MapClass): RectHitbox[] {
58
+ const hitboxes: RectHitbox[] = [];
121
59
  const mapWidth = tiledMap.width;
122
60
  const mapHeight = tiledMap.height;
123
61
  const tileWidth = tiledMap.tilewidth;
@@ -129,15 +67,32 @@ function collectBlockedTiles(tiledMap: MapClass): Set<string> {
129
67
  populateTiles: true,
130
68
  });
131
69
  if (tileInfo.hasCollision) {
132
- blockedTiles.add(`${x},${y}`);
70
+ hitboxes.push({
71
+ id: createTiledHitboxId(x, y),
72
+ x: x * tileWidth,
73
+ y: y * tileHeight,
74
+ width: tileWidth,
75
+ height: tileHeight,
76
+ });
133
77
  }
134
78
  }
135
79
  }
136
80
 
137
- return blockedTiles;
81
+ return hitboxes;
82
+ }
83
+
84
+ function mergeTiledHitboxes(existingHitboxes: any, tiledHitboxes: RectHitbox[]): any[] {
85
+ const preservedHitboxes = Array.isArray(existingHitboxes)
86
+ ? existingHitboxes.filter((hitbox) => !isGeneratedTiledHitbox(hitbox))
87
+ : [];
88
+
89
+ return [...preservedHitboxes, ...tiledHitboxes];
90
+ }
91
+
92
+ function isGeneratedTiledHitbox(hitbox: any): boolean {
93
+ return typeof hitbox?.id === "string" && hitbox.id.startsWith(TILED_HITBOX_ID_PREFIX);
138
94
  }
139
95
 
140
- function ensureUnsubscribers(map: AnyMap): Map<string, () => void> {
141
- map._tiledCollisionUnsubscribers = map._tiledCollisionUnsubscribers || new Map();
142
- return map._tiledCollisionUnsubscribers;
96
+ function createTiledHitboxId(x: number, y: number): string {
97
+ return `${TILED_HITBOX_ID_PREFIX}${x},${y}`;
143
98
  }
package/src/server.ts CHANGED
@@ -1,13 +1,7 @@
1
1
  import { RpgMap, RpgServer } from "@rpgjs/server";
2
2
  import { MapClass } from "@canvasengine/tiled";
3
3
  import { defineModule } from "@rpgjs/common";
4
- import {
5
- applyTiledPointEvents,
6
- attachTiledCollisionToEntity,
7
- detachTiledCollisionFromEntity,
8
- prepareTiledPhysicsData,
9
- resetTiledCollisionHandlers,
10
- } from "./physics";
4
+ import { applyTiledPointEvents, prepareTiledPhysicsData } from "./physics";
11
5
 
12
6
  declare module "@rpgjs/server" {
13
7
  interface RpgMap {
@@ -27,18 +21,7 @@ export default defineModule<RpgServer>({
27
21
  return map;
28
22
  },
29
23
  onPhysicsInit(map: any, context: { mapData: any }) {
30
- if (!map?._blockedTiles || !map?.tiled) {
31
- prepareTiledPhysicsData(context?.mapData, map);
32
- }
33
- },
34
- onPhysicsEntityAdd(map: any, context: { owner: any }) {
35
- attachTiledCollisionToEntity(context?.owner, map);
36
- },
37
- onPhysicsEntityRemove(map: any, context: { owner: any }) {
38
- detachTiledCollisionFromEntity(context?.owner, map);
39
- },
40
- onPhysicsReset(map: any) {
41
- resetTiledCollisionHandlers(map);
24
+ prepareTiledPhysicsData(context?.mapData, map);
42
25
  },
43
26
  },
44
27
  });