@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.
Files changed (48) hide show
  1. package/package.json +8 -10
  2. package/src/Game/Map.ts +513 -0
  3. package/src/Game/WorldMaps.ts +45 -0
  4. package/src/Gui/DialogGui.ts +67 -0
  5. package/src/Gui/Gui.ts +45 -0
  6. package/src/Gui/MenuGui.ts +26 -0
  7. package/src/Gui/NotificationGui.ts +10 -0
  8. package/src/Gui/ShopGui.ts +43 -0
  9. package/src/Gui/index.ts +13 -0
  10. package/src/Interfaces/Gui.ts +4 -0
  11. package/src/Interfaces/StateStore.ts +5 -0
  12. package/src/MatchMaker.ts +63 -0
  13. package/src/Monitor/index.ts +78 -0
  14. package/src/Player/BattleManager.ts +123 -0
  15. package/src/Player/ClassManager.ts +72 -0
  16. package/src/Player/ComponentManager.ts +538 -0
  17. package/src/Player/EffectManager.ts +94 -0
  18. package/src/Player/ElementManager.ts +142 -0
  19. package/src/Player/GoldManager.ts +26 -0
  20. package/src/Player/GuiManager.ts +308 -0
  21. package/src/Player/ItemFixture.ts +24 -0
  22. package/src/Player/ItemManager.ts +474 -0
  23. package/src/Player/MoveManager.ts +635 -0
  24. package/src/Player/ParameterManager.ts +468 -0
  25. package/src/Player/Player.ts +931 -0
  26. package/src/Player/SkillManager.ts +229 -0
  27. package/src/Player/StateManager.ts +230 -0
  28. package/src/Player/VariableManager.ts +55 -0
  29. package/src/Query.ts +172 -0
  30. package/src/RpgServer.ts +429 -0
  31. package/src/Scenes/Map.ts +302 -0
  32. package/src/decorators/event.ts +57 -0
  33. package/src/decorators/map.ts +223 -0
  34. package/src/entry-point.ts +102 -0
  35. package/src/express/api.ts +118 -0
  36. package/src/express/errors/NotAuthorized.ts +6 -0
  37. package/src/express/errors/NotFound.ts +6 -0
  38. package/src/express/server.ts +93 -0
  39. package/src/index.ts +28 -0
  40. package/src/logs/index.ts +11 -0
  41. package/src/logs/item.ts +31 -0
  42. package/src/logs/log.ts +3 -0
  43. package/src/logs/skill.ts +16 -0
  44. package/src/logs/state.ts +13 -0
  45. package/src/models/Item.ts +11 -0
  46. package/src/presets/index.ts +71 -0
  47. package/src/server.ts +394 -0
  48. 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.1",
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.1",
26
- "@rpgjs/database": "^4.0.0-beta.1",
27
- "@rpgjs/tiled": "^4.0.0-beta.1",
28
- "@rpgjs/types": "^4.0.0-beta.1",
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": "b12826ef87716da2691da3bae02d7d7c66772789",
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
  }
@@ -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
+ }