@rpgjs/server 4.0.0-beta.1 → 4.0.0-beta.3
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/package.json +8 -10
- package/src/Game/Map.ts +513 -0
- package/src/Game/WorldMaps.ts +45 -0
- package/src/Gui/DialogGui.ts +67 -0
- package/src/Gui/Gui.ts +45 -0
- package/src/Gui/MenuGui.ts +26 -0
- package/src/Gui/NotificationGui.ts +10 -0
- package/src/Gui/ShopGui.ts +43 -0
- package/src/Gui/index.ts +13 -0
- package/src/Interfaces/Gui.ts +4 -0
- package/src/Interfaces/StateStore.ts +5 -0
- package/src/MatchMaker.ts +63 -0
- package/src/Monitor/index.ts +78 -0
- package/src/Player/BattleManager.ts +123 -0
- package/src/Player/ClassManager.ts +72 -0
- package/src/Player/ComponentManager.ts +538 -0
- package/src/Player/EffectManager.ts +94 -0
- package/src/Player/ElementManager.ts +142 -0
- package/src/Player/GoldManager.ts +26 -0
- package/src/Player/GuiManager.ts +308 -0
- package/src/Player/ItemFixture.ts +24 -0
- package/src/Player/ItemManager.ts +474 -0
- package/src/Player/MoveManager.ts +635 -0
- package/src/Player/ParameterManager.ts +468 -0
- package/src/Player/Player.ts +931 -0
- package/src/Player/SkillManager.ts +229 -0
- package/src/Player/StateManager.ts +230 -0
- package/src/Player/VariableManager.ts +55 -0
- package/src/Query.ts +172 -0
- package/src/RpgServer.ts +429 -0
- package/src/Scenes/Map.ts +302 -0
- package/src/decorators/event.ts +57 -0
- package/src/decorators/map.ts +223 -0
- package/src/entry-point.ts +102 -0
- package/src/express/api.ts +118 -0
- package/src/express/errors/NotAuthorized.ts +6 -0
- package/src/express/errors/NotFound.ts +6 -0
- package/src/express/server.ts +93 -0
- package/src/index.ts +28 -0
- package/src/logs/index.ts +11 -0
- package/src/logs/item.ts +31 -0
- package/src/logs/log.ts +3 -0
- package/src/logs/skill.ts +16 -0
- package/src/logs/state.ts +13 -0
- package/src/models/Item.ts +11 -0
- package/src/presets/index.ts +71 -0
- package/src/server.ts +394 -0
- package/tsconfig.json +27 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rpgjs/server",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"types": "./lib/express/index.d.ts",
|
|
@@ -15,17 +15,14 @@
|
|
|
15
15
|
".": "./lib/index.js",
|
|
16
16
|
"./express": "./lib/express/server.js"
|
|
17
17
|
},
|
|
18
|
-
"files": [
|
|
19
|
-
"lib"
|
|
20
|
-
],
|
|
21
18
|
"keywords": [],
|
|
22
19
|
"author": "Samuel Ronce",
|
|
23
20
|
"license": "MIT",
|
|
24
21
|
"dependencies": {
|
|
25
|
-
"@rpgjs/common": "^4.0.0-beta.
|
|
26
|
-
"@rpgjs/database": "^4.0.0-beta.
|
|
27
|
-
"@rpgjs/tiled": "^4.0.0-beta.
|
|
28
|
-
"@rpgjs/types": "^4.0.0-beta.
|
|
22
|
+
"@rpgjs/common": "^4.0.0-beta.3",
|
|
23
|
+
"@rpgjs/database": "^4.0.0-beta.3",
|
|
24
|
+
"@rpgjs/tiled": "^4.0.0-beta.3",
|
|
25
|
+
"@rpgjs/types": "^4.0.0-beta.3",
|
|
29
26
|
"axios": "^1.3.6",
|
|
30
27
|
"express": "^4.18.2",
|
|
31
28
|
"lodash.merge": "^4.6.2",
|
|
@@ -36,11 +33,12 @@
|
|
|
36
33
|
"simple-room": "^2.0.2",
|
|
37
34
|
"socket.io": "^4.6.1"
|
|
38
35
|
},
|
|
39
|
-
"gitHead": "
|
|
36
|
+
"gitHead": "952bdc2cecd04ad8dd9341ca0d51bf64efcd2bab",
|
|
40
37
|
"devDependencies": {
|
|
41
38
|
"typescript": "^5.0.4"
|
|
42
39
|
},
|
|
43
40
|
"alias": {
|
|
44
41
|
"express": "./lib/express/server"
|
|
45
|
-
}
|
|
42
|
+
},
|
|
43
|
+
"type": "module"
|
|
46
44
|
}
|
package/src/Game/Map.ts
ADDED
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
import { RpgCommonMap, Utils, RpgShape, RpgCommonGame, AbstractObject } from '@rpgjs/common'
|
|
2
|
+
import { TiledParserFile, TiledParser, TiledTileset } from '@rpgjs/tiled'
|
|
3
|
+
import { EventOptions } from '../decorators/event'
|
|
4
|
+
import { RpgPlayer, EventMode, RpgEvent, RpgClassEvent } from '../Player/Player'
|
|
5
|
+
import { Move } from '../Player/MoveManager'
|
|
6
|
+
import { RpgServerEngine } from '../server'
|
|
7
|
+
import { Observable } from 'rxjs'
|
|
8
|
+
import path from 'path'
|
|
9
|
+
import { HitBox, MovingHitbox, Position } from '@rpgjs/types'
|
|
10
|
+
import { World } from 'simple-room'
|
|
11
|
+
|
|
12
|
+
export type EventPosOption = {
|
|
13
|
+
x: number,
|
|
14
|
+
y: number,
|
|
15
|
+
z?: number,
|
|
16
|
+
event: EventOptions
|
|
17
|
+
}
|
|
18
|
+
export type EventOption = EventPosOption | EventOptions
|
|
19
|
+
|
|
20
|
+
export type PlayersList = {
|
|
21
|
+
[eventId: string]: RpgEvent
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type EventsList = {
|
|
25
|
+
[playerId: string]: RpgPlayer
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
class AutoEvent extends RpgEvent {
|
|
29
|
+
static mode: EventMode
|
|
30
|
+
static hitbox: any = {}
|
|
31
|
+
|
|
32
|
+
onInit() {
|
|
33
|
+
const { graphic, direction, speed, frequency, move } = this.properties
|
|
34
|
+
if (graphic) {
|
|
35
|
+
this.setGraphic(graphic)
|
|
36
|
+
}
|
|
37
|
+
if (direction) {
|
|
38
|
+
this.changeDirection(direction)
|
|
39
|
+
}
|
|
40
|
+
if (speed) {
|
|
41
|
+
this.speed = speed
|
|
42
|
+
}
|
|
43
|
+
if (frequency) {
|
|
44
|
+
this.frequency = frequency
|
|
45
|
+
}
|
|
46
|
+
if (move == 'random') {
|
|
47
|
+
this.infiniteMoveRoute([Move.tileRandom()])
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async onAction(player: RpgPlayer) {
|
|
52
|
+
const { text } = this.properties
|
|
53
|
+
if (text) {
|
|
54
|
+
await player.showText(text, {
|
|
55
|
+
talkWith: this
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export class RpgMap extends RpgCommonMap {
|
|
62
|
+
|
|
63
|
+
public _events: EventOption[]
|
|
64
|
+
public file: any
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @title event list
|
|
68
|
+
* @prop { { [eventId: string]: RpgEvent } } [events]
|
|
69
|
+
* @memberof Map
|
|
70
|
+
* */
|
|
71
|
+
public events: EventsList = {}
|
|
72
|
+
|
|
73
|
+
constructor(private _server: RpgServerEngine) {
|
|
74
|
+
super()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// alias of users property in simple-room package
|
|
78
|
+
/**
|
|
79
|
+
* @title Players list
|
|
80
|
+
* @prop { { [playerId: string]: RpgPlayer } } [players]
|
|
81
|
+
* @readonly
|
|
82
|
+
* @memberof Map
|
|
83
|
+
*/
|
|
84
|
+
get players(): PlayersList {
|
|
85
|
+
return this['users']
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @title Number of players
|
|
90
|
+
* @prop {number} [nbPlayers]
|
|
91
|
+
* @readonly
|
|
92
|
+
* @memberof Map
|
|
93
|
+
*/
|
|
94
|
+
get nbPlayers(): number {
|
|
95
|
+
return Object.keys(this.players).length
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async load() {
|
|
99
|
+
if (RpgCommonMap.buffer.has(this.id)) {
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
const data = await this.parseTmx(this.file)
|
|
103
|
+
super.load(data)
|
|
104
|
+
this.getAllObjects().forEach(this.createShape.bind(this))
|
|
105
|
+
this.loadProperties((data as any).properties)
|
|
106
|
+
this._server.workers?.call('loadMap', {
|
|
107
|
+
id: this.id,
|
|
108
|
+
data
|
|
109
|
+
})
|
|
110
|
+
RpgCommonMap.buffer.set(this.id, this)
|
|
111
|
+
this.loadCommonEvents(this._server.inputOptions.events)
|
|
112
|
+
this.createDynamicEvent(this._events as EventPosOption[])
|
|
113
|
+
if (this.onLoad) this.onLoad()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Update the map with new data. Data can be a string (TMX content) or an object (parsed TMX content)
|
|
118
|
+
* New Map data will be sent to all players on the map
|
|
119
|
+
*
|
|
120
|
+
* @title Update map
|
|
121
|
+
* @method map.update(data)
|
|
122
|
+
* @since 4.0.0
|
|
123
|
+
* @returns {Promise<void>}
|
|
124
|
+
* @param {object | string} data
|
|
125
|
+
* @memberof Map
|
|
126
|
+
*/
|
|
127
|
+
async update(data: object | string): Promise<void> {
|
|
128
|
+
let objectData
|
|
129
|
+
// Data is XML (TMX content)
|
|
130
|
+
if (typeof data == 'string') {
|
|
131
|
+
objectData = await this.parseTmx(data, this.file)
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
objectData = data
|
|
135
|
+
}
|
|
136
|
+
super.load(objectData)
|
|
137
|
+
RpgCommonMap.buffer.set(this.id, this)
|
|
138
|
+
this.clearShapes()
|
|
139
|
+
this.getAllObjects().forEach(this.createShape.bind(this))
|
|
140
|
+
for (let playerId in this.players) {
|
|
141
|
+
const player = this.players[playerId]
|
|
142
|
+
player.emitSceneMap()
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Update tileset with new data. Data can be a string (TSX content) or an object (TiledTileset)
|
|
148
|
+
* Cache will be removed for this tileset
|
|
149
|
+
* New tileset data will be sent to all players on the map
|
|
150
|
+
* Warning: tileset is not updated for all maps, only for the current map
|
|
151
|
+
*
|
|
152
|
+
* @title Update tileset
|
|
153
|
+
* @method map.updateTileset(data)
|
|
154
|
+
* @since 4.0.0
|
|
155
|
+
* @returns {<void>}
|
|
156
|
+
* @param {TiledTileset | string} data
|
|
157
|
+
* @memberof Map
|
|
158
|
+
*/
|
|
159
|
+
updateTileset(data: TiledTileset | string) {
|
|
160
|
+
let objectData: TiledTileset
|
|
161
|
+
// Data is XML (TMX content)
|
|
162
|
+
if (typeof data == 'string') {
|
|
163
|
+
const parser = new TiledParser(data, this.file)
|
|
164
|
+
objectData = parser.parseTileset()
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
objectData = data
|
|
168
|
+
}
|
|
169
|
+
this.removeCacheTileset(objectData.name)
|
|
170
|
+
this.update({
|
|
171
|
+
...this.data,
|
|
172
|
+
tilesets: this.data.tilesets.map((tileset: any) => {
|
|
173
|
+
if (tileset.name == objectData.name) {
|
|
174
|
+
objectData.firstgid = tileset.firstgid
|
|
175
|
+
return objectData
|
|
176
|
+
}
|
|
177
|
+
return tileset
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Remove the map from the server. If there are still players on the map, an error will be thrown
|
|
184
|
+
* Not delete the map file, only in memory
|
|
185
|
+
*
|
|
186
|
+
* @title Remove map
|
|
187
|
+
* @method map.remove()
|
|
188
|
+
* @since 4.0.0
|
|
189
|
+
* @returns {void}
|
|
190
|
+
* @throws {Error} If there are still players on the map
|
|
191
|
+
* @memberof Map
|
|
192
|
+
* */
|
|
193
|
+
remove(ignorePlayers = false): never | void {
|
|
194
|
+
const players = Object.values(this.players)
|
|
195
|
+
if (players.length > 0 && !ignorePlayers) {
|
|
196
|
+
throw new Error(`Cannot remove map ${this.id} because there are still players on it`)
|
|
197
|
+
}
|
|
198
|
+
for (let eventId in this.events) {
|
|
199
|
+
this.removeEvent(eventId)
|
|
200
|
+
}
|
|
201
|
+
RpgCommonMap.buffer.delete(this.id)
|
|
202
|
+
World.removeRoom(this.id)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private async parseTmx(file: string, relativePath: string = '') {
|
|
206
|
+
// @ts-ignore
|
|
207
|
+
const hasAssetsPath = !!import.meta.env.VITE_BUILT
|
|
208
|
+
const parser = new TiledParserFile(
|
|
209
|
+
file,
|
|
210
|
+
{
|
|
211
|
+
basePath: process.env.NODE_ENV == 'test' ? '.' : '',
|
|
212
|
+
staticDir: hasAssetsPath ? path.join(this._server.inputOptions.basePath, this._server.assetsPath) : '',
|
|
213
|
+
relativePath
|
|
214
|
+
}
|
|
215
|
+
)
|
|
216
|
+
const data = await parser.parseFilePromise({
|
|
217
|
+
getOnlyBasename: hasAssetsPath
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
return data
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private loadProperties(properties: {
|
|
224
|
+
[key: string]: any
|
|
225
|
+
}) {
|
|
226
|
+
for (let key in properties) {
|
|
227
|
+
this[key] = properties[key]
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
get game(): RpgCommonGame {
|
|
232
|
+
return this._server.gameEngine
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Hook: called by simple-room package
|
|
236
|
+
onLeave(player: RpgPlayer) {
|
|
237
|
+
this.removeObject(player)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private loadCommonEvents(commonEvents: RpgClassEvent<RpgEvent>[]) {
|
|
241
|
+
let events: EventPosOption[] = []
|
|
242
|
+
this.getShapes().forEach(shape => {
|
|
243
|
+
const findEvent = commonEvents.find(event => event._name == shape.name)
|
|
244
|
+
if (!findEvent) return
|
|
245
|
+
const { x, y, } = shape.hitbox
|
|
246
|
+
events.push({
|
|
247
|
+
x,
|
|
248
|
+
y,
|
|
249
|
+
event: findEvent
|
|
250
|
+
})
|
|
251
|
+
})
|
|
252
|
+
this.createDynamicEvent(events)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// TODO
|
|
256
|
+
autoLoadEvent() {
|
|
257
|
+
this.getShapes().forEach(shape => {
|
|
258
|
+
const { properties } = shape
|
|
259
|
+
const { x, y, pos, w, h } = shape.hitbox
|
|
260
|
+
if (shape.isEvent() && !this.events[shape.name]) {
|
|
261
|
+
const mode = properties.mode || EventMode.Shared
|
|
262
|
+
AutoEvent.prototype['_name'] = shape.name
|
|
263
|
+
AutoEvent.mode = mode
|
|
264
|
+
AutoEvent.hitbox = {
|
|
265
|
+
width: 32,
|
|
266
|
+
height: 16
|
|
267
|
+
}
|
|
268
|
+
const event = this.createEvent({
|
|
269
|
+
x,
|
|
270
|
+
y,
|
|
271
|
+
event: AutoEvent
|
|
272
|
+
}, mode, shape)
|
|
273
|
+
if (event) this.events[shape.name] = event
|
|
274
|
+
}
|
|
275
|
+
})
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Edit a tile on the map. All players on the map will see the modified tile
|
|
280
|
+
*
|
|
281
|
+
*
|
|
282
|
+
* @title Change Tile in map
|
|
283
|
+
* @since 3.0.0-beta.4
|
|
284
|
+
* @method map.setTile(x,y,layer,tileInfo)
|
|
285
|
+
* @param {number} x Position X
|
|
286
|
+
* @param {number} y Position Y
|
|
287
|
+
* @param {string | ((layer: any) => boolean)} layer Name of the layer where you want to put a tile. OYou can also put a function that will act as a filter. The first parameter is the layer and you return a boolean to indicate if you modify the tile of this layer or not
|
|
288
|
+
* @param {object} tileInfo Object with the following properties:
|
|
289
|
+
* - {number} gid: The tile number in tileset (from 1)
|
|
290
|
+
* - {object} properties Property of the tile. You own object. To set a collision, set the `collision:true` property
|
|
291
|
+
* @example
|
|
292
|
+
* ```ts
|
|
293
|
+
* map.setTile(15, 18, 'mylayer', { gid: 2 })
|
|
294
|
+
* ```
|
|
295
|
+
* @returns {void}
|
|
296
|
+
* @memberof Map
|
|
297
|
+
*/
|
|
298
|
+
setTile(x: number, y: number, layerFilter: string | ((layer: any) => boolean), tileInfo: {
|
|
299
|
+
gid: number,
|
|
300
|
+
properties?: object
|
|
301
|
+
}): any {
|
|
302
|
+
const tiles = super.setTile(x, y, layerFilter, tileInfo)
|
|
303
|
+
const players: RpgPlayer[] = Object.values(this['users'])
|
|
304
|
+
for (let player of players) {
|
|
305
|
+
player.emit('changeTile', tiles)
|
|
306
|
+
}
|
|
307
|
+
return tiles
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
getEventShape(eventName: string): RpgShape | undefined {
|
|
311
|
+
return this.getShapes().find(shape => shape.name == eventName)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Dynamically create an event in Shared mode
|
|
316
|
+
*
|
|
317
|
+
* ```ts
|
|
318
|
+
* @EventData({
|
|
319
|
+
* name: 'EV-1'
|
|
320
|
+
* })
|
|
321
|
+
* class MyEvent extends RpgEvent {
|
|
322
|
+
* onAction() {
|
|
323
|
+
* console.log('ok')
|
|
324
|
+
* }
|
|
325
|
+
* }
|
|
326
|
+
*
|
|
327
|
+
* map.createDynamicEvent({
|
|
328
|
+
* x: 100,
|
|
329
|
+
* y: 100,
|
|
330
|
+
* event: MyEvent
|
|
331
|
+
* })
|
|
332
|
+
* ```
|
|
333
|
+
*
|
|
334
|
+
* You can also put an array of objects to create several events at once
|
|
335
|
+
*
|
|
336
|
+
* @title Create Dynamic Event
|
|
337
|
+
* @since 3.0.0-beta.4
|
|
338
|
+
* @method map.createDynamicEvent(eventObj|eventObj[])
|
|
339
|
+
* @param { { x: number, y: number, z?: number, event: eventClass } } eventsList
|
|
340
|
+
* @returns { { [eventId: string]: RpgEvent } }
|
|
341
|
+
* @memberof Map
|
|
342
|
+
*/
|
|
343
|
+
createDynamicEvent(eventsList: EventPosOption | EventPosOption[]): {
|
|
344
|
+
[eventId: string]: RpgEvent
|
|
345
|
+
} {
|
|
346
|
+
if (!eventsList) return {}
|
|
347
|
+
if (!Utils.isArray(eventsList)) {
|
|
348
|
+
eventsList = [eventsList as EventPosOption]
|
|
349
|
+
}
|
|
350
|
+
const events = this.createEvents(eventsList as EventPosOption[], EventMode.Shared)
|
|
351
|
+
let ret = {}
|
|
352
|
+
for (let key in events) {
|
|
353
|
+
this.events[key] = events[key]
|
|
354
|
+
this.events[key].updateInVirtualGrid()
|
|
355
|
+
this.events[key].execMethod('onInit')
|
|
356
|
+
// force to get Proxy object to sync with client
|
|
357
|
+
ret = { ...ret, [key]: this.events[key] }
|
|
358
|
+
}
|
|
359
|
+
return ret
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Get Event in current map
|
|
364
|
+
* @title Get Event
|
|
365
|
+
* @since 3.0.0-beta.7
|
|
366
|
+
* @method map.getEvent(eventId)
|
|
367
|
+
* @param {string} eventId Event Id
|
|
368
|
+
* @returns {RpgEvent | undefined}
|
|
369
|
+
* @memberof Map
|
|
370
|
+
*/
|
|
371
|
+
getEvent<T extends RpgEvent>(eventId: string): T | undefined {
|
|
372
|
+
return this.events[eventId] as T
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Removes an event from the map. Returns false if the event is not found
|
|
377
|
+
* @title Remove Event
|
|
378
|
+
* @since 3.0.0-beta.4
|
|
379
|
+
* @method map.removeEvent(eventId)
|
|
380
|
+
* @param {string} eventId Event Name
|
|
381
|
+
* @returns {boolean}
|
|
382
|
+
* @memberof Map
|
|
383
|
+
*/
|
|
384
|
+
removeEvent(eventId: string): boolean {
|
|
385
|
+
if (!this.events[eventId]) return false
|
|
386
|
+
this.removeObject(this.events[eventId])
|
|
387
|
+
delete this.events[eventId]
|
|
388
|
+
return true
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
createEvent(obj: EventPosOption, mode: EventMode, shape?: RpgShape): RpgEvent | null {
|
|
392
|
+
let event: any, position: Position | undefined
|
|
393
|
+
|
|
394
|
+
// We retrieve the information of the event ([Event] or [{event: Event, x: number, y: number}])
|
|
395
|
+
if (obj.x === undefined) {
|
|
396
|
+
event = obj
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
event = obj.event
|
|
400
|
+
position = { x: obj.x, y: obj.y, z: 0 }
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// The event is ignored if the mode is different.
|
|
404
|
+
if (event.mode != mode) {
|
|
405
|
+
return null
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Create an instance of RpgEvent and assign its options
|
|
409
|
+
const ev = this.game.addEvent<RpgEvent>(event)
|
|
410
|
+
const _shape = shape || this.getEventShape(ev.name)
|
|
411
|
+
ev.map = this.id
|
|
412
|
+
ev.server = this._server
|
|
413
|
+
ev.width = event.width || this.tileWidth
|
|
414
|
+
ev.height = event.height || this.tileHeight
|
|
415
|
+
if (_shape && _shape.properties) ev.properties = _shape.properties
|
|
416
|
+
if (event.hitbox) ev.setHitbox(event.hitbox.width, event.hitbox.height)
|
|
417
|
+
ev.teleport(position || ev.name)
|
|
418
|
+
return ev
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
createEvents(eventsList: EventOption[], mode: EventMode): EventsList {
|
|
422
|
+
const events = {}
|
|
423
|
+
|
|
424
|
+
if (!eventsList) return events
|
|
425
|
+
|
|
426
|
+
for (let obj of eventsList) {
|
|
427
|
+
const ev = this.createEvent(obj as EventPosOption, mode)
|
|
428
|
+
if (ev) {
|
|
429
|
+
events[ev.id] = ev
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return events
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
private removeObject(object: RpgPlayer | RpgEvent) {
|
|
437
|
+
this.getShapes().forEach(shape => shape.out(object))
|
|
438
|
+
const events: RpgPlayer[] = Object.values(this.game.world.getObjectsOfGroup(this.id, object))
|
|
439
|
+
for (let event of events) {
|
|
440
|
+
object.getShapes().forEach(shape => shape.out(event))
|
|
441
|
+
event.getShapes().forEach(shape => shape.out(object))
|
|
442
|
+
}
|
|
443
|
+
object._destroy$.next()
|
|
444
|
+
object._destroy$.complete()
|
|
445
|
+
this.grid.clearObjectInCells(object.id)
|
|
446
|
+
for (let playerId in this.players) {
|
|
447
|
+
if (object.id == playerId) continue
|
|
448
|
+
const otherPlayer = this.players[playerId]
|
|
449
|
+
if (otherPlayer.following?.id == object.id) {
|
|
450
|
+
otherPlayer.cameraFollow(otherPlayer)
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
// last player before removed of this map
|
|
454
|
+
if (this.nbPlayers === 1 && object.type === 'player') {
|
|
455
|
+
// clear cache for this map
|
|
456
|
+
this.remove(true)
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Allows to create a temporary hitbox on the map that can have a movement
|
|
462
|
+
For example, you can use it to explode a bomb and find all the affected players, or during a sword strike, you can create a moving hitbox and find the affected players again
|
|
463
|
+
* @title Create a temporary and moving hitbox
|
|
464
|
+
* @since 3.2.0
|
|
465
|
+
* @method map.createMovingHitbox(hitboxes,options)
|
|
466
|
+
* @param {Array<{ width: number, height: number, x: number, y: number }>} hitboxes Create several hitboxes that will give an effect of movement
|
|
467
|
+
* @param {object} [options]
|
|
468
|
+
* @param {speed} [options.speed=1] speed of movement (in frames)
|
|
469
|
+
* @returns {Observable<AbstractObject>} You find the methods of position and movement of an event
|
|
470
|
+
* @memberof Map
|
|
471
|
+
* @example
|
|
472
|
+
*
|
|
473
|
+
* ```ts
|
|
474
|
+
* // Two hitboxes that will be done very quickly
|
|
475
|
+
* map.createMovingHitbox(
|
|
476
|
+
* [
|
|
477
|
+
* { x: 0, y: 0, width: 100, height: 100 },
|
|
478
|
+
* { x: 20, y: 0, width: 100, height: 100 }
|
|
479
|
+
* ]
|
|
480
|
+
* ).subscribe({
|
|
481
|
+
* next(hitbox) {
|
|
482
|
+
* console.log(hitbox.otherPlayersCollision)
|
|
483
|
+
* },
|
|
484
|
+
* complete() {
|
|
485
|
+
* console.log('finish')
|
|
486
|
+
* }
|
|
487
|
+
* })
|
|
488
|
+
* ```
|
|
489
|
+
*/
|
|
490
|
+
createMovingHitbox(
|
|
491
|
+
hitboxes: Pick<HitBox, 'width' | 'height' | 'x' | 'y'>[],
|
|
492
|
+
options: MovingHitbox = {}): Observable<AbstractObject> {
|
|
493
|
+
return this._createMovingHitbox<RpgCommonGame>(
|
|
494
|
+
this.game,
|
|
495
|
+
this._server.tick as any,
|
|
496
|
+
this.id,
|
|
497
|
+
hitboxes,
|
|
498
|
+
options) as any
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
setSync(schema: any) {
|
|
502
|
+
return this.$setSchema(schema)
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export interface RpgMap {
|
|
507
|
+
sounds: string[]
|
|
508
|
+
$schema: any
|
|
509
|
+
$setSchema: (schema: any) => void
|
|
510
|
+
$patchSchema: (schema: any) => void
|
|
511
|
+
$snapshotUser: (userId: string) => any
|
|
512
|
+
onLoad()
|
|
513
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { RpgCommonWorldMaps, Utils } from '@rpgjs/common'
|
|
2
|
+
import { TiledWorld, TiledMap, TiledWorldMap } from '@rpgjs/tiled'
|
|
3
|
+
import { RpgClassMap, SceneMap } from '../Scenes/Map'
|
|
4
|
+
import { RpgMap } from './Map'
|
|
5
|
+
|
|
6
|
+
export type RpgTiledWorldMap = {
|
|
7
|
+
id?: string
|
|
8
|
+
fileName: string | TiledMap
|
|
9
|
+
} & TiledWorldMap
|
|
10
|
+
|
|
11
|
+
export type RpgTiledWorld = {
|
|
12
|
+
maps: RpgTiledWorldMap[]
|
|
13
|
+
} & TiledWorld
|
|
14
|
+
|
|
15
|
+
export interface WorldMap extends RpgTiledWorld {
|
|
16
|
+
id?: string,
|
|
17
|
+
basePath?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class RpgWorldMaps extends RpgCommonWorldMaps {
|
|
21
|
+
load(world: WorldMap, sceneMap: SceneMap) {
|
|
22
|
+
for (let worldMap of world.maps) {
|
|
23
|
+
const { fileName } = worldMap
|
|
24
|
+
let id, map: RpgClassMap<RpgMap>
|
|
25
|
+
if (worldMap.id) {
|
|
26
|
+
id = worldMap.id
|
|
27
|
+
}
|
|
28
|
+
else if (Utils.isString(fileName)) {
|
|
29
|
+
id = Utils.extractId(fileName)
|
|
30
|
+
}
|
|
31
|
+
const create = () => sceneMap.createDynamicMap({
|
|
32
|
+
id,
|
|
33
|
+
file: world.basePath ? `${world.basePath}/${fileName}` : fileName
|
|
34
|
+
})
|
|
35
|
+
if (!id) {
|
|
36
|
+
map = create()
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
map = sceneMap.getMapBydId(id) ?? create()
|
|
40
|
+
}
|
|
41
|
+
this.addMap(worldMap, map)
|
|
42
|
+
}
|
|
43
|
+
return this
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { PrebuiltGui } from '@rpgjs/common'
|
|
2
|
+
import { Gui } from './Gui'
|
|
3
|
+
import { RpgPlayer } from '../Player/Player'
|
|
4
|
+
import { IGui } from '../Interfaces/Gui'
|
|
5
|
+
import { Move } from '../Player/MoveManager'
|
|
6
|
+
|
|
7
|
+
export enum DialogPosition {
|
|
8
|
+
Top = 'top',
|
|
9
|
+
Bottom = 'bottom',
|
|
10
|
+
Middle = 'middle'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type Choice = { text: string, value: any }
|
|
14
|
+
|
|
15
|
+
export interface DialogOptions {
|
|
16
|
+
choices?: Choice[],
|
|
17
|
+
position?: DialogPosition,
|
|
18
|
+
fullWidth?: boolean,
|
|
19
|
+
autoClose?: boolean,
|
|
20
|
+
tranparent?: boolean,
|
|
21
|
+
typewriterEffect?: boolean,
|
|
22
|
+
talkWith?: RpgPlayer
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class DialogGui extends Gui implements IGui {
|
|
26
|
+
constructor(player: RpgPlayer) {
|
|
27
|
+
super(PrebuiltGui.Dialog, player)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
openDialog(message: string, options: DialogOptions): Promise<any> {
|
|
31
|
+
if (!options.choices) options.choices = []
|
|
32
|
+
if (options.autoClose == undefined) options.autoClose = false
|
|
33
|
+
if (!options.position) options.position = DialogPosition.Bottom
|
|
34
|
+
if (options.fullWidth == undefined) options.fullWidth = true
|
|
35
|
+
if (options.typewriterEffect == undefined) options.typewriterEffect = true
|
|
36
|
+
const event = options.talkWith
|
|
37
|
+
let memoryDir
|
|
38
|
+
if (event) {
|
|
39
|
+
memoryDir = event.direction
|
|
40
|
+
event.breakRoutes(true)
|
|
41
|
+
event.moveRoutes([ Move.turnTowardPlayer(this.player) ])
|
|
42
|
+
}
|
|
43
|
+
const data = {
|
|
44
|
+
autoClose: options.autoClose,
|
|
45
|
+
position: options.position,
|
|
46
|
+
fullWidth: options.fullWidth,
|
|
47
|
+
typewriterEffect: options.typewriterEffect,
|
|
48
|
+
// remove value property. It is not useful to know this on the client side.
|
|
49
|
+
choices: options.choices.map(choice => ({
|
|
50
|
+
text: choice.text
|
|
51
|
+
}))
|
|
52
|
+
}
|
|
53
|
+
return super.open({
|
|
54
|
+
message,
|
|
55
|
+
...data
|
|
56
|
+
}, {
|
|
57
|
+
waitingAction: true,
|
|
58
|
+
blockPlayerInput: true
|
|
59
|
+
}).then((val: any) => {
|
|
60
|
+
if (event) {
|
|
61
|
+
event.replayRoutes()
|
|
62
|
+
event.direction = memoryDir
|
|
63
|
+
}
|
|
64
|
+
return val
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
}
|