@rpgjs/server 3.3.2 → 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 (152) hide show
  1. package/LICENSE +19 -0
  2. package/lib/Game/Map.d.ts +58 -5
  3. package/lib/Game/Map.js +185 -80
  4. package/lib/Game/Map.js.map +1 -1
  5. package/lib/Game/WorldMaps.d.ts +3 -2
  6. package/lib/Game/WorldMaps.js +6 -11
  7. package/lib/Game/WorldMaps.js.map +1 -1
  8. package/lib/Gui/DialogGui.d.ts +1 -1
  9. package/lib/Gui/DialogGui.js +12 -13
  10. package/lib/Gui/DialogGui.js.map +1 -1
  11. package/lib/Gui/Gui.js +2 -6
  12. package/lib/Gui/Gui.js.map +1 -1
  13. package/lib/Gui/MenuGui.js +4 -8
  14. package/lib/Gui/MenuGui.js.map +1 -1
  15. package/lib/Gui/NotificationGui.js +4 -8
  16. package/lib/Gui/NotificationGui.js.map +1 -1
  17. package/lib/Gui/ShopGui.js +4 -8
  18. package/lib/Gui/ShopGui.js.map +1 -1
  19. package/lib/Gui/index.js +6 -13
  20. package/lib/Gui/index.js.map +1 -1
  21. package/lib/Interfaces/Gui.js +1 -2
  22. package/lib/Interfaces/StateStore.js +1 -2
  23. package/lib/MatchMaker.js +28 -46
  24. package/lib/MatchMaker.js.map +1 -1
  25. package/lib/Monitor/index.js +3 -5
  26. package/lib/Monitor/index.js.map +1 -1
  27. package/lib/Player/BattleManager.js +17 -16
  28. package/lib/Player/BattleManager.js.map +1 -1
  29. package/lib/Player/ClassManager.js +6 -10
  30. package/lib/Player/ClassManager.js.map +1 -1
  31. package/lib/Player/ComponentManager.d.ts +4 -4
  32. package/lib/Player/ComponentManager.js +37 -24
  33. package/lib/Player/ComponentManager.js.map +1 -1
  34. package/lib/Player/EffectManager.js +8 -12
  35. package/lib/Player/EffectManager.js.map +1 -1
  36. package/lib/Player/ElementManager.js +5 -9
  37. package/lib/Player/ElementManager.js.map +1 -1
  38. package/lib/Player/GoldManager.js +1 -5
  39. package/lib/Player/GoldManager.js.map +1 -1
  40. package/lib/Player/GuiManager.js +17 -15
  41. package/lib/Player/GuiManager.js.map +1 -1
  42. package/lib/Player/ItemFixture.js +1 -5
  43. package/lib/Player/ItemFixture.js.map +1 -1
  44. package/lib/Player/ItemManager.d.ts +3 -3
  45. package/lib/Player/ItemManager.js +29 -31
  46. package/lib/Player/ItemManager.js.map +1 -1
  47. package/lib/Player/MoveManager.d.ts +7 -6
  48. package/lib/Player/MoveManager.js +67 -74
  49. package/lib/Player/MoveManager.js.map +1 -1
  50. package/lib/Player/ParameterManager.js +10 -14
  51. package/lib/Player/ParameterManager.js.map +1 -1
  52. package/lib/Player/Player.d.ts +7 -1
  53. package/lib/Player/Player.js +193 -191
  54. package/lib/Player/Player.js.map +1 -1
  55. package/lib/Player/SkillManager.js +18 -22
  56. package/lib/Player/SkillManager.js.map +1 -1
  57. package/lib/Player/StateManager.js +9 -13
  58. package/lib/Player/StateManager.js.map +1 -1
  59. package/lib/Player/VariableManager.js +1 -5
  60. package/lib/Player/VariableManager.js.map +1 -1
  61. package/lib/Query.d.ts +2 -1
  62. package/lib/Query.js +19 -15
  63. package/lib/Query.js.map +1 -1
  64. package/lib/RpgServer.d.ts +11 -3
  65. package/lib/RpgServer.js +1 -2
  66. package/lib/Scenes/Map.d.ts +27 -4
  67. package/lib/Scenes/Map.js +117 -122
  68. package/lib/Scenes/Map.js.map +1 -1
  69. package/lib/decorators/event.js +4 -7
  70. package/lib/decorators/event.js.map +1 -1
  71. package/lib/decorators/map.d.ts +1 -1
  72. package/lib/decorators/map.js +5 -9
  73. package/lib/decorators/map.js.map +1 -1
  74. package/lib/entry-point.js +59 -65
  75. package/lib/entry-point.js.map +1 -1
  76. package/lib/express/api.d.ts +3 -0
  77. package/lib/express/api.js +105 -0
  78. package/lib/express/api.js.map +1 -0
  79. package/lib/express/errors/NotAuthorized.d.ts +4 -0
  80. package/lib/express/errors/NotAuthorized.js +7 -0
  81. package/lib/express/errors/NotAuthorized.js.map +1 -0
  82. package/lib/express/errors/NotFound.d.ts +4 -0
  83. package/lib/express/errors/NotFound.js +7 -0
  84. package/lib/express/errors/NotFound.js.map +1 -0
  85. package/lib/express/server.js +20 -5
  86. package/lib/express/server.js.map +1 -1
  87. package/lib/index.js +15 -68
  88. package/lib/index.js.map +1 -1
  89. package/lib/logs/index.js +5 -11
  90. package/lib/logs/index.js.map +1 -1
  91. package/lib/logs/item.js +11 -15
  92. package/lib/logs/item.js.map +1 -1
  93. package/lib/logs/log.js +1 -5
  94. package/lib/logs/log.js.map +1 -1
  95. package/lib/logs/skill.js +6 -10
  96. package/lib/logs/skill.js.map +1 -1
  97. package/lib/logs/state.js +5 -9
  98. package/lib/logs/state.js.map +1 -1
  99. package/lib/models/Item.js +1 -2
  100. package/lib/presets/index.js +28 -36
  101. package/lib/presets/index.js.map +1 -1
  102. package/lib/server.d.ts +23 -2
  103. package/lib/server.js +227 -134
  104. package/lib/server.js.map +1 -1
  105. package/package.json +24 -16
  106. package/src/Game/Map.ts +513 -0
  107. package/src/Game/WorldMaps.ts +45 -0
  108. package/src/Gui/DialogGui.ts +67 -0
  109. package/src/Gui/Gui.ts +45 -0
  110. package/src/Gui/MenuGui.ts +26 -0
  111. package/src/Gui/NotificationGui.ts +10 -0
  112. package/src/Gui/ShopGui.ts +43 -0
  113. package/src/Gui/index.ts +13 -0
  114. package/src/Interfaces/Gui.ts +4 -0
  115. package/src/Interfaces/StateStore.ts +5 -0
  116. package/src/MatchMaker.ts +63 -0
  117. package/src/Monitor/index.ts +78 -0
  118. package/src/Player/BattleManager.ts +123 -0
  119. package/src/Player/ClassManager.ts +72 -0
  120. package/src/Player/ComponentManager.ts +538 -0
  121. package/src/Player/EffectManager.ts +94 -0
  122. package/src/Player/ElementManager.ts +142 -0
  123. package/src/Player/GoldManager.ts +26 -0
  124. package/src/Player/GuiManager.ts +308 -0
  125. package/src/Player/ItemFixture.ts +24 -0
  126. package/src/Player/ItemManager.ts +474 -0
  127. package/src/Player/MoveManager.ts +635 -0
  128. package/src/Player/ParameterManager.ts +468 -0
  129. package/src/Player/Player.ts +931 -0
  130. package/src/Player/SkillManager.ts +229 -0
  131. package/src/Player/StateManager.ts +230 -0
  132. package/src/Player/VariableManager.ts +55 -0
  133. package/src/Query.ts +172 -0
  134. package/src/RpgServer.ts +429 -0
  135. package/src/Scenes/Map.ts +302 -0
  136. package/src/decorators/event.ts +57 -0
  137. package/src/decorators/map.ts +223 -0
  138. package/src/entry-point.ts +102 -0
  139. package/src/express/api.ts +118 -0
  140. package/src/express/errors/NotAuthorized.ts +6 -0
  141. package/src/express/errors/NotFound.ts +6 -0
  142. package/src/express/server.ts +93 -0
  143. package/src/index.ts +28 -0
  144. package/src/logs/index.ts +11 -0
  145. package/src/logs/item.ts +31 -0
  146. package/src/logs/log.ts +3 -0
  147. package/src/logs/skill.ts +16 -0
  148. package/src/logs/state.ts +13 -0
  149. package/src/models/Item.ts +11 -0
  150. package/src/presets/index.ts +71 -0
  151. package/src/server.ts +394 -0
  152. package/tsconfig.json +27 -0
@@ -0,0 +1,931 @@
1
+ import { RpgCommonPlayer, Utils, RpgPlugin, RpgCommonGame, RpgCommonMap, Direction } from '@rpgjs/common'
2
+ import { Room } from 'simple-room'
3
+ import { RpgMap, EventPosOption } from '../Game/Map'
4
+ import { Query } from '../Query'
5
+ import merge from 'lodash.merge'
6
+ import { ItemManager } from './ItemManager'
7
+ import { GoldManager } from './GoldManager'
8
+ import { StateManager } from './StateManager';
9
+ import { SkillManager } from './SkillManager'
10
+ import { ParameterManager } from './ParameterManager';
11
+ import { EffectManager } from './EffectManager';
12
+ import { ClassManager } from './ClassManager';
13
+ import { ElementManager } from './ElementManager'
14
+ import { GuiManager } from './GuiManager'
15
+ import { VariableManager } from './VariableManager'
16
+ import { Frequency, MoveManager, Speed } from './MoveManager'
17
+ import { BattleManager } from './BattleManager'
18
+
19
+ import {
20
+ MAXHP,
21
+ MAXSP,
22
+ STR,
23
+ INT,
24
+ DEX,
25
+ AGI,
26
+ MAXHP_CURVE,
27
+ MAXSP_CURVE,
28
+ STR_CURVE,
29
+ INT_CURVE,
30
+ DEX_CURVE,
31
+ AGI_CURVE
32
+ } from '../presets'
33
+ import { RpgServerEngine } from '../server'
34
+ import { RpgClassMap } from '../Scenes/Map'
35
+ import { RpgTiledWorldMap } from '../Game/WorldMaps'
36
+ import { CameraOptions, PositionXY_OptionalZ, SocketEvents, SocketMethods, LayoutObject } from '@rpgjs/types'
37
+ import { ComponentManager } from './ComponentManager'
38
+ import { Subject } from 'rxjs'
39
+
40
+ const {
41
+ isPromise,
42
+ applyMixins,
43
+ isString
44
+ } = Utils
45
+
46
+ export interface Position { x: number, y: number, z: number }
47
+
48
+ const itemSchemas = {
49
+ name: String,
50
+ description: String,
51
+ price: Number,
52
+ consumable: Boolean,
53
+ id: String
54
+ }
55
+
56
+ export const componentSchema = { id: String, value: String }
57
+ export const layoutSchema = {
58
+ width: Number,
59
+ height: Number,
60
+ marginBottom: Number,
61
+ marginTop: Number,
62
+ marginLeft: Number,
63
+ marginRight: Number,
64
+ lines: [{
65
+ col: [componentSchema]
66
+ }]
67
+ }
68
+
69
+ const playerSchemas = {
70
+ position: {
71
+ x: Number,
72
+ y: Number,
73
+ z: Number
74
+ },
75
+ direction: Number,
76
+ teleported: {
77
+ $permanent: false
78
+ },
79
+ param: Object,
80
+ hp: Number,
81
+ sp: Number,
82
+ gold: Number,
83
+ level: Number,
84
+ exp: Number,
85
+ name: String,
86
+ expForNextlevel: Number,
87
+ items: [{ nb: Number, item: itemSchemas }],
88
+ _class: { name: String, description: String, id: String },
89
+ equipments: [itemSchemas],
90
+ skills: [{ name: String, description: String, spCost: Number, id: String }],
91
+ states: [{ name: String, description: String, id: String }],
92
+ effects: [String],
93
+
94
+ layout: {
95
+ top: layoutSchema,
96
+ bottom: layoutSchema,
97
+ left: layoutSchema,
98
+ right: layoutSchema,
99
+ center: layoutSchema
100
+ },
101
+
102
+ action: Number,
103
+ map: String,
104
+
105
+ speed: Number,
106
+ frequency: Number,
107
+ canMove: Boolean,
108
+ through: Boolean,
109
+ throughOtherPlayer: Boolean,
110
+
111
+ width: Number,
112
+ height: Number,
113
+ wHitbox: Number,
114
+ hHitbox: Number,
115
+
116
+ // only for server
117
+
118
+ _statesEfficiency: [{
119
+ rate: {
120
+ $syncWithClient: false
121
+ },
122
+ state: {
123
+ $syncWithClient: false
124
+ }
125
+ }],
126
+ tmpPositions: {
127
+ $syncWithClient: false
128
+ },
129
+ initialLevel: {
130
+ $syncWithClient: false
131
+ },
132
+ finalLevel: {
133
+ $syncWithClient: false
134
+ },
135
+ }
136
+
137
+ const layoutObject = {
138
+ lines: []
139
+ }
140
+
141
+ export class RpgPlayer extends RpgCommonPlayer {
142
+ public readonly type: string = 'player'
143
+ static schemas = {
144
+ ...playerSchemas,
145
+ events: [playerSchemas]
146
+ }
147
+
148
+ layout: LayoutObject<any> = {
149
+ top: layoutObject,
150
+ bottom: layoutObject,
151
+ left: layoutObject,
152
+ right: layoutObject,
153
+ center: layoutObject
154
+ }
155
+
156
+ private _name
157
+ public events: any = {}
158
+ public param: any
159
+ public _rooms = []
160
+ public session: string | null = null
161
+ public prevMap: string = ''
162
+ /** @internal */
163
+ public server: RpgServerEngine
164
+ private touchSide: boolean = false
165
+ /** @internal */
166
+ public tmpPositions: Position | string | null = null
167
+ public otherPossessedPlayer: RpgPlayer | RpgEvent | null = null
168
+ public following: RpgPlayer | RpgEvent | null = null
169
+
170
+ _lastFramePositions: {
171
+ frame: number
172
+ position: Position
173
+ } | undefined
174
+
175
+ constructor(gameEngine: RpgCommonGame, playerId: string) {
176
+ super(gameEngine, playerId)
177
+ this.initialize()
178
+ }
179
+
180
+ // redefine type (as RpgPlayer)
181
+ get otherPlayersCollision(): RpgPlayer[] {
182
+ return super.otherPlayersCollision as RpgPlayer[]
183
+ }
184
+
185
+ // As soon as a teleport has been made, the value is changed to force the client to change the positions on the map without making a move.
186
+ teleported: number = 0
187
+
188
+ /** @internal */
189
+ initialize() {
190
+ this.expCurve = {
191
+ basis: 30,
192
+ extra: 20,
193
+ accelerationA: 30,
194
+ accelerationB: 30
195
+ }
196
+ this.parameters = new Map()
197
+ this.variables = new Map()
198
+ this.states = []
199
+ this.equipments = []
200
+ this._effects = []
201
+ this.items = []
202
+ this.skills = []
203
+ this.gold = 0
204
+ this.exp = 0
205
+ this.speed = Speed.Normal
206
+ this.frequency = Frequency.None
207
+ this.canMove = true
208
+ this.through = false
209
+ this.throughOtherPlayer = true
210
+ this.initialLevel = 1
211
+ this.finalLevel = 99
212
+ this.level = this.initialLevel
213
+ this._gui = {}
214
+ this._elementsEfficiency = []
215
+ this._statesEfficiency = []
216
+
217
+ this.addParameter(MAXHP, MAXHP_CURVE)
218
+ this.addParameter(MAXSP, MAXSP_CURVE)
219
+ this.addParameter(STR, STR_CURVE)
220
+ this.addParameter(INT, INT_CURVE)
221
+ this.addParameter(DEX, DEX_CURVE)
222
+ this.addParameter(AGI, AGI_CURVE)
223
+ this.allRecovery()
224
+ }
225
+
226
+ _init() {
227
+ this._socket.on('gui.interaction', ({ guiId, name, data }) => {
228
+ if (this._gui[guiId]) {
229
+ this._gui[guiId].emit(name, data)
230
+ this.syncChanges()
231
+ }
232
+ })
233
+ this._socket.on('gui.exit', ({ guiId, data }) => {
234
+ this.removeGui(guiId, data)
235
+ })
236
+ }
237
+
238
+ private get schema() {
239
+ return {
240
+ ...RpgPlayer.schemas,
241
+ ...this.server['playerProps']
242
+ }
243
+ }
244
+
245
+ /**
246
+ * ```ts
247
+ * player.name = 'Link'
248
+ * ```
249
+ * @title Read/Give a name
250
+ * @prop {string} player.name
251
+ * @memberof Player
252
+ * */
253
+ get name(): string {
254
+ return this._name
255
+ }
256
+
257
+ set name(val: string) {
258
+ this._name = val
259
+ }
260
+
261
+ /**
262
+ * Change your map. Indicate the positions to put the player at a place on the map
263
+ *
264
+ * > The map must be added to RpgServer beforehand. Guide: [Create Map](/guide/create-map.html)
265
+ *
266
+ * You don't have to give positions but you can put a starting position in the TMX file. Guide: [Start Position](/guide/player-start.html)
267
+ *
268
+ * @title Change Map
269
+ * @method player.changeMap(mapId,positions)
270
+ * @param {string} mapId
271
+ * @param { {x: number, y: number, z?: number} | string } [positions]
272
+ * @returns {Promise<RpgMap | null>} null if map not exists
273
+ * @memberof Player
274
+ */
275
+ changeMap(mapId: string, positions?: { x: number, y: number, z?: number } | string): Promise<RpgMap | null | boolean> {
276
+ return this.server.sceneMap.changeMap(mapId, this, positions)
277
+ }
278
+
279
+ async autoChangeMap(nextPosition: Position): Promise<boolean> {
280
+ const map = this.getCurrentMap()
281
+ const worldMaps = map?.getInWorldMaps()
282
+ let ret: boolean = false
283
+ if (worldMaps && map) {
284
+ const direction = this.getDirection()
285
+ const marginLeftRight = map.tileWidth / 2
286
+ const marginTopDown = map.tileHeight / 2
287
+
288
+ const changeMap = async (adjacent, to) => {
289
+ if (this.touchSide) {
290
+ return false
291
+ }
292
+ this.touchSide = true
293
+ const [nextMap] = worldMaps.getAdjacentMaps(map, adjacent) as RpgClassMap<RpgMap>[]
294
+ if (!nextMap) return false
295
+ const id = nextMap.id as string
296
+ const nextMapInfo = worldMaps.getMapInfo(id) as RpgTiledWorldMap
297
+ return !!(await this.changeMap(id, to(nextMapInfo)))
298
+ }
299
+
300
+ if (nextPosition.x < marginLeftRight && direction == Direction.Left) {
301
+ ret = await changeMap({
302
+ x: map.worldX - 1,
303
+ y: this.worldPositionY + 1
304
+ }, nextMapInfo => ({
305
+ x: (nextMapInfo.width) - this.wHitbox - marginLeftRight,
306
+ y: map.worldY - nextMapInfo.y + nextPosition.y
307
+ }))
308
+ }
309
+ else if (nextPosition.x > map.widthPx - this.wHitbox - marginLeftRight && direction == Direction.Right) {
310
+ ret = await changeMap({
311
+ x: map.worldX + map.widthPx + 1,
312
+ y: this.worldPositionY + 1
313
+ }, nextMapInfo => ({
314
+ x: marginLeftRight,
315
+ y: map.worldY - nextMapInfo.y + nextPosition.y
316
+ }))
317
+ }
318
+ else if (nextPosition.y < marginTopDown && direction == Direction.Up) {
319
+ ret = await changeMap({
320
+ x: this.worldPositionX + 1,
321
+ y: map.worldY - 1
322
+ }, nextMapInfo => ({
323
+ x: map.worldX - nextMapInfo.x + nextPosition.x,
324
+ y: (nextMapInfo.height) - this.hHitbox - marginTopDown,
325
+ }))
326
+ }
327
+ else if (nextPosition.y > map.heightPx - this.hHitbox - marginTopDown && direction == Direction.Down) {
328
+ ret = await changeMap({
329
+ x: this.worldPositionX + 1,
330
+ y: map.worldY + map.heightPx + 1
331
+ }, nextMapInfo => ({
332
+ x: map.worldX - nextMapInfo.x + nextPosition.x,
333
+ y: marginTopDown,
334
+ }))
335
+ }
336
+ else {
337
+ this.touchSide = false
338
+ }
339
+ }
340
+ return ret
341
+ }
342
+
343
+ /**
344
+ * Dynamically create an event in Scenario mode on the current map
345
+ *
346
+ * ```ts
347
+ * @EventData({
348
+ * name: 'EV-1'
349
+ * })
350
+ * class MyEvent extends RpgEvent {
351
+ * onAction() {
352
+ * console.log('ok')
353
+ * }
354
+ * }
355
+ *
356
+ * player.createDynamicEvent({
357
+ * x: 100,
358
+ * y: 100,
359
+ * event: MyEvent
360
+ * })
361
+ * ```
362
+ *
363
+ * You can also put an array of objects to create several events at once
364
+ *
365
+ * @title Create Dynamic Event
366
+ * @since 3.0.0-beta.4
367
+ * @method player.createDynamicEvent(eventObj | eventObj[])
368
+ * @param { { x: number, y: number, z?: number, event: eventClass } } [eventsList]
369
+ * @returns { { [eventId: string]: RpgEvent } }
370
+ * @memberof Player
371
+ */
372
+ createDynamicEvent(eventsList: EventPosOption | EventPosOption[], forceMode: boolean = true): {
373
+ [eventId: string]: RpgEvent
374
+ } {
375
+ if (!eventsList) return {}
376
+ const mapInstance = this.getCurrentMap<RpgMap>()
377
+ if (!mapInstance) {
378
+ throw 'The player is not assigned to any map'
379
+ }
380
+ if (!Utils.isArray(eventsList)) {
381
+ eventsList = [eventsList as EventPosOption]
382
+ }
383
+ let eventsListMode = eventsList
384
+ if (forceMode) {
385
+ eventsListMode = (eventsList as EventPosOption[]).map(event => {
386
+ event.event.mode = EventMode.Scenario
387
+ return event
388
+ })
389
+ }
390
+ const events = mapInstance.createEvents(eventsListMode as EventPosOption[], EventMode.Scenario)
391
+ let ret = {}
392
+ for (let key in events) {
393
+ this.events[key] = events[key]
394
+ this.events[key].execMethod('onInit', [this])
395
+ // force to get Proxy object to sync with client
396
+ ret = { ...ret, [key]: this.events[key] }
397
+ }
398
+ return ret
399
+ }
400
+
401
+ /**
402
+ * Removes an event from the map (Scenario Mode). Returns false if the event is not found
403
+ * @title Remove Event
404
+ * @since 3.0.0-beta.4
405
+ * @method player.removeEvent(eventId)
406
+ * @param {string} eventId Event Name
407
+ * @returns {boolean}
408
+ * @memberof Player
409
+ */
410
+ removeEvent(eventId: string): boolean {
411
+ if (!this.events[eventId]) return false
412
+ delete this.events[eventId]
413
+ return true
414
+ }
415
+
416
+ /**
417
+ * Allows to change the positions of the player on the current map.
418
+ * You can put the X and Y positions or the name of the created shape on Tiled Map Editor.
419
+ * If you have several shapes with the same name, one position will be chosen randomly.
420
+ *
421
+ * ```ts
422
+ * player.teleport({ x: 100, y: 500 })
423
+ * ```
424
+ *
425
+ * or
426
+ *
427
+ * ```ts
428
+ * player.teleport('my-shape-name')
429
+ * ```
430
+ *
431
+ * If no parameter:
432
+ *
433
+ * ```ts
434
+ * player.teleport() // { x: 0, y: 0, z: 0 }
435
+ * ```
436
+ *
437
+ * @title Teleport on the map
438
+ * @method player.teleport(positions)
439
+ * @param { {x: number, y: number, z?: number} | string } [positions]
440
+ * @returns {Promise<{ {x: number, y: number, z: number} }>}
441
+ * @memberof Player
442
+ */
443
+ async teleport(positions?: PositionXY_OptionalZ | string): Promise<Position> {
444
+ if (isString(positions)) positions = <Position>this.getCurrentMap()?.getPositionByShape(shape => shape.name == positions || shape.getType() == positions)
445
+ if (!positions) positions = { x: 0, y: 0, z: 0 }
446
+ if (!(positions as Position).z) (positions as Position).z = 0
447
+ this.teleported++
448
+ this.position = positions as Position
449
+ // delete last frame positions because when the character is teleported, no server reconciliation is needed on the client side
450
+ this._lastFramePositions = undefined
451
+ // force interaction with event or shape
452
+ await this.isCollided(this.position)
453
+ return (positions as Position)
454
+ }
455
+
456
+ /**
457
+ * Load the saved data with the method save()
458
+ * If the player was on a map, it repositions the player on the map.
459
+ *
460
+ * ```ts
461
+ * const json = player.save()
462
+ * player.load(json)
463
+ * ```
464
+ *
465
+ * @title Load progress
466
+ * @method player.load(json)
467
+ * @param {string} json The JSON sent by the method save()
468
+ * @returns {string}
469
+ * @memberof Player
470
+ */
471
+ load(json: any) {
472
+ if (isString(json)) json = JSON.parse(json)
473
+
474
+ const getData = (id) => new (this.databaseById(id))()
475
+
476
+ for (let key in json) {
477
+ const val = json[key]
478
+ if (Utils.isObject(val) && val.hasOwnProperty('0')) {
479
+ json[key] = Object.values(val)
480
+ }
481
+ }
482
+
483
+ const items = {}
484
+
485
+ if (json.items) {
486
+ for (let it of json.items) {
487
+ items[it.item.id] = getData(it.item.id)
488
+ }
489
+ json.items = json.items.map(it => ({ nb: it.nb, item: items[it.item.id] }))
490
+ json.equipments = json.equipments.map(it => {
491
+ items[it.id].equipped = true
492
+ return items[it.id]
493
+ })
494
+ }
495
+ if (json.states) json.states = json.states.map(state => getData(state.id))
496
+ if (json.skills) json.skills = json.skills.map(skill => getData(skill.id))
497
+ if (json.variables) json.variables = new Map(json.variables)
498
+
499
+ merge(this, json)
500
+
501
+ this.position = json.position
502
+ if (json.map) {
503
+ this.map = ''
504
+ this.changeMap(json.map, json.tmpPositions || json.position)
505
+ }
506
+ }
507
+
508
+ /**
509
+ * Returns a JSON with all the data to keep in memory. Then use the `load()` method to load the data
510
+ *
511
+ * You can also use the JSON.stringify
512
+ *
513
+ * ```ts
514
+ * const json = player.save() // or JSON.stringify(player)
515
+ * player.load(json)
516
+ * ```
517
+ *
518
+ * @title Save progress
519
+ * @method player.save()
520
+ * @returns {string}
521
+ * @memberof Player
522
+ */
523
+ save() {
524
+ return JSON.stringify(this)
525
+ }
526
+
527
+ // TODO
528
+ toObject() {
529
+ return {
530
+ direction: this.direction,
531
+ id: this.id,
532
+ canMove: this.canMove,
533
+ position: {
534
+ x: this.position.x,
535
+ y: this.position.y,
536
+ z: this.position.z
537
+ },
538
+ hitbox: {
539
+ width: this.wHitbox,
540
+ height: this.hHitbox
541
+ },
542
+ map: this.map,
543
+ pendingMove: this.pendingMove,
544
+ speed: this.speed
545
+ }
546
+ }
547
+
548
+ toJSON() {
549
+ const { permanentObject } = Room.toDict(this.schema)
550
+ const snapshot = Room.extractObjectOfRoom(this, permanentObject)
551
+ snapshot.variables = [...this.variables]
552
+ return snapshot
553
+ }
554
+
555
+ /**
556
+ * Run the change detection cycle. Normally, as soon as a hook is called in a class, the cycle is started. But you can start it manually
557
+ * The method calls the `onChanges` method on events and synchronizes all map data with the client.
558
+
559
+ * @title Run Sync Changes
560
+ * @method player.syncChanges()
561
+ * @returns {void}
562
+ * @memberof Player
563
+ */
564
+ syncChanges() {
565
+ this._eventChanges()
566
+ }
567
+
568
+ databaseById(id: string) {
569
+ const data = this.server.database[id]
570
+ if (!data) throw new Error(`The ID=${id} data is not found in the database. Add the data in the property "database" of @RpgServer decorator.`)
571
+ return data
572
+ }
573
+
574
+ /**
575
+ * Retrieves data from the current map
576
+ *
577
+ * returns null if the player is not assigned to a map
578
+ *
579
+ * @title Get Current Map
580
+ * @method player.getCurrentMap()
581
+ * @returns {RpgMap | null}
582
+ * @memberof Player
583
+ */
584
+ getCurrentMap<T extends RpgMap = RpgMap>(): T | null {
585
+ return this._getMap(this.map)
586
+ }
587
+
588
+ loadScene(name: string, data: any): void {
589
+ this.emit(SocketEvents.LoadScene, {
590
+ name,
591
+ data
592
+ })
593
+ }
594
+
595
+ emitSceneMap() {
596
+ const mapInstance = this.getCurrentMap<RpgMap>()
597
+ if (!mapInstance) {
598
+ throw '[Emit] The player is not assigned to any map'
599
+ }
600
+ let { data: serializeMap } = Object.assign({}, mapInstance) as any
601
+ delete serializeMap.shapes
602
+ delete serializeMap.events
603
+ delete serializeMap._events
604
+
605
+ for (let layer of serializeMap.layers) {
606
+ delete layer.map
607
+ }
608
+
609
+ this.loadScene('map', {
610
+ id: mapInstance.id,
611
+ sounds: mapInstance.sounds,
612
+ ...serializeMap
613
+ })
614
+ }
615
+
616
+ gameReload() {
617
+ this.emit(SocketEvents.GameReload)
618
+ }
619
+
620
+ changeServer(url: string, port: number) {
621
+ this.emit(SocketEvents.ChangeServer, {
622
+ url,
623
+ port
624
+ })
625
+ }
626
+
627
+ private _getMap(id) {
628
+ return RpgCommonMap.buffer.get(id)
629
+ }
630
+
631
+ /**
632
+ * Calls the showAnimation() method on the client side to display an animation on the player
633
+ * You must remember to create the spritesheet beforehand
634
+ *
635
+ * For this type of spritesheet:
636
+ *
637
+ * ```ts
638
+ * @Spritesheet({
639
+ * id: 'fire',
640
+ * image: require('')
641
+ * textures: {
642
+ * default: {
643
+ * animations: [
644
+ *
645
+ * ]
646
+ * }
647
+ * }
648
+ * })
649
+ * export class FireAnimation {}
650
+ * ```
651
+ *
652
+ * Here is the call of the method:
653
+ *
654
+ * ```ts
655
+ * player.showAnimation('fire', 'default')
656
+ * ```
657
+ *
658
+ * If you don't want to put an animation on top of the event but replace the event graphic with another one, set true as last parameter.
659
+ * This is useful, if for example, you want to make an animated character (sword stroke when pressing a key)
660
+ * When the animation is finished, the original graphic is displayed again
661
+ *
662
+ * ```ts
663
+ * player.showAnimation('sword_stroke', 'default', true)
664
+ * ```
665
+ *
666
+ * Since version 3.0.0-rc, you can define several graphic elements. This allows you to animate them all at once
667
+ *
668
+ * ```ts
669
+ * player.showAnimation(['body', 'sword_stroke'], 'default', true)
670
+ * ```
671
+ *
672
+ * @title Show Animation
673
+ * @method player.showAnimation(graphic,animationName,replaceGraphic=false)
674
+ * @param {string | string[]} graphic spritesheet identifier
675
+ * @param {string} animationName Name of the animation in the spritesheet
676
+ * @param {boolean} [replaceGraphic] Replace the event graphic with the animation. After the end of the animation, the original graphic is reapplied
677
+ * @returns {void}
678
+ * @memberof Player
679
+ */
680
+ showAnimation(graphic: string | string[], animationName: string, replaceGraphic: boolean = false) {
681
+ this.emitToMap('callMethod', {
682
+ objectId: this.playerId,
683
+ name: SocketMethods.ShowAnimation,
684
+ params: [graphic, animationName, replaceGraphic]
685
+ })
686
+ }
687
+
688
+ /**
689
+ * TODO:
690
+ * 1. It is necessary, on the client side, to make the character move even if controlled by someone else (problem: same playerId so, one will not move because of the client side prediction. Solution: create a new Id ? like session Id ?
691
+ * 2. You would need several sockets per character. If the character changes map or changes server, all players controlling the character must be able to see it
692
+ * 3. If the player regains control, what happens, do we return to the previous map?
693
+ * 4. If it's an event, you must be able to get the event by id in GameEngine
694
+ */
695
+ takePossessionOf(otherPlayer: RpgPlayer | RpgEvent) {
696
+ this.otherPossessedPlayer = otherPlayer
697
+ this._socket.emit('playerJoined', { playerId: otherPlayer.id, session: this.session })
698
+ this.cameraFollow(otherPlayer)
699
+ }
700
+
701
+ /**
702
+ * Sends the client which event or player the camera should follow. You can set options to perform a motion animation
703
+ *
704
+ * @title Camera Follow
705
+ * @method player.cameraFollow(otherPlayer,options)
706
+ * @param {RpgPlayer | RpgEvent} otherPlayer
707
+ * @param {options} options
708
+ * @param {object | boolean} [options.smoothMove] - animate. Set a boolean to use default parameters
709
+ * @param {number} [options.smoothMove.time=1000] - time to animate
710
+ * @param {string} [options.smoothMove.ease=linear] - easing to use. Go to https://easings.net to get function name
711
+ * @returns {void}
712
+ * @since 3.1.0
713
+ * @memberof Player
714
+ */
715
+ cameraFollow(otherPlayer: RpgPlayer | RpgEvent, options: CameraOptions = {}) {
716
+ if (otherPlayer.id == this.id) {
717
+ this.following = null
718
+ }
719
+ else {
720
+ this.following = otherPlayer
721
+ }
722
+ this.emit(SocketEvents.CallMethod, {
723
+ objectId: this.playerId,
724
+ name: SocketMethods.CameraFollow,
725
+ params: [otherPlayer.id, options]
726
+ })
727
+ }
728
+
729
+ /**
730
+ * Emit data to clients with socket
731
+ *
732
+ * @title Emit to client
733
+ * @method player.emit(key,value)
734
+ * @param {string} key
735
+ * @param {any} value
736
+ * @returns {void}
737
+ * @memberof Player
738
+ */
739
+ public emit(key: string, value?: any): void {
740
+ if (this._socket) this._socket.emit(key, value)
741
+ }
742
+
743
+
744
+ /**
745
+ * Listen to the data (socket) sent by the client
746
+ *
747
+ * @title Listen to data from the client
748
+ * @method player.on(key,cb)
749
+ * @param {string} key
750
+ * @param {function} cb
751
+ * @returns {void}
752
+ * @memberof Player
753
+ */
754
+ public on(key: string, cb: Function) {
755
+ if (this._socket) this._socket.on(key, cb)
756
+ }
757
+
758
+ /**
759
+ * Adds a one-time listener function for the event named eventName
760
+ *
761
+ * @title Listen one-time to data from the client
762
+ * @method player.once(key,cb)
763
+ * @since 3.0.0-beta.5
764
+ * @param {string} key
765
+ * @param {function} cb
766
+ * @returns {void}
767
+ * @memberof Player
768
+ */
769
+ public once(key: string, cb: Function) {
770
+ if (this._socket) this._socket.once(key, cb)
771
+ }
772
+
773
+ /**
774
+ * Removes all listeners of the specified eventName.
775
+ *
776
+ * @title Removes all listeners of the client
777
+ * @method player.off(key)
778
+ * @since 3.0.0-beta.5
779
+ * @param {string} key
780
+ * @returns {void}
781
+ * @memberof Player
782
+ */
783
+ public off(key: string) {
784
+ if (this._socket) this._socket.removeAllListeners(key)
785
+ }
786
+
787
+ emitToMap(key: string, value: any) {
788
+ Query.getPlayersOfMap(this.map).forEach(player => player.emit(key, value))
789
+ }
790
+
791
+ async execMethod(methodName: string, methodData = []) {
792
+ const ret = await RpgPlugin.emit(`Server.${methodName}`, [this, ...methodData], true)
793
+ this.syncChanges()
794
+ return ret
795
+ }
796
+
797
+ _triggerHook(name, val?) {
798
+ if (this[name]) this[name](val)
799
+ this.emit('Player.' + name, val)
800
+ }
801
+
802
+ private _eventChanges() {
803
+ if (!this._getMap(this.map)) return
804
+ const {
805
+ events
806
+ } = this._getMap(this.map)
807
+ const arrayEvents: any[] = [...Object.values(this.events), ...Object.values(events)]
808
+ for (let event of arrayEvents) {
809
+ if (event.onChanges) event.onChanges(this)
810
+ }
811
+ }
812
+
813
+ /**
814
+ * Allows to play a sound, heard only by the player or by the players of the map
815
+ *
816
+ * Here is a sound, client side:
817
+ *
818
+ * ```ts
819
+ * import { Sound } from '@rpgjs/client'
820
+ * @Sound({
821
+ * id: 'town-music',
822
+ * sound: require('./sound/town.ogg')
823
+ * })
824
+ * export class TownMusic {}
825
+ * ```
826
+ *
827
+ * Here is the call of the method, server side:
828
+ *
829
+ * ```ts
830
+ * player.playSound('town-music')
831
+ * ```
832
+ *
833
+ * If you want everyone to listen to the sound on the map:
834
+ *
835
+ * ```ts
836
+ * player.playSound('town-music', true)
837
+ * ```
838
+ *
839
+ * @title Play Sound
840
+ * @method player.playSound(soundId,allMap=false)
841
+ * @param {string} soundId Sound identifier, defined on the client side
842
+ * @param {boolean} [allMap] Indicate if the sound is heard by the players on the map
843
+ * @since 3.0.0-alpha.9
844
+ * @returns {void}
845
+ * @memberof Player
846
+ */
847
+ playSound(soundId: string, allMap: boolean = false) {
848
+ const obj = {
849
+ objectId: this.playerId,
850
+ name: SocketMethods.PlaySound,
851
+ params: [soundId]
852
+ }
853
+ if (!allMap) {
854
+ this.emit(SocketEvents.CallMethod, obj)
855
+ return
856
+ }
857
+ this.emitToMap(SocketEvents.CallMethod, obj)
858
+ }
859
+
860
+ }
861
+
862
+ export interface RpgPlayer extends
863
+ ItemManager,
864
+ GoldManager,
865
+ StateManager,
866
+ SkillManager,
867
+ ParameterManager,
868
+ EffectManager,
869
+ ClassManager,
870
+ ElementManager,
871
+ GuiManager,
872
+ VariableManager,
873
+ MoveManager,
874
+ BattleManager,
875
+ ComponentManager {
876
+ _socket: any
877
+ vision,
878
+ attachShape: any
879
+ }
880
+
881
+ applyMixins(RpgPlayer, [
882
+ ItemManager,
883
+ GoldManager,
884
+ StateManager,
885
+ SkillManager,
886
+ ParameterManager,
887
+ EffectManager,
888
+ ClassManager,
889
+ ElementManager,
890
+ GuiManager,
891
+ VariableManager,
892
+ MoveManager,
893
+ BattleManager,
894
+ ComponentManager
895
+ ])
896
+
897
+ export enum EventMode {
898
+ Shared = 'shared',
899
+ Scenario = 'scenario'
900
+ }
901
+
902
+ export interface RpgClassEvent<T> {
903
+ _name: string
904
+ new(): T,
905
+ }
906
+
907
+ export class RpgEvent extends RpgPlayer {
908
+
909
+ public readonly type: string = 'event'
910
+ properties: any = {}
911
+
912
+ async execMethod(methodName: string, methodData = []) {
913
+ if (!this[methodName]) {
914
+ return
915
+ }
916
+ const ret = this[methodName](...methodData)
917
+ const sync = () => {
918
+ const player: any = methodData[0]
919
+ if (player instanceof RpgPlayer) {
920
+ player.syncChanges()
921
+ }
922
+ }
923
+ if (Utils.isPromise(ret)) {
924
+ ret.then(sync)
925
+ }
926
+ else {
927
+ sync()
928
+ }
929
+ return ret
930
+ }
931
+ }