@rpgjs/server 5.0.0-alpha.33 → 5.0.0-alpha.35

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.
@@ -1,5 +1,4 @@
1
- export declare class Log {
2
- private id;
3
- private msg;
1
+ export declare class Log extends Error {
2
+ readonly id: string;
4
3
  constructor(id: string, msg: string);
5
4
  }
@@ -1,5 +1,5 @@
1
1
  import { MockConnection, RoomMethods, RoomOnJoin } from '@signe/room';
2
- import { Hooks, RpgCommonMap, RpgShape, WorldMapsManager, WorldMapConfig } from '../../../common/src';
2
+ import { Hooks, RpgCommonMap, RpgShape, WorldMapsManager, WeatherState, WorldMapConfig } from '../../../common/src';
3
3
  import { RpgPlayer, RpgEvent } from '../Player/Player';
4
4
  import { BehaviorSubject } from 'rxjs';
5
5
  import { MapOptions } from '../decorators/map';
@@ -17,6 +17,8 @@ export interface Controls {
17
17
  minTimeBetweenInputs?: number;
18
18
  /** Whether to enable anti-cheat validation */
19
19
  enableAntiCheat?: boolean;
20
+ /** Maximum number of queued inputs processed per server tick */
21
+ maxInputsPerTick?: number;
20
22
  }
21
23
  /**
22
24
  * Interface representing hook methods available for map events
@@ -58,6 +60,9 @@ export type EventPosOption = {
58
60
  */
59
61
  event: EventConstructor | (EventHooks & Record<string, any>);
60
62
  };
63
+ interface WeatherSetOptions {
64
+ sync?: boolean;
65
+ }
61
66
  export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
62
67
  /**
63
68
  * Synchronized signal containing all players currently on the map
@@ -158,6 +163,7 @@ export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoi
158
163
  * with custom formulas when the map is loaded.
159
164
  */
160
165
  damageFormulas: any;
166
+ private _weatherState;
161
167
  /** Internal: Map of shapes by name */
162
168
  private _shapes;
163
169
  /** Internal: Map of shape entity UUIDs to RpgShape instances */
@@ -169,6 +175,8 @@ export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoi
169
175
  autoSync: boolean;
170
176
  constructor(room: any);
171
177
  onStart(): Promise<void>;
178
+ private isPositiveNumber;
179
+ private resolveTrustedMapDimensions;
172
180
  /**
173
181
  * Setup collision detection between players, events, and shapes
174
182
  *
@@ -391,6 +399,10 @@ export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoi
391
399
  * ```
392
400
  */
393
401
  onInput(player: RpgPlayer, input: any): Promise<void>;
402
+ onPing(player: RpgPlayer, payload: {
403
+ clientTime?: number;
404
+ clientFrame?: number;
405
+ }): void;
394
406
  saveSlot(player: RpgPlayer, value: {
395
407
  requestId: string;
396
408
  index: number;
@@ -463,10 +475,11 @@ export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoi
463
475
  /**
464
476
  * Process pending inputs for a player with anti-cheat validation
465
477
  *
466
- * This method processes all pending inputs for a player while performing
478
+ * This method processes pending inputs for a player while performing
467
479
  * anti-cheat validation to prevent time manipulation and frame skipping.
468
480
  * It validates the time deltas between inputs and ensures they are within
469
- * acceptable ranges.
481
+ * acceptable ranges. To preserve movement itinerary under network bursts,
482
+ * the number of inputs processed per call is capped.
470
483
  *
471
484
  * ## Architecture
472
485
  *
@@ -924,6 +937,27 @@ export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoi
924
937
  x: number;
925
938
  y: number;
926
939
  }, graphic: string, animationName?: string): void;
940
+ private cloneWeatherState;
941
+ /**
942
+ * Get the current map weather state.
943
+ */
944
+ getWeather(): WeatherState | null;
945
+ /**
946
+ * Set the full weather state for this map.
947
+ *
948
+ * When `sync` is true (default), all connected clients receive the new weather.
949
+ */
950
+ setWeather(next: WeatherState | null, options?: WeatherSetOptions): WeatherState | null;
951
+ /**
952
+ * Patch the current weather state.
953
+ *
954
+ * Nested `params` values are merged.
955
+ */
956
+ patchWeather(patch: Partial<WeatherState>, options?: WeatherSetOptions): WeatherState | null;
957
+ /**
958
+ * Clear weather for this map.
959
+ */
960
+ clearWeather(options?: WeatherSetOptions): void;
927
961
  /**
928
962
  * Configure runtime synchronized properties on the map
929
963
  *
@@ -1257,3 +1291,4 @@ export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoi
1257
1291
  }
1258
1292
  export interface RpgMap extends RoomMethods {
1259
1293
  }
1294
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpgjs/server",
3
- "version": "5.0.0-alpha.33",
3
+ "version": "5.0.0-alpha.35",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "publishConfig": {
@@ -11,16 +11,16 @@
11
11
  "license": "MIT",
12
12
  "description": "",
13
13
  "dependencies": {
14
- "@rpgjs/common": "5.0.0-alpha.33",
15
- "@rpgjs/physic": "5.0.0-alpha.33",
16
- "@rpgjs/testing": "5.0.0-alpha.33",
14
+ "@rpgjs/common": "5.0.0-alpha.35",
15
+ "@rpgjs/physic": "5.0.0-alpha.35",
16
+ "@rpgjs/testing": "5.0.0-alpha.35",
17
17
  "@rpgjs/database": "^4.3.0",
18
18
  "@signe/di": "^2.8.3",
19
19
  "@signe/reactive": "^2.8.3",
20
20
  "@signe/room": "^2.8.3",
21
21
  "@signe/sync": "^2.8.3",
22
22
  "rxjs": "^7.8.2",
23
- "zod": "^4.3.5"
23
+ "zod": "^4.3.6"
24
24
  },
25
25
  "devDependencies": {
26
26
  "vite": "^7.3.1",
@@ -10,6 +10,32 @@ export type ExpCurve = {
10
10
  accelerationB: number;
11
11
  };
12
12
 
13
+ const DEFAULT_EXP_CURVE: ExpCurve = {
14
+ basis: 30,
15
+ extra: 20,
16
+ accelerationA: 30,
17
+ accelerationB: 30
18
+ };
19
+
20
+ function isObject(value: unknown): value is Record<string, unknown> {
21
+ return typeof value === "object" && value !== null;
22
+ }
23
+
24
+ function toValidNumber(value: unknown, fallback: number): number {
25
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
26
+ }
27
+
28
+ function normalizeExpCurve(value: unknown): ExpCurve {
29
+ if (!isObject(value)) return DEFAULT_EXP_CURVE;
30
+
31
+ return {
32
+ basis: toValidNumber(value.basis, DEFAULT_EXP_CURVE.basis),
33
+ extra: toValidNumber(value.extra, DEFAULT_EXP_CURVE.extra),
34
+ accelerationA: toValidNumber(value.accelerationA, DEFAULT_EXP_CURVE.accelerationA),
35
+ accelerationB: toValidNumber(value.accelerationB, DEFAULT_EXP_CURVE.accelerationB)
36
+ };
37
+ }
38
+
13
39
  /**
14
40
  * Interface for Parameter Manager functionality
15
41
  *
@@ -549,10 +575,18 @@ export function WithParameterManager<TBase extends PlayerCtor>(Base: TBase) {
549
575
  * ```
550
576
  * @memberof ParameterManager
551
577
  * */
552
- public _expCurveSignal = type(signal<string>('') as any, '_expCurveSignal', { persist: true }, this as any)
578
+ public _expCurveSignal = type(signal<string>(JSON.stringify(DEFAULT_EXP_CURVE)) as any, '_expCurveSignal', { persist: true }, this as any)
553
579
 
554
580
  get expCurve(): ExpCurve {
555
- return JSON.parse(this._expCurveSignal())
581
+ const raw = this._expCurveSignal()
582
+ if (!raw) return DEFAULT_EXP_CURVE
583
+
584
+ try {
585
+ return normalizeExpCurve(JSON.parse(raw))
586
+ }
587
+ catch {
588
+ return DEFAULT_EXP_CURVE
589
+ }
556
590
  }
557
591
 
558
592
  set expCurve(val: ExpCurve) {
@@ -187,7 +187,9 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
187
187
  constructor() {
188
188
  super();
189
189
 
190
- let lastEmitted: { x: number; y: number } | null = null;
190
+ const initialX = typeof this.x === "function" ? Number(this.x()) || 0 : 0;
191
+ const initialY = typeof this.y === "function" ? Number(this.y()) || 0 : 0;
192
+ let lastEmitted: { x: number; y: number } | null = { x: initialX, y: initialY };
191
193
  let pendingUpdate: { x: number; y: number } | null = null;
192
194
  let updateScheduled = false;
193
195
 
@@ -252,6 +254,8 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
252
254
 
253
255
  setMap(map: RpgMap) {
254
256
  this.map = map;
257
+ // Prevent immediate ping-pong map transfers when spawning near a border.
258
+ this.touchSide = true;
255
259
  }
256
260
 
257
261
  applyFrames() {
@@ -303,12 +307,13 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
303
307
  if (canChange.some(v => v === false)) return false;
304
308
 
305
309
  if (positions && typeof positions === 'object') {
306
- this.teleport(positions)
310
+ await this.teleport(positions)
307
311
  }
308
- await room?.$sessionTransfer(this.conn, realMapId);
312
+ const transferToken = await room?.$sessionTransfer(this.conn, realMapId);
309
313
  this.emit("changeMap", {
310
314
  mapId: realMapId,
311
315
  positions,
316
+ transferToken: typeof transferToken === 'string' ? transferToken : undefined,
312
317
  });
313
318
  return true;
314
319
  }
@@ -321,17 +326,34 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
321
326
  const direction = this.getDirection()
322
327
  const marginLeftRight = map.tileWidth / 2
323
328
  const marginTopDown = map.tileHeight / 2
329
+ const hitbox = this.hitbox()
330
+ const currentX = this.x()
331
+ const currentY = this.y()
332
+ const nearBorder =
333
+ currentX < marginLeftRight ||
334
+ currentX > map.widthPx - hitbox.w - marginLeftRight ||
335
+ currentY < marginTopDown ||
336
+ currentY > map.heightPx - hitbox.h - marginTopDown
337
+
338
+ if (this.touchSide) {
339
+ if (nearBorder) {
340
+ return false
341
+ }
342
+ this.touchSide = false
343
+ }
324
344
 
325
345
  const changeMap = async (adjacent, to) => {
326
- if (this.touchSide) {
346
+ const [nextMap] = worldMaps.getAdjacentMaps(map, adjacent)
347
+ if (!nextMap) {
327
348
  return false
328
349
  }
329
- this.touchSide = true
330
- const [nextMap] = worldMaps.getAdjacentMaps(map, adjacent)
331
- if (!nextMap) return false
332
350
  const id = nextMap.id as string
333
351
  const nextMapInfo = worldMaps.getMapInfo(id)
334
- return !!(await this.changeMap(id, to(nextMapInfo)))
352
+ const changed = !!(await this.changeMap(id, to(nextMapInfo)))
353
+ if (changed) {
354
+ this.touchSide = true
355
+ }
356
+ return changed
335
357
  }
336
358
 
337
359
  if (nextPosition.x < marginLeftRight && direction == Direction.Left) {
@@ -1,3 +1,5 @@
1
+ import type { WeatherState } from "@rpgjs/common";
2
+
1
3
  export interface MapOptions {
2
4
  /**
3
5
  * Map identifier. Allows to go to the map (for example with player.changeMap())
@@ -97,6 +99,28 @@ export interface MapOptions {
97
99
  * */
98
100
  sounds?: string[]
99
101
 
102
+ /**
103
+ * Initial weather state for this map.
104
+ *
105
+ * This value is applied when the map is loaded and can later be updated
106
+ * at runtime with `map.setWeather()` from server logic.
107
+ *
108
+ * ```ts
109
+ * @MapData({
110
+ * id: 'forest',
111
+ * file: require('./tmx/forest.tmx'),
112
+ * weather: {
113
+ * effect: 'fog',
114
+ * preset: 'rpgForestFog',
115
+ * params: { density: 1.2, height: 0.75 },
116
+ * transitionMs: 1200
117
+ * }
118
+ * })
119
+ * class ForestMap extends RpgMap {}
120
+ * ```
121
+ */
122
+ weather?: WeatherState | null
123
+
100
124
  /**
101
125
  * Whether to stop all sounds before playing the map sounds when a player joins.
102
126
  *
@@ -278,6 +302,7 @@ export function MapData(options: MapOptions) {
278
302
  target.prototype.file = options.file
279
303
  target.prototype.id = options.id
280
304
  target.prototype.sounds = options.sounds
305
+ target.prototype.weather = options.weather
281
306
  target.prototype.lowMemory = options.lowMemory
282
307
  target.prototype.stopAllSoundsBeforeJoin = options.stopAllSoundsBeforeJoin
283
308
 
@@ -299,4 +324,4 @@ export function MapData(options: MapOptions) {
299
324
  target.prototype.onLeave = options.onLeave
300
325
  }
301
326
  }
302
- }
327
+ }
package/src/logs/log.ts CHANGED
@@ -1,3 +1,10 @@
1
- export class Log {
2
- constructor(private id: string, private msg: string) {}
3
- }
1
+ export class Log extends Error {
2
+ readonly id: string;
3
+
4
+ constructor(id: string, msg: string) {
5
+ super(`[${id}] ${msg}`);
6
+ this.name = "RpgLog";
7
+ this.id = id;
8
+ Object.setPrototypeOf(this, new.target.prototype);
9
+ }
10
+ }
package/src/module.ts CHANGED
@@ -97,6 +97,7 @@ export function provideServerModules(modules: RpgServerModule[]): FactoryProvide
97
97
  type: MapClass.type,
98
98
  name: MapClass.prototype?.name,
99
99
  sounds: MapClass.prototype?.sounds,
100
+ weather: MapClass.prototype?.weather,
100
101
  lowMemory: MapClass.prototype?.lowMemory,
101
102
  stopAllSoundsBeforeJoin: MapClass.prototype?.stopAllSoundsBeforeJoin,
102
103
  events: MapClass.prototype?._events,