@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.
- package/LICENSE +19 -0
- package/lib/AbstractObject.d.ts +3 -2
- package/lib/AbstractObject.js +296 -323
- package/lib/AbstractObject.js.map +1 -1
- package/lib/Color.js +1 -5
- package/lib/Color.js.map +1 -1
- package/lib/Event.js +2 -6
- package/lib/Event.js.map +1 -1
- package/lib/EventEmitter.js +3 -7
- package/lib/EventEmitter.js.map +1 -1
- package/lib/Game.js +73 -85
- package/lib/Game.js.map +1 -1
- package/lib/Hit.js +21 -28
- package/lib/Hit.js.map +1 -1
- package/lib/Logger.js +2 -7
- package/lib/Logger.js.map +1 -1
- package/lib/Map.d.ts +1 -0
- package/lib/Map.js +29 -53
- package/lib/Map.js.map +1 -1
- package/lib/Module.d.ts +2 -2
- package/lib/Module.js +84 -97
- package/lib/Module.js.map +1 -1
- package/lib/Player.d.ts +1 -0
- package/lib/Player.js +4 -7
- package/lib/Player.js.map +1 -1
- package/lib/Plugin.d.ts +4 -3
- package/lib/Plugin.js +14 -14
- package/lib/Plugin.js.map +1 -1
- package/lib/Scheduler.js +14 -21
- package/lib/Scheduler.js.map +1 -1
- package/lib/Shape.d.ts +1 -1
- package/lib/Shape.js +39 -52
- package/lib/Shape.js.map +1 -1
- package/lib/Utils.d.ts +4 -1
- package/lib/Utils.js +38 -53
- package/lib/Utils.js.map +1 -1
- package/lib/Vector2d.js +3 -8
- package/lib/Vector2d.js.map +1 -1
- package/lib/VirtualGrid.d.ts +1 -1
- package/lib/VirtualGrid.js +5 -12
- package/lib/VirtualGrid.js.map +1 -1
- package/lib/Worker.js +2 -10
- package/lib/Worker.js.map +1 -1
- package/lib/WorldMaps.d.ts +3 -1
- package/lib/WorldMaps.js +29 -15
- package/lib/WorldMaps.js.map +1 -1
- package/lib/gui/PrebuiltGui.js +2 -5
- package/lib/gui/PrebuiltGui.js.map +1 -1
- package/lib/index.d.ts +3 -1
- package/lib/index.js +24 -69
- package/lib/index.js.map +1 -1
- package/lib/transports/io.js +5 -9
- package/lib/transports/io.js.map +1 -1
- package/lib/workers/move.js +26 -42
- package/lib/workers/move.js.map +1 -1
- package/package.json +9 -11
- package/src/AbstractObject.ts +915 -0
- package/src/Color.ts +29 -0
- package/src/DefaultInput.ts +26 -0
- package/src/Event.ts +3 -0
- package/src/EventEmitter.ts +52 -0
- package/src/Game.ts +150 -0
- package/src/Hit.ts +70 -0
- package/src/Logger.ts +7 -0
- package/src/Map.ts +335 -0
- package/src/Module.ts +108 -0
- package/src/Player.ts +30 -0
- package/src/Plugin.ts +91 -0
- package/src/Scheduler.ts +88 -0
- package/src/Shape.ts +300 -0
- package/src/Utils.ts +168 -0
- package/src/Vector2d.ts +70 -0
- package/src/VirtualGrid.ts +78 -0
- package/src/Worker.ts +17 -0
- package/src/WorldMaps.ts +204 -0
- package/src/gui/PrebuiltGui.ts +27 -0
- package/src/index.ts +24 -0
- package/src/transports/io.ts +91 -0
- package/src/workers/move.ts +61 -0
- 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,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
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
|
+
}
|