@rpgjs/common 3.3.2 → 4.0.0-beta.10

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.
Files changed (80) hide show
  1. package/LICENSE +19 -0
  2. package/lib/AbstractObject.d.ts +3 -2
  3. package/lib/AbstractObject.js +296 -323
  4. package/lib/AbstractObject.js.map +1 -1
  5. package/lib/Color.js +1 -5
  6. package/lib/Color.js.map +1 -1
  7. package/lib/Event.js +2 -6
  8. package/lib/Event.js.map +1 -1
  9. package/lib/EventEmitter.js +3 -7
  10. package/lib/EventEmitter.js.map +1 -1
  11. package/lib/Game.js +73 -85
  12. package/lib/Game.js.map +1 -1
  13. package/lib/Hit.js +21 -28
  14. package/lib/Hit.js.map +1 -1
  15. package/lib/Logger.js +2 -7
  16. package/lib/Logger.js.map +1 -1
  17. package/lib/Map.d.ts +1 -0
  18. package/lib/Map.js +29 -53
  19. package/lib/Map.js.map +1 -1
  20. package/lib/Module.d.ts +2 -2
  21. package/lib/Module.js +84 -97
  22. package/lib/Module.js.map +1 -1
  23. package/lib/Player.d.ts +1 -0
  24. package/lib/Player.js +4 -7
  25. package/lib/Player.js.map +1 -1
  26. package/lib/Plugin.d.ts +4 -3
  27. package/lib/Plugin.js +14 -14
  28. package/lib/Plugin.js.map +1 -1
  29. package/lib/Scheduler.js +14 -21
  30. package/lib/Scheduler.js.map +1 -1
  31. package/lib/Shape.d.ts +1 -1
  32. package/lib/Shape.js +39 -52
  33. package/lib/Shape.js.map +1 -1
  34. package/lib/Utils.d.ts +4 -1
  35. package/lib/Utils.js +38 -53
  36. package/lib/Utils.js.map +1 -1
  37. package/lib/Vector2d.js +3 -8
  38. package/lib/Vector2d.js.map +1 -1
  39. package/lib/VirtualGrid.d.ts +1 -1
  40. package/lib/VirtualGrid.js +5 -12
  41. package/lib/VirtualGrid.js.map +1 -1
  42. package/lib/Worker.js +2 -10
  43. package/lib/Worker.js.map +1 -1
  44. package/lib/WorldMaps.d.ts +3 -1
  45. package/lib/WorldMaps.js +29 -15
  46. package/lib/WorldMaps.js.map +1 -1
  47. package/lib/gui/PrebuiltGui.js +2 -5
  48. package/lib/gui/PrebuiltGui.js.map +1 -1
  49. package/lib/index.d.ts +3 -1
  50. package/lib/index.js +24 -69
  51. package/lib/index.js.map +1 -1
  52. package/lib/transports/io.js +5 -9
  53. package/lib/transports/io.js.map +1 -1
  54. package/lib/workers/move.js +26 -42
  55. package/lib/workers/move.js.map +1 -1
  56. package/package.json +9 -11
  57. package/src/AbstractObject.ts +915 -0
  58. package/src/Color.ts +29 -0
  59. package/src/DefaultInput.ts +26 -0
  60. package/src/Event.ts +3 -0
  61. package/src/EventEmitter.ts +52 -0
  62. package/src/Game.ts +150 -0
  63. package/src/Hit.ts +70 -0
  64. package/src/Logger.ts +7 -0
  65. package/src/Map.ts +335 -0
  66. package/src/Module.ts +108 -0
  67. package/src/Player.ts +30 -0
  68. package/src/Plugin.ts +91 -0
  69. package/src/Scheduler.ts +88 -0
  70. package/src/Shape.ts +300 -0
  71. package/src/Utils.ts +168 -0
  72. package/src/Vector2d.ts +70 -0
  73. package/src/VirtualGrid.ts +78 -0
  74. package/src/Worker.ts +17 -0
  75. package/src/WorldMaps.ts +204 -0
  76. package/src/gui/PrebuiltGui.ts +27 -0
  77. package/src/index.ts +24 -0
  78. package/src/transports/io.ts +91 -0
  79. package/src/workers/move.ts +61 -0
  80. package/tsconfig.json +25 -0
package/src/Color.ts ADDED
@@ -0,0 +1,29 @@
1
+ type RGB = [number, number, number];
2
+
3
+ function hexToRGB(hex: string): RGB {
4
+ let r = parseInt(hex.substring(0, 2), 16);
5
+ let g = parseInt(hex.substring(2, 4), 16);
6
+ let b = parseInt(hex.substring(4, 6), 16);
7
+ return [r, g, b];
8
+ }
9
+
10
+ function RGBToHex(rgb: RGB): string {
11
+ let r = rgb[0].toString(16).padStart(2, '0');
12
+ let g = rgb[1].toString(16).padStart(2, '0');
13
+ let b = rgb[2].toString(16).padStart(2, '0');
14
+ return r + g + b;
15
+ }
16
+
17
+ export function transitionColor(startColor: string, endColor: string, steps: number): string[] {
18
+ let startRGB = hexToRGB(startColor.replace('#', ''));
19
+ let endRGB = hexToRGB(endColor.replace('#', ''));
20
+ let deltaRGB = [(endRGB[0] - startRGB[0]) / steps, (endRGB[1] - startRGB[1]) / steps, (endRGB[2] - startRGB[2]) / steps];
21
+
22
+ let colors: string[] = [];
23
+ for (let i = 0; i < steps; i++) {
24
+ let color = [startRGB[0] + deltaRGB[0] * i, startRGB[1] + deltaRGB[1] * i, startRGB[2] + deltaRGB[2] * i];
25
+ colors.push(RGBToHex(color as any));
26
+ }
27
+ colors.push(endColor.replace('#', ''));
28
+ return colors;
29
+ }
@@ -0,0 +1,26 @@
1
+ import { Controls, Input, Control } from '@rpgjs/types'
2
+
3
+ export const DefaultInput: Controls = {
4
+ [Control.Up]: {
5
+ repeat: true,
6
+ bind: Input.Up
7
+ },
8
+ [Control.Down]: {
9
+ repeat: true,
10
+ bind: Input.Down
11
+ },
12
+ [Control.Right]: {
13
+ repeat: true,
14
+ bind: Input.Right
15
+ },
16
+ [Control.Left]: {
17
+ repeat: true,
18
+ bind: Input.Left
19
+ },
20
+ [Control.Action]: {
21
+ bind: [Input.Space, Input.Enter]
22
+ },
23
+ [Control.Back]: {
24
+ bind: Input.Escape
25
+ }
26
+ }
package/src/Event.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { RpgCommonPlayer } from './Player'
2
+
3
+ export class RpgCommonEvent extends RpgCommonPlayer {}
@@ -0,0 +1,52 @@
1
+ import { isPromise } from './Utils'
2
+
3
+ export class EventEmitter {
4
+ private listeners: {
5
+ [eventName: string]: Function[]
6
+ } = {}
7
+
8
+ private listenersOnce: {
9
+ [eventName: string]: Function
10
+ } = {}
11
+
12
+ once(name: string, cb: Function): EventEmitter {
13
+ this.listenersOnce[name] = cb
14
+ return this
15
+ }
16
+
17
+ on(name: string, cb: Function): EventEmitter {
18
+ if (!this.listeners[name]) this.listeners[name] = []
19
+ this.listeners[name].push(cb)
20
+ return this
21
+ }
22
+
23
+ emit(name: string, data?: any, rest: boolean = false): Promise<any[]> {
24
+ const ret: any = []
25
+ if (this.listeners[name]) {
26
+ for (let listener of this.listeners[name]) {
27
+ if (rest) ret.push(listener(...data))
28
+ else ret.push(listener(data))
29
+ }
30
+ }
31
+ else if (this.listenersOnce[name]) {
32
+ if (rest) ret.push(this.listenersOnce[name](...data))
33
+ else ret.push(this.listenersOnce[name](data))
34
+ }
35
+ return Promise.all(ret.map(val => {
36
+ if (!isPromise(val)) {
37
+ return Promise.resolve(val)
38
+ }
39
+ return val
40
+ }))
41
+ }
42
+
43
+ off(name: string) {
44
+ delete this.listeners[name]
45
+ delete this.listenersOnce[name]
46
+ }
47
+
48
+ clear() {
49
+ this.listeners = {}
50
+ this.listenersOnce = {}
51
+ }
52
+ }
package/src/Game.ts ADDED
@@ -0,0 +1,150 @@
1
+ import Utils, { generateUID, isClass } from './Utils'
2
+ import { EventEmitter } from './EventEmitter'
3
+ import { RpgCommonPlayer } from './Player'
4
+ import { constructor, Control, Controls, Direction } from '@rpgjs/types'
5
+ import { RpgPlugin } from './Plugin'
6
+ import { GameWorker } from './Worker'
7
+ import { HitObject } from './Hit'
8
+ import { RpgShape } from './Shape'
9
+ import { TiledObjectClass } from '@rpgjs/tiled'
10
+
11
+ export enum GameSide {
12
+ Server = 'server',
13
+ Client = 'client',
14
+ Worker = 'worker'
15
+ }
16
+
17
+ export class RpgCommonGame extends EventEmitter {
18
+ events: any
19
+ world: any
20
+
21
+ constructor(public side: GameSide) {
22
+ super()
23
+ this.events = {} // events for all player in map
24
+ }
25
+
26
+ get isWorker() {
27
+ return this.side == 'worker'
28
+ }
29
+
30
+ start(world) {
31
+ this.world = world
32
+ }
33
+
34
+ createWorkers(options: any) {
35
+ return new GameWorker(options)
36
+ }
37
+
38
+ addObject(_class, playerId?: string) {
39
+ let event
40
+ if (!playerId) playerId = generateUID()
41
+ if (isClass(_class)) {
42
+ event = new _class(this, playerId)
43
+ }
44
+ else {
45
+ event = _class
46
+ }
47
+ return event
48
+ }
49
+
50
+ addPlayer(playerClass, playerId?: string) {
51
+ const player = this.addObject(playerClass, playerId)
52
+ return player
53
+ }
54
+
55
+ addEvent<T>(eventClass: constructor<T>, eventId?: string): T {
56
+ const event = this.addObject(eventClass, eventId)
57
+ return event
58
+ }
59
+
60
+ addShape(obj: HitObject): RpgShape {
61
+ const id = obj.name = (obj.name || generateUID()) as string
62
+ const shape = new RpgShape(obj as TiledObjectClass)
63
+ shape.name = id
64
+ return shape
65
+ }
66
+
67
+ async processInput<RpgPlayer extends RpgCommonPlayer>(playerId: string, controls?: Controls): Promise<{
68
+ player: RpgPlayer,
69
+ inputs: string[]
70
+ }> {
71
+ const player: RpgPlayer = this.world.getObject(playerId)
72
+ const inputs: string[] = []
73
+
74
+ if (!player) return {
75
+ player,
76
+ inputs
77
+ }
78
+
79
+ const routesMove: any = []
80
+
81
+ while (player.pendingMove.length > 0) {
82
+ const inputData = player.pendingMove.shift()
83
+
84
+ let { input, deltaTimeInt } = inputData as any
85
+ let moving = false
86
+
87
+ if (controls && controls[input]) {
88
+ const control = controls[input]
89
+ const now = Date.now()
90
+ const inputTime = player.inputsTimestamp[input] || 0
91
+
92
+ if (inputTime >= now) {
93
+ continue
94
+ }
95
+
96
+ if (control.delay) {
97
+ let duration: number
98
+ let otherControls: (string | Control)[] = []
99
+
100
+ if (typeof control.delay == 'number') {
101
+ duration = control.delay
102
+ }
103
+ else {
104
+ duration = control.delay.duration
105
+ if (control.delay.otherControls) {
106
+ otherControls = control.delay.otherControls
107
+ }
108
+ }
109
+
110
+ player.inputsTimestamp[input] = now + duration
111
+
112
+ for (let control of otherControls) {
113
+ player.inputsTimestamp[control] = now + duration
114
+ }
115
+ }
116
+ }
117
+
118
+ if (input == Control.Action) {
119
+ await player.triggerCollisionWith(RpgCommonPlayer.ACTIONS.ACTION)
120
+ }
121
+ else if (
122
+ input == Direction.Left ||
123
+ input == Direction.Right ||
124
+ input == Direction.Up ||
125
+ input == Direction.Down
126
+ ) {
127
+ moving = true
128
+ const isMove = await player.moveByDirection(+input, deltaTimeInt || 1)
129
+ if (isMove) {
130
+ routesMove.push(inputData)
131
+ }
132
+ }
133
+ // TODO, is Worker
134
+ // verify if is server because, rpg mode causes a bug (see #184)
135
+ if (this.side == GameSide.Server) {
136
+ await RpgPlugin.emit('Server.onInput', [player, {
137
+ ...inputData,
138
+ moving
139
+ }], true)
140
+ }
141
+
142
+ inputs.push(input)
143
+ }
144
+
145
+ return {
146
+ player,
147
+ inputs
148
+ }
149
+ }
150
+ }
package/src/Hit.ts ADDED
@@ -0,0 +1,70 @@
1
+ import { HitEllipse, HitObject, HitType } from '@rpgjs/types'
2
+ import SAT from 'sat'
3
+ import { isInstanceOf } from './Utils'
4
+ export { HitType, HitObject } from '@rpgjs/types'
5
+
6
+ class HitClass {
7
+
8
+ createObjectHitbox(x: number, y: number, z: number, w: number, h: number): SAT.Box {
9
+ return new SAT.Box(new SAT.Vector(x, y - z), w, h)
10
+ }
11
+
12
+ getHitbox(obj: HitObject, offset?: { x: number, y: number }): {
13
+ hitbox: SAT,
14
+ type?: string,
15
+ name?: string
16
+ } {
17
+ let hitbox: SAT, type: string | undefined
18
+ if (!offset) offset = { x: 0, y: 0 }
19
+ const x = obj.x + offset.x
20
+ const y = obj.y + offset.y
21
+ if ('ellipse' in obj || obj.type == HitType.Circle) {
22
+ type = HitType.Circle
23
+ const radius = (<HitEllipse>obj).width / 2
24
+ hitbox = new SAT.Circle(new SAT.Vector(x + radius, y + radius), radius)
25
+ }
26
+ else if ('polygon' in obj) {
27
+ type = HitType.Polygon
28
+ hitbox = new SAT.Polygon(new SAT.Vector(x, y), obj.polygon.map(pos => new SAT.Vector(+pos.x, +pos.y)))
29
+ }
30
+ else if (!('polygon' in obj) && ('width' in obj) && ('height' in obj)) {
31
+ type = HitType.Box
32
+ hitbox = new SAT.Box(new SAT.Vector(x, y), obj.width, obj.height)
33
+ }
34
+ else {
35
+ hitbox = new SAT.Vector(x, y)
36
+ type = obj.type
37
+ }
38
+ return {
39
+ hitbox,
40
+ type,
41
+ name: obj.name
42
+ }
43
+ }
44
+
45
+ testPolyCollision(type: string, hit1: SAT, hit2: SAT): boolean {
46
+ let collided = false
47
+ if (type == HitType.Box) {
48
+ if (hit1.pos.x <= hit2.pos.x + hit2.w &&
49
+ hit1.pos.x + hit1.w >= hit2.pos.x &&
50
+ hit1.pos.y <= hit2.pos.y + hit2.h &&
51
+ hit1.h + hit1.pos.y >= hit2.pos.y) {
52
+ return true
53
+ }
54
+ return false
55
+ }
56
+ if (isInstanceOf(hit1, SAT.Box)) hit1 = hit1.toPolygon()
57
+ if (isInstanceOf(hit2, SAT.Box)) hit2 = hit2.toPolygon()
58
+ switch (type) {
59
+ case HitType.Circle:
60
+ collided = SAT.testPolygonCircle(hit1, hit2)
61
+ break
62
+ case HitType.Polygon:
63
+ collided = SAT.testPolygonPolygon(hit1, hit2)
64
+ break
65
+ }
66
+ return collided
67
+ }
68
+ }
69
+
70
+ export const Hit = new HitClass()
package/src/Logger.ts ADDED
@@ -0,0 +1,7 @@
1
+ export function log(message) {
2
+ return new Error(`[RPGJS] - ${message}`)
3
+ }
4
+
5
+ export function warning(...message) {
6
+ console.warn('[RPGJS Warning]', ...message)
7
+ }
package/src/Map.ts ADDED
@@ -0,0 +1,335 @@
1
+ import { HitObject } from './Hit'
2
+ import SAT from 'sat'
3
+ import Utils, { random, intersection, generateUID, isString } from './Utils'
4
+ import { RpgShape } from './Shape'
5
+ import { Hit } from './Hit'
6
+ import { VirtualGrid } from './VirtualGrid'
7
+ import { RpgCommonWorldMaps } from './WorldMaps'
8
+ import { TiledLayer, TiledLayerType, TiledMap, Layer, Tileset, Tile, TiledObject, TiledObjectClass, MapClass } from '@rpgjs/tiled'
9
+ import { Vector2d } from './Vector2d'
10
+ import { AbstractObject } from './AbstractObject'
11
+ import { RpgCommonGame } from './Game'
12
+ import { Observable, map, Subject, takeUntil, mergeMap, from, filter } from 'rxjs'
13
+ import { HitBox, MovingHitbox, Tick } from '@rpgjs/types'
14
+
15
+ const buffer = new Map()
16
+ const bufferClient = new Map()
17
+
18
+ export interface TileInfo {
19
+ tiles: Tile[]
20
+ hasCollision: boolean | undefined
21
+ isClimbable?: boolean | undefined
22
+ isOverlay: boolean | undefined
23
+ objectGroups: TiledObjectClass[],
24
+ tileIndex: number
25
+ }
26
+
27
+ export interface LayerInfo {
28
+ type: string,
29
+ name: string,
30
+ opacity: number,
31
+ visible: boolean,
32
+ properties: any,
33
+ objects: HitObject[]
34
+ tiles: Tile[]
35
+ }
36
+
37
+
38
+ export class RpgCommonMap extends MapClass {
39
+ /**
40
+ * @title map id
41
+ * @readonly
42
+ * @prop {string} [id]
43
+ * @memberof Map
44
+ * */
45
+ readonly id: string
46
+
47
+ grid: VirtualGrid
48
+ gridShapes: VirtualGrid
49
+ gridTiles: VirtualGrid
50
+
51
+ get tileWidth() {
52
+ return this.tilewidth
53
+ }
54
+
55
+ get tileHeight() {
56
+ return this.tileheight
57
+ }
58
+
59
+ /**
60
+ * @title Layers of map
61
+ * @prop {object[]} [layers]
62
+ * @readonly
63
+ * @memberof Map
64
+ * @memberof RpgSceneMap
65
+ * */
66
+
67
+ /** @internal */
68
+ shapes: {
69
+ [shapeName: string]: RpgShape
70
+ } = {}
71
+
72
+ private worldMapParent: RpgCommonWorldMaps | undefined
73
+
74
+ /**
75
+ * Retrieves the X position of the map in the world (0 if no world assigned)
76
+ *
77
+ * @title World X Position
78
+ * @prop {number} [worldX]
79
+ * @readonly
80
+ * @since 3.0.0-beta.8
81
+ * @memberof Map
82
+ * */
83
+ get worldX() {
84
+ return this.getInWorldMaps()?.getMapInfo(this.id)?.x || 0
85
+ }
86
+
87
+ /**
88
+ * Retrieves the Y position of the map in the world (0 if no world assigned)
89
+ *
90
+ * @title World Y Position
91
+ * @prop {number} [worldY]
92
+ * @readonly
93
+ * @since 3.0.0-beta.8
94
+ * @memberof Map
95
+ * */
96
+ get worldY() {
97
+ return this.getInWorldMaps()?.getMapInfo(this.id)?.y || 0
98
+ }
99
+
100
+ /**
101
+ * Memorize the maps so you don't have to make a new request or open a file each time you load a map
102
+ */
103
+ static get buffer() {
104
+ return buffer
105
+ }
106
+
107
+ /**
108
+ * In RPG mode, to avoid confusion with buffer, we have a new variable to memorize the maps
109
+ */
110
+ static get bufferClient() {
111
+ return bufferClient
112
+ }
113
+
114
+ load(data: TiledMap) {
115
+ super.load(data)
116
+ this.gridTiles = new VirtualGrid(this.width, this.tileWidth, this.tileHeight)
117
+ this.grid = new VirtualGrid(this.width, this.tileWidth, this.tileHeight).zoom(10)
118
+ this.gridShapes = new VirtualGrid(this.width, this.tileWidth, this.tileHeight).zoom(20)
119
+ }
120
+
121
+ /**
122
+ * Create a shape dynamically on the map
123
+ *
124
+ * Object:
125
+ * - (number) x: Position X
126
+ * - (number) y: Position Y
127
+ * - (number) width: Width
128
+ * - (number) height: Height
129
+ * - (object) properties (optionnal):
130
+ * - (number) z: Position Z
131
+ * - (hexadecimal) color: Color (shared with client)
132
+ * - (boolean) collision
133
+ * - You can your own properties
134
+ *
135
+ * @title Create Shape
136
+ * @since 3.0.0-beta.3
137
+ * @method map.createShape(obj)
138
+ * @param {object} obj
139
+ * @returns {RpgShape}
140
+ * @memberof Map
141
+ */
142
+ createShape(obj: HitObject): RpgShape {
143
+ const id = obj.name = (obj.name || generateUID()) as string
144
+ const shape = new RpgShape(obj as TiledObjectClass)
145
+ this.shapes[id] = shape
146
+ if (!shape.isShapePosition()) {
147
+ this.gridShapes.insertInCells(id, shape.getSizeBox(this.tileWidth))
148
+ }
149
+ // trick to sync with client
150
+ return this.shapes[id]
151
+ }
152
+
153
+ /**
154
+ * Delete a shape
155
+ *
156
+ * @title Delete Shape
157
+ * @method map.removeShape(name)
158
+ * @param {string} name Name of shape
159
+ * @returns {void}
160
+ * @memberof Map
161
+ */
162
+ removeShape(name: string) {
163
+ // TODO: out players after delete shape
164
+ //this.shapes = this.shapes.filter(shape => shape.name != name)
165
+ delete this.shapes[name]
166
+ }
167
+
168
+ clearShapes() {
169
+ this.shapes = {}
170
+ }
171
+
172
+ /**
173
+ * Return all shapes on the map
174
+ *
175
+ * @title Get Shapes
176
+ * @method map.getShapes()
177
+ * @returns {RpgShape[]}
178
+ * @memberof Map
179
+ * @memberof RpgSceneMap
180
+ */
181
+ getShapes(): RpgShape[] {
182
+ return Object.values(this.shapes)
183
+ }
184
+
185
+ /**
186
+ * Returns a shape by its name. Returns undefined is nothing is found
187
+ *
188
+ * @title Get Shape by name
189
+ * @method map.getShape(name)
190
+ * @param {string} name Name of shape
191
+ * @returns {RpgShape[] | undefined}
192
+ * @memberof Map
193
+ * @memberof RpgSceneMap
194
+ */
195
+ getShape(name: string): RpgShape | undefined {
196
+ return this.getShapes().find(shape => shape.name == name)
197
+ }
198
+
199
+ getPositionByShape(filter: (shape: RpgShape) => {}): { x: number, y: number, z: number } | null {
200
+ const startsFind = this.getShapes().filter(filter)
201
+ if (startsFind.length) {
202
+ const start = startsFind[random(0, startsFind.length-1)]
203
+ return { x: start.hitbox.x, y: start.hitbox.y, z: start.properties.z * this.zTileHeight || 0 }
204
+ }
205
+ return null
206
+ }
207
+
208
+ /**
209
+ * Get tile and verify collision with hitbox
210
+ * @param hitbox
211
+ * @param x
212
+ * @param y
213
+ * @param z
214
+ * @returns TileInfo
215
+ */
216
+ getTile(hitbox, x: number, y: number, z: [number, number] = [0, 0]): TileInfo {
217
+ const tile = {...this.getTileByPosition(x, y, z)}
218
+ const tilePos = this.getTileOriginPosition(x, y)
219
+ if (tile.objectGroups) {
220
+ for (let object of tile.objectGroups) {
221
+ const hit = Hit.getHitbox(object, {
222
+ x: tilePos.x,
223
+ y: tilePos.y
224
+ })
225
+ if (hit.type) {
226
+ const collided = Hit.testPolyCollision(hit.type, hit.hitbox, hitbox)
227
+ if (collided) {
228
+ tile.hasCollision = true
229
+ }
230
+ }
231
+ }
232
+ }
233
+ return tile
234
+ }
235
+
236
+ /**
237
+ * Assign the map to a world
238
+
239
+ * @title Assign the map to a world
240
+ * @method map.setInWorldMaps(name)
241
+ * @param {RpgWorldMaps} worldMap world maps
242
+ * @since 3.0.0-beta.8
243
+ * @memberof Map
244
+ */
245
+ setInWorldMaps(worldMap: RpgCommonWorldMaps) {
246
+ this.worldMapParent = worldMap
247
+ }
248
+
249
+ /**
250
+ * Remove this map from the world
251
+ * @title Remove this map from the world
252
+ * @method map.removeFromWorldMaps()
253
+ * @returns {boolean | undefined}
254
+ * @since 3.0.0-beta.8
255
+ * @memberof Map
256
+ */
257
+ removeFromWorldMaps(): boolean | undefined {
258
+ return this.worldMapParent?.removeMap(this.id)
259
+ }
260
+
261
+ /**
262
+ * Recover the world attached to this map (`undefined` if no world attached)
263
+
264
+ * @title Get attached World
265
+ * @method map.getInWorldMaps()
266
+ * @return {RpgCommonWorldMaps | undefined}
267
+ * @since 3.0.0-beta.8
268
+ * @memberof Map
269
+ */
270
+ getInWorldMaps(): RpgCommonWorldMaps | undefined {
271
+ return this.worldMapParent
272
+ }
273
+
274
+ boundingMap(nextPosition: Vector2d, hitbox: SAT): { bounding: boolean, nextPosition: Vector2d } | null {
275
+ let bounding = false
276
+ if (nextPosition.x < 0) {
277
+ nextPosition.x = 0
278
+ bounding = true
279
+ }
280
+ else if (nextPosition.y < 0) {
281
+ nextPosition.y = 0
282
+ bounding = true
283
+ }
284
+ else if (nextPosition.x > this.widthPx - hitbox.w) {
285
+ nextPosition.x = this.widthPx - hitbox.w
286
+ bounding = true
287
+ }
288
+ else if (nextPosition.y > this.heightPx - hitbox.h) {
289
+ nextPosition.y = this.heightPx - hitbox.h
290
+ bounding = true
291
+ }
292
+ return {
293
+ bounding,
294
+ nextPosition
295
+ }
296
+ }
297
+
298
+ _createMovingHitbox<T extends RpgCommonGame>(
299
+ gameEngine: T,
300
+ tick$: Observable<Tick>,
301
+ mapId: string,
302
+ hitboxes: Pick<HitBox, 'width' | 'height' | 'x' | 'y'>[],
303
+ options: MovingHitbox = {}
304
+ ): Observable<AbstractObject> {
305
+ const object = new AbstractObject(gameEngine, Utils.generateUID())
306
+ object.disableVirtualGrid = true
307
+ object.map = mapId
308
+ object.speed = options.speed ?? 1
309
+ let i = 0
310
+ let frame = 0
311
+ const destroyHitbox$ = new Subject<AbstractObject>()
312
+ return tick$.pipe(
313
+ takeUntil(destroyHitbox$),
314
+ filter(() => {
315
+ frame++
316
+ return frame % object.speed == 0
317
+ }),
318
+ map(() => {
319
+ const hitbox = hitboxes[i]
320
+ if (!hitbox) {
321
+ destroyHitbox$.next(object)
322
+ destroyHitbox$.complete()
323
+ return object
324
+ }
325
+ object.position.x = hitbox.x
326
+ object.position.y = hitbox.y
327
+ object.setHitbox(hitbox.width, hitbox.height)
328
+ i++
329
+ return object
330
+ }),
331
+ mergeMap((object) => from(object.isCollided(object.position, { allSearch: true }))),
332
+ map(() => object)
333
+ )
334
+ }
335
+ }