@rpgjs/server 5.0.0-alpha.25 → 5.0.0-alpha.26

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/dist/index.js CHANGED
@@ -7743,12 +7743,30 @@ var Server = class {
7743
7743
  load(instance, tmpObject, true);
7744
7744
  }, "loadMemory");
7745
7745
  instance.$memoryAll = {};
7746
+ instance.$autoSync = instance["autoSync"] !== false;
7747
+ instance.$pendingSync = /* @__PURE__ */ new Map();
7746
7748
  instance.$send = (conn, obj) => {
7747
7749
  return this.send(conn, obj, instance);
7748
7750
  };
7749
7751
  instance.$broadcast = (obj) => {
7750
7752
  return this.broadcast(obj, instance);
7751
7753
  };
7754
+ instance.$applySync = () => {
7755
+ let packet;
7756
+ if (instance.$pendingSync.size > 0) {
7757
+ if (options.getMemoryAll) {
7758
+ buildObject(instance.$pendingSync, instance.$memoryAll);
7759
+ }
7760
+ packet = buildObject(instance.$pendingSync, instance.$memoryAll);
7761
+ instance.$pendingSync.clear();
7762
+ } else {
7763
+ packet = instance.$memoryAll;
7764
+ }
7765
+ this.broadcast({
7766
+ type: "sync",
7767
+ value: packet
7768
+ }, instance);
7769
+ };
7752
7770
  instance.$sessionTransfer = async (conn, targetRoomId) => {
7753
7771
  let user;
7754
7772
  const signal2 = this.getUsersProperty(instance);
@@ -7815,6 +7833,13 @@ var Server = class {
7815
7833
  init = false;
7816
7834
  return;
7817
7835
  }
7836
+ if (!instance.$autoSync) {
7837
+ for (const [path, value] of values) {
7838
+ instance.$pendingSync.set(path, value);
7839
+ }
7840
+ values.clear();
7841
+ return;
7842
+ }
7818
7843
  const packet = buildObject(values, instance.$memoryAll);
7819
7844
  this.broadcast({
7820
7845
  type: "sync",
@@ -7839,11 +7864,12 @@ var Server = class {
7839
7864
  values.clear();
7840
7865
  }, "persistCb");
7841
7866
  syncClass(instance, {
7842
- onSync: throttle(syncCb, instance["throttleSync"] ?? 500),
7843
- onPersist: throttle(persistCb, instance["throttleStorage"] ?? 2e3)
7867
+ onSync: instance["throttleSync"] ? throttle(syncCb, instance["throttleSync"]) : syncCb,
7868
+ onPersist: instance["throttleStorage"] ? throttle(persistCb, instance["throttleStorage"]) : persistCb
7844
7869
  });
7845
7870
  await loadMemory();
7846
7871
  initPersist = false;
7872
+ init = false;
7847
7873
  return instance;
7848
7874
  }
7849
7875
  /**
@@ -8049,13 +8075,15 @@ var Server = class {
8049
8075
  publicId
8050
8076
  });
8051
8077
  await awaitReturn(subRoom["onJoin"]?.(user, conn, ctx));
8052
- this.send(conn, {
8053
- type: "sync",
8054
- value: {
8055
- pId: publicId,
8056
- ...subRoom.$memoryAll
8057
- }
8058
- }, subRoom);
8078
+ if (subRoom.$autoSync) {
8079
+ this.send(conn, {
8080
+ type: "sync",
8081
+ value: {
8082
+ pId: publicId,
8083
+ ...subRoom.$memoryAll
8084
+ }
8085
+ }, subRoom);
8086
+ }
8059
8087
  }
8060
8088
  /**
8061
8089
  * @method onConnect
@@ -9777,10 +9805,10 @@ class Item {
9777
9805
  this.quantity = signal(1);
9778
9806
  this.onAdd = () => {
9779
9807
  };
9780
- this.description.set(data.description);
9781
- this.price.set(data.price);
9782
- this.name.set(data.name);
9783
- this.onAdd = data.onAdd?.bind(this) ?? (() => {
9808
+ this.description.set(data?.description ?? "");
9809
+ this.price.set(data?.price ?? 0);
9810
+ this.name.set(data?.name ?? "");
9811
+ this.onAdd = data?.onAdd?.bind(this) ?? (() => {
9784
9812
  });
9785
9813
  }
9786
9814
  }
@@ -15777,6 +15805,10 @@ class RpgCommonMap {
15777
15805
  });
15778
15806
  this.moveManager = new MovementManager(() => this.physic);
15779
15807
  this.speedScalar = 50;
15808
+ // Default speed scalar for movement
15809
+ // World Maps properties
15810
+ this.tileWidth = 32;
15811
+ this.tileHeight = 32;
15780
15812
  this.physicsAccumulatorMs = 0;
15781
15813
  this.physicsSyncDepth = 0;
15782
15814
  /**
@@ -15831,6 +15863,88 @@ class RpgCommonMap {
15831
15863
  get isStandalone() {
15832
15864
  return typeof window !== "undefined";
15833
15865
  }
15866
+ /**
15867
+ * Get the width of the map in pixels
15868
+ *
15869
+ * @returns The width of the map in pixels, or 0 if not loaded
15870
+ *
15871
+ * @example
15872
+ * ```ts
15873
+ * const width = map.widthPx;
15874
+ * console.log(`Map width: ${width}px`);
15875
+ * ```
15876
+ */
15877
+ get widthPx() {
15878
+ return this.data()?.width ?? 0;
15879
+ }
15880
+ /**
15881
+ * Get the height of the map in pixels
15882
+ *
15883
+ * @returns The height of the map in pixels, or 0 if not loaded
15884
+ *
15885
+ * @example
15886
+ * ```ts
15887
+ * const height = map.heightPx;
15888
+ * console.log(`Map height: ${height}px`);
15889
+ * ```
15890
+ */
15891
+ get heightPx() {
15892
+ return this.data()?.height ?? 0;
15893
+ }
15894
+ /**
15895
+ * Get the unique identifier of the map
15896
+ *
15897
+ * @returns The map ID, or empty string if not loaded
15898
+ *
15899
+ * @example
15900
+ * ```ts
15901
+ * const mapId = map.id;
15902
+ * console.log(`Current map: ${mapId}`);
15903
+ * ```
15904
+ */
15905
+ get id() {
15906
+ return this.data()?.id ?? "";
15907
+ }
15908
+ /**
15909
+ * Get the X position of this map in the world coordinate system
15910
+ *
15911
+ * This is used when maps are part of a larger world map. The world position
15912
+ * indicates where this map is located relative to other maps.
15913
+ *
15914
+ * @returns The X position in world coordinates, or 0 if not in a world
15915
+ *
15916
+ * @example
15917
+ * ```ts
15918
+ * const worldX = map.worldX;
15919
+ * console.log(`Map is at world position (${worldX}, ${map.worldY})`);
15920
+ * ```
15921
+ */
15922
+ get worldX() {
15923
+ const worldMaps = this.getWorldMapsManager?.();
15924
+ if (!worldMaps) return 0;
15925
+ const mapId = this.id.startsWith("map-") ? this.id.slice(4) : this.id;
15926
+ return worldMaps.getMapInfo(mapId)?.worldX ?? 0;
15927
+ }
15928
+ /**
15929
+ * Get the Y position of this map in the world coordinate system
15930
+ *
15931
+ * This is used when maps are part of a larger world map. The world position
15932
+ * indicates where this map is located relative to other maps.
15933
+ *
15934
+ * @returns The Y position in world coordinates, or 0 if not in a world
15935
+ *
15936
+ * @example
15937
+ * ```ts
15938
+ * const worldY = map.worldY;
15939
+ * console.log(`Map is at world position (${map.worldX}, ${worldY})`);
15940
+ * ```
15941
+ */
15942
+ get worldY() {
15943
+ const worldMaps = this.getWorldMapsManager?.();
15944
+ if (!worldMaps) return 0;
15945
+ const mapId = this.id.startsWith("map-") ? this.id.slice(4) : this.id;
15946
+ return worldMaps.getMapInfo(mapId)?.worldY ?? 0;
15947
+ }
15834
15948
  /**
15835
15949
  * Clear all physics content and reset to initial state
15836
15950
  *
@@ -16922,17 +17036,19 @@ class WorldMapsManager {
16922
17036
  if (typeof search === "number") {
16923
17037
  const src = map;
16924
17038
  return maps.filter((m) => {
16925
- const horizontallyOverlaps = Math.max(src.worldX, m.worldX) < Math.min(src.worldX + src.width, m.worldX + m.width);
16926
- const verticallyOverlaps = Math.max(src.worldY, m.worldY) < Math.min(src.worldY + src.height, m.worldY + m.height);
17039
+ const horizontallyOverlapsOrTouches = Math.max(src.worldX, m.worldX) <= Math.min(src.worldX + src.widthPx, m.worldX + m.widthPx);
17040
+ const verticallyOverlapsOrTouches = Math.max(src.worldY, m.worldY) <= Math.min(src.worldY + src.heightPx, m.worldY + m.heightPx);
17041
+ const marginLeftRight = src.tileWidth / 2;
17042
+ const marginTopDown = src.tileHeight / 2;
16927
17043
  switch (search) {
16928
17044
  case 0:
16929
- return verticallyOverlaps && m.worldY + m.height === src.worldY;
17045
+ return verticallyOverlapsOrTouches && m.worldY + m.heightPx - marginTopDown === src.worldY;
16930
17046
  case 1:
16931
- return verticallyOverlaps && m.worldY === src.worldY + src.height;
17047
+ return verticallyOverlapsOrTouches && m.worldY + marginTopDown === src.worldY + src.heightPx;
16932
17048
  case 2:
16933
- return horizontallyOverlaps && m.worldX + m.width === src.worldX;
17049
+ return horizontallyOverlapsOrTouches && m.worldX + m.widthPx - marginLeftRight === src.worldX;
16934
17050
  case 3:
16935
- return horizontallyOverlaps && m.worldX === src.worldX + src.width;
17051
+ return horizontallyOverlapsOrTouches && m.worldX + marginLeftRight === src.worldX + src.widthPx;
16936
17052
  default:
16937
17053
  return false;
16938
17054
  }
@@ -16940,7 +17056,7 @@ class WorldMapsManager {
16940
17056
  }
16941
17057
  if ("x" in search && "y" in search) {
16942
17058
  const found = maps.find(
16943
- (m) => search.x >= m.worldX && search.x < m.worldX + m.width && search.y >= m.worldY && search.y < m.worldY + m.height
17059
+ (m) => search.x >= m.worldX && search.x < m.worldX + m.widthPx && search.y >= m.worldY && search.y < m.worldY + m.heightPx
16944
17060
  );
16945
17061
  return found ? [found] : [];
16946
17062
  }
@@ -16948,9 +17064,9 @@ class WorldMapsManager {
16948
17064
  const { minX, minY, maxX, maxY } = search;
16949
17065
  return maps.filter((m) => {
16950
17066
  const aLeft = m.worldX;
16951
- const aRight = m.worldX + m.width;
17067
+ const aRight = m.worldX + m.widthPx;
16952
17068
  const aTop = m.worldY;
16953
- const aBottom = m.worldY + m.height;
17069
+ const aBottom = m.worldY + m.heightPx;
16954
17070
  const bLeft = minX;
16955
17071
  const bRight = maxX;
16956
17072
  const bTop = minY;
@@ -19361,8 +19477,8 @@ function WithItemManager(Base) {
19361
19477
  });
19362
19478
  }
19363
19479
  addItem(item, nb = 1) {
19364
- const map = this.getCurrentMap();
19365
- if (!map) {
19480
+ const map = this.getCurrentMap() || this.map;
19481
+ if (!map || !map.database) {
19366
19482
  throw new Error("Player must be on a map to add items");
19367
19483
  }
19368
19484
  let itemId;
@@ -19371,11 +19487,6 @@ function WithItemManager(Base) {
19371
19487
  if (isString(item)) {
19372
19488
  itemId = item;
19373
19489
  data = this.databaseById(itemId);
19374
- if (!data) {
19375
- throw new Error(
19376
- `The ID=${itemId} data is not found in the database. Add the data in the property "database"`
19377
- );
19378
- }
19379
19490
  } else if (typeof item === "function" || item.prototype) {
19380
19491
  itemId = item.name;
19381
19492
  const existingData = map.database()[itemId];
@@ -19404,6 +19515,21 @@ function WithItemManager(Base) {
19404
19515
  if (existingItem) {
19405
19516
  instance = existingItem;
19406
19517
  instance.quantity.update((it) => it + nb);
19518
+ if (data.name !== void 0) {
19519
+ instance.name.set(data.name);
19520
+ }
19521
+ if (data.description !== void 0) {
19522
+ instance.description.set(data.description);
19523
+ }
19524
+ if (data.price !== void 0) {
19525
+ instance.price.set(data.price);
19526
+ }
19527
+ if (itemInstance && typeof itemInstance === "object" && !(itemInstance instanceof Function)) {
19528
+ instance._itemInstance = itemInstance;
19529
+ if (itemInstance.onAdd) {
19530
+ instance.onAdd = itemInstance.onAdd.bind(itemInstance);
19531
+ }
19532
+ }
19407
19533
  } else {
19408
19534
  instance = new Item(data);
19409
19535
  instance.id.set(itemId);
@@ -19488,7 +19614,11 @@ function WithItemManager(Base) {
19488
19614
  getParamItem(name) {
19489
19615
  let nb = 0;
19490
19616
  for (let item of this.equipments()) {
19491
- nb += item[name] || 0;
19617
+ try {
19618
+ const itemData = this.databaseById(item.id());
19619
+ nb += itemData[name] || 0;
19620
+ } catch {
19621
+ }
19492
19622
  }
19493
19623
  const modifier = this.paramsModifier?.[name];
19494
19624
  if (modifier) {
@@ -19515,19 +19645,23 @@ function WithItemManager(Base) {
19515
19645
  if (!inventory) {
19516
19646
  throw ItemLog.notInInventory(itemId);
19517
19647
  }
19518
- const item = inventory;
19519
- if (item.consumable === false) {
19648
+ const itemData = this.databaseById(itemId);
19649
+ const consumable = itemData?.consumable;
19650
+ if (consumable === false) {
19520
19651
  throw ItemLog.notUseItem(itemId);
19521
19652
  }
19522
- const hitRate = item.hitRate ?? 1;
19523
- const hookTarget = item._itemInstance || item;
19653
+ if (consumable === void 0 && itemData?._type && itemData._type !== "item") {
19654
+ throw ItemLog.notUseItem(itemId);
19655
+ }
19656
+ const hitRate = itemData?.hitRate ?? 1;
19657
+ const hookTarget = inventory._itemInstance || inventory;
19524
19658
  if (Math.random() > hitRate) {
19525
19659
  this.removeItem(itemClass);
19526
19660
  this["execMethod"]("onUseFailed", [this], hookTarget);
19527
19661
  throw ItemLog.chanceToUseFailed(itemId);
19528
19662
  }
19529
- this.applyEffect?.(item);
19530
- this.applyStates?.(this, item);
19663
+ this.applyEffect?.(itemData);
19664
+ this.applyStates?.(this, itemData);
19531
19665
  this["execMethod"]("onUse", [this], hookTarget);
19532
19666
  this.removeItem(itemClass);
19533
19667
  return inventory;
@@ -19972,7 +20106,7 @@ const _RpgPlayer = class _RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
19972
20106
  this.map = null;
19973
20107
  this.conn = null;
19974
20108
  this.touchSide = false;
19975
- // Protection against map change loops
20109
+ this._worldPositionSignals = /* @__PURE__ */ new WeakMap();
19976
20110
  /** Internal: Shapes attached to this player */
19977
20111
  this._attachedShapes = /* @__PURE__ */ new Map();
19978
20112
  /** Internal: Shapes where this player is currently located */
@@ -20021,6 +20155,56 @@ const _RpgPlayer = class _RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
20021
20155
  }
20022
20156
  });
20023
20157
  }
20158
+ // Protection against map change loops
20159
+ /**
20160
+ * Computed signal for world X position
20161
+ *
20162
+ * Calculates the absolute world X position from the map's world position
20163
+ * plus the player's local X position. Returns 0 if no map is assigned.
20164
+ *
20165
+ * @example
20166
+ * ```ts
20167
+ * const worldX = player.worldX();
20168
+ * console.log(`Player is at world X: ${worldX}`);
20169
+ * ```
20170
+ */
20171
+ get worldPositionX() {
20172
+ return this._getComputedWorldPosition("x");
20173
+ }
20174
+ /**
20175
+ * Computed signal for world Y position
20176
+ *
20177
+ * Calculates the absolute world Y position from the map's world position
20178
+ * plus the player's local Y position. Returns 0 if no map is assigned.
20179
+ *
20180
+ * @example
20181
+ * ```ts
20182
+ * const worldY = player.worldY();
20183
+ * console.log(`Player is at world Y: ${worldY}`);
20184
+ * ```
20185
+ */
20186
+ get worldPositionY() {
20187
+ return this._getComputedWorldPosition("y");
20188
+ }
20189
+ _getComputedWorldPosition(axis) {
20190
+ if (!this._worldPositionSignals) {
20191
+ this._worldPositionSignals = /* @__PURE__ */ new WeakMap();
20192
+ }
20193
+ const key = axis;
20194
+ let signals = this._worldPositionSignals.get(this);
20195
+ if (!signals) {
20196
+ signals = {};
20197
+ this._worldPositionSignals.set(this, signals);
20198
+ }
20199
+ if (!signals[key]) {
20200
+ signals[key] = computed(() => {
20201
+ const map2 = this.map;
20202
+ const mapWorldPos = map2 ? map2[axis === "x" ? "worldX" : "worldY"] ?? 0 : 0;
20203
+ return mapWorldPos + this[axis]();
20204
+ });
20205
+ }
20206
+ return signals[key];
20207
+ }
20024
20208
  _onInit() {
20025
20209
  this.hooks.callHooks("server-playerProps-load", this).subscribe();
20026
20210
  }
@@ -20031,6 +20215,9 @@ const _RpgPlayer = class _RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
20031
20215
  get server() {
20032
20216
  return this.map;
20033
20217
  }
20218
+ setMap(map2) {
20219
+ this.map = map2;
20220
+ }
20034
20221
  applyFrames() {
20035
20222
  this._frames.set(this.frames);
20036
20223
  this.frames = [];
@@ -20080,76 +20267,55 @@ const _RpgPlayer = class _RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
20080
20267
  });
20081
20268
  return true;
20082
20269
  }
20083
- /**
20084
- * Auto change map when player touches map borders
20085
- *
20086
- * This method checks if the player touches the current map borders
20087
- * and automatically performs a change to the adjacent map if it exists.
20088
- *
20089
- * @param nextPosition - The next position of the player
20090
- * @returns Promise<boolean> - true if a map change occurred
20091
- *
20092
- * @example
20093
- * ```ts
20094
- * // Called automatically by the movement system
20095
- * const changed = await player.autoChangeMap({ x: newX, y: newY });
20096
- * if (changed) {
20097
- * console.log('Player changed map automatically');
20098
- * }
20099
- * ```
20100
- */
20101
- async autoChangeMap(nextPosition, forcedDirection) {
20270
+ async autoChangeMap(nextPosition) {
20102
20271
  const map2 = this.getCurrentMap();
20103
- if (!map2) return false;
20104
- const worldMaps = map2.getWorldMapsManager?.();
20272
+ const worldMaps = map2?.getInWorldMaps();
20105
20273
  let ret = false;
20106
20274
  if (worldMaps && map2) {
20107
- const direction = forcedDirection ?? this.getDirection();
20108
- const marginLeftRight = (map2.tileWidth ?? 32) / 2;
20109
- const marginTopDown = (map2.tileHeight ?? 32) / 2;
20110
- (map2.worldX ?? 0) + this.x();
20111
- (map2.worldY ?? 0) + this.y();
20112
- const changeMap = async (directionNumber, positionCalculator) => {
20275
+ const direction = this.getDirection();
20276
+ const marginLeftRight = map2.tileWidth / 2;
20277
+ const marginTopDown = map2.tileHeight / 2;
20278
+ const changeMap = async (adjacent, to) => {
20113
20279
  if (this.touchSide) {
20114
20280
  return false;
20115
20281
  }
20116
20282
  this.touchSide = true;
20117
- const [nextMap] = worldMaps.getAdjacentMaps(map2, directionNumber);
20118
- if (!nextMap) {
20119
- this.touchSide = false;
20120
- return false;
20121
- }
20283
+ const [nextMap] = worldMaps.getAdjacentMaps(map2, adjacent);
20284
+ if (!nextMap) return false;
20122
20285
  const id = nextMap.id;
20123
20286
  const nextMapInfo = worldMaps.getMapInfo(id);
20124
- if (!nextMapInfo) {
20125
- this.touchSide = false;
20126
- return false;
20127
- }
20128
- const newPosition = positionCalculator(nextMapInfo);
20129
- const success = await this.changeMap(id, newPosition);
20130
- setTimeout(() => {
20131
- this.touchSide = false;
20132
- }, 100);
20133
- return !!success;
20287
+ return !!await this.changeMap(id, to(nextMapInfo));
20134
20288
  };
20135
- if (nextPosition.x < marginLeftRight && direction === Direction.Left) {
20136
- ret = await changeMap(2, (nextMapInfo) => ({
20289
+ if (nextPosition.x < marginLeftRight && direction == Direction.Left) {
20290
+ ret = await changeMap({
20291
+ x: map2.worldX - 1,
20292
+ y: this.worldPositionY() + 1
20293
+ }, (nextMapInfo) => ({
20137
20294
  x: nextMapInfo.width - this.hitbox().w - marginLeftRight,
20138
- y: (map2.worldY ?? 0) - (nextMapInfo.y ?? 0) + nextPosition.y
20295
+ y: map2.worldY - nextMapInfo.y + nextPosition.y
20139
20296
  }));
20140
- } else if (nextPosition.x > map2.widthPx - this.hitbox().w - marginLeftRight && direction === Direction.Right) {
20141
- ret = await changeMap(3, (nextMapInfo) => ({
20297
+ } else if (nextPosition.x > map2.widthPx - this.hitbox().w - marginLeftRight && direction == Direction.Right) {
20298
+ ret = await changeMap({
20299
+ x: map2.worldX + map2.widthPx + 1,
20300
+ y: this.worldPositionY() + 1
20301
+ }, (nextMapInfo) => ({
20142
20302
  x: marginLeftRight,
20143
- y: (map2.worldY ?? 0) - (nextMapInfo.y ?? 0) + nextPosition.y
20303
+ y: map2.worldY - nextMapInfo.y + nextPosition.y
20144
20304
  }));
20145
- } else if (nextPosition.y < marginTopDown && direction === Direction.Up) {
20146
- ret = await changeMap(0, (nextMapInfo) => ({
20147
- x: (map2.worldX ?? 0) - (nextMapInfo.x ?? 0) + nextPosition.x,
20305
+ } else if (nextPosition.y < marginTopDown && direction == Direction.Up) {
20306
+ ret = await changeMap({
20307
+ x: this.worldPositionX() + 1,
20308
+ y: map2.worldY - 1
20309
+ }, (nextMapInfo) => ({
20310
+ x: map2.worldX - nextMapInfo.x + nextPosition.x,
20148
20311
  y: nextMapInfo.height - this.hitbox().h - marginTopDown
20149
20312
  }));
20150
- } else if (nextPosition.y > map2.heightPx - this.hitbox().h - marginTopDown && direction === Direction.Down) {
20151
- ret = await changeMap(1, (nextMapInfo) => ({
20152
- x: (map2.worldX ?? 0) - (nextMapInfo.x ?? 0) + nextPosition.x,
20313
+ } else if (nextPosition.y > map2.heightPx - this.hitbox().h - marginTopDown && direction == Direction.Down) {
20314
+ ret = await changeMap({
20315
+ x: this.worldPositionX() + 1,
20316
+ y: map2.worldY + map2.heightPx + 1
20317
+ }, (nextMapInfo) => ({
20318
+ x: map2.worldX - nextMapInfo.x + nextPosition.x,
20153
20319
  y: marginTopDown
20154
20320
  }));
20155
20321
  } else {
@@ -20160,15 +20326,14 @@ const _RpgPlayer = class _RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
20160
20326
  }
20161
20327
  async teleport(positions) {
20162
20328
  if (!this.map) return false;
20163
- if (this.map.physic) {
20329
+ if (this.map && this.map.physic) {
20164
20330
  const entity = this.map.physic.getEntityByUUID(this.id);
20165
20331
  if (entity) {
20166
20332
  this.map.physic.teleport(entity, { x: positions.x, y: positions.y });
20167
20333
  }
20168
- } else {
20169
- this.x.set(positions.x);
20170
- this.y.set(positions.y);
20171
20334
  }
20335
+ this.x.set(positions.x);
20336
+ this.y.set(positions.y);
20172
20337
  queueMicrotask(() => {
20173
20338
  this.applyFrames();
20174
20339
  });
@@ -20250,8 +20415,8 @@ const _RpgPlayer = class _RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
20250
20415
  this._eventChanges();
20251
20416
  }
20252
20417
  databaseById(id) {
20253
- const map2 = this.getCurrentMap();
20254
- if (!map2) return;
20418
+ const map2 = this.map;
20419
+ if (!map2 || !map2.database) return;
20255
20420
  const data = map2.database()[id];
20256
20421
  if (!data)
20257
20422
  throw new Error(
@@ -24318,6 +24483,99 @@ function superRefine(fn) {
24318
24483
  return _superRefine(fn);
24319
24484
  }
24320
24485
 
24486
+ class BaseRoom {
24487
+ constructor() {
24488
+ /**
24489
+ * Signal containing the room's database of items, classes, and other game data
24490
+ *
24491
+ * This database can be dynamically populated using `addInDatabase()` and
24492
+ * `removeInDatabase()` methods. It's used to store game entities like items,
24493
+ * classes, skills, etc. that are available in this room.
24494
+ *
24495
+ * @example
24496
+ * ```ts
24497
+ * // Add data to database
24498
+ * room.addInDatabase('Potion', PotionClass);
24499
+ *
24500
+ * // Access database
24501
+ * const potion = room.database()['Potion'];
24502
+ * ```
24503
+ */
24504
+ this.database = signal({});
24505
+ }
24506
+ /**
24507
+ * Add data to the room's database
24508
+ *
24509
+ * Adds an item, class, or other game entity to the room's database.
24510
+ * If the ID already exists and `force` is not enabled, the addition is ignored.
24511
+ *
24512
+ * ## Architecture
24513
+ *
24514
+ * This method is used by the item management system to store item definitions
24515
+ * in the room's database. When a player adds an item, the system first checks
24516
+ * if the item exists in the database, and if not, adds it using this method.
24517
+ *
24518
+ * @param id - Unique identifier for the data
24519
+ * @param data - The data to add (can be a class, object, etc.)
24520
+ * @param options - Optional configuration
24521
+ * @param options.force - If true, overwrites existing data with the same ID
24522
+ * @returns `true` if data was added, `false` if it was ignored (ID already exists)
24523
+ *
24524
+ * @example
24525
+ * ```ts
24526
+ * // Add a class to the database
24527
+ * room.addInDatabase('Potion', PotionClass);
24528
+ *
24529
+ * // Add an item object to the database
24530
+ * room.addInDatabase('custom-item', {
24531
+ * name: 'Custom Item',
24532
+ * price: 100
24533
+ * });
24534
+ *
24535
+ * // Force overwrite existing data
24536
+ * room.addInDatabase('Potion', UpdatedPotionClass, { force: true });
24537
+ * ```
24538
+ */
24539
+ addInDatabase(id, data, options) {
24540
+ const database = this.database();
24541
+ if (database[id] !== void 0 && !options?.force) {
24542
+ return false;
24543
+ }
24544
+ database[id] = data;
24545
+ this.database.set(database);
24546
+ return true;
24547
+ }
24548
+ /**
24549
+ * Remove data from the room's database
24550
+ *
24551
+ * This method allows you to remove items or data from the room's database.
24552
+ *
24553
+ * @param id - Unique identifier of the data to remove
24554
+ * @returns `true` if data was removed, `false` if ID didn't exist
24555
+ *
24556
+ * @example
24557
+ * ```ts
24558
+ * // Remove an item from the database
24559
+ * room.removeInDatabase('Potion');
24560
+ *
24561
+ * // Check if removal was successful
24562
+ * const removed = room.removeInDatabase('custom-item');
24563
+ * if (removed) {
24564
+ * console.log('Item removed successfully');
24565
+ * }
24566
+ * ```
24567
+ */
24568
+ removeInDatabase(id) {
24569
+ const database = this.database();
24570
+ if (database[id] === void 0) {
24571
+ return false;
24572
+ }
24573
+ delete database[id];
24574
+ this.database.set(database);
24575
+ return true;
24576
+ }
24577
+ }
24578
+
24321
24579
  var __defProp$1 = Object.defineProperty;
24322
24580
  var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
24323
24581
  var __decorateClass$1 = (decorators, target, key, kind) => {
@@ -24341,7 +24599,7 @@ const MapUpdateSchema = object({
24341
24599
  height: number()
24342
24600
  });
24343
24601
  let RpgMap = class extends RpgCommonMap {
24344
- constructor() {
24602
+ constructor(room) {
24345
24603
  super();
24346
24604
  this.players = signal({});
24347
24605
  this.events = signal({});
@@ -24418,9 +24676,18 @@ let RpgMap = class extends RpgCommonMap {
24418
24676
  this._shapeEntities = /* @__PURE__ */ new Map();
24419
24677
  /** Enable/disable automatic tick processing (useful for unit tests) */
24420
24678
  this._autoTickEnabled = true;
24679
+ this.autoSync = true;
24421
24680
  this.hooks.callHooks("server-map-onStart", this).subscribe();
24422
- this.throttleSync = this.isStandalone ? 0 : 50;
24423
- this.throttleStorage = this.isStandalone ? 0 : 1e3;
24681
+ const isTest = room.env.TEST === "true" ? true : false;
24682
+ if (isTest) {
24683
+ this.autoSync = false;
24684
+ this.setAutoTick(false);
24685
+ this.throttleSync = 0;
24686
+ this.throttleStorage = 0;
24687
+ } else {
24688
+ this.throttleSync = this.isStandalone ? 1 : 50;
24689
+ this.throttleStorage = this.isStandalone ? 1 : 50;
24690
+ }
24424
24691
  this.sessionExpiryTime = 1e3 * 60 * 5;
24425
24692
  this.setupCollisionDetection();
24426
24693
  if (this._autoTickEnabled) {
@@ -24617,7 +24884,11 @@ let RpgMap = class extends RpgCommonMap {
24617
24884
  * ```
24618
24885
  */
24619
24886
  onJoin(player, conn) {
24620
- player.map = this;
24887
+ if (player.setMap) {
24888
+ player.setMap(this);
24889
+ } else {
24890
+ player.map = this;
24891
+ }
24621
24892
  player.context = context$1;
24622
24893
  player.conn = conn;
24623
24894
  player._onInit();
@@ -24683,85 +24954,8 @@ let RpgMap = class extends RpgCommonMap {
24683
24954
  get hooks() {
24684
24955
  return inject$1(context$1, ModulesToken);
24685
24956
  }
24686
- /**
24687
- * Get the width of the map in pixels
24688
- *
24689
- * @returns The width of the map in pixels, or 0 if not loaded
24690
- *
24691
- * @example
24692
- * ```ts
24693
- * const width = map.widthPx;
24694
- * console.log(`Map width: ${width}px`);
24695
- * ```
24696
- */
24697
- get widthPx() {
24698
- return this.data()?.width ?? 0;
24699
- }
24700
- /**
24701
- * Get the height of the map in pixels
24702
- *
24703
- * @returns The height of the map in pixels, or 0 if not loaded
24704
- *
24705
- * @example
24706
- * ```ts
24707
- * const height = map.heightPx;
24708
- * console.log(`Map height: ${height}px`);
24709
- * ```
24710
- */
24711
- get heightPx() {
24712
- return this.data()?.height ?? 0;
24713
- }
24714
- /**
24715
- * Get the unique identifier of the map
24716
- *
24717
- * @returns The map ID, or empty string if not loaded
24718
- *
24719
- * @example
24720
- * ```ts
24721
- * const mapId = map.id;
24722
- * console.log(`Current map: ${mapId}`);
24723
- * ```
24724
- */
24725
- get id() {
24726
- return this.data()?.id ?? "";
24727
- }
24728
- /**
24729
- * Get the X position of this map in the world coordinate system
24730
- *
24731
- * This is used when maps are part of a larger world map. The world position
24732
- * indicates where this map is located relative to other maps.
24733
- *
24734
- * @returns The X position in world coordinates, or 0 if not in a world
24735
- *
24736
- * @example
24737
- * ```ts
24738
- * const worldX = map.worldX;
24739
- * console.log(`Map is at world position (${worldX}, ${map.worldY})`);
24740
- * ```
24741
- */
24742
- get worldX() {
24743
- const worldMaps = this.getWorldMapsManager?.();
24744
- return worldMaps?.getMapInfo(this.id)?.worldX ?? 0;
24745
- }
24746
- /**
24747
- * Get the Y position of this map in the world coordinate system
24748
- *
24749
- * This is used when maps are part of a larger world map. The world position
24750
- * indicates where this map is located relative to other maps.
24751
- *
24752
- * @returns The Y position in world coordinates, or 0 if not in a world
24753
- *
24754
- * @example
24755
- * ```ts
24756
- * const worldY = map.worldY;
24757
- * console.log(`Map is at world position (${map.worldX}, ${worldY})`);
24758
- * ```
24759
- */
24760
- get worldY() {
24761
- const worldMaps = this.getWorldMapsManager?.();
24762
- return worldMaps?.getMapInfo(this.id)?.worldY ?? 0;
24763
- }
24764
24957
  guiInteraction(player, value) {
24958
+ this.hooks.callHooks("server-player-guiInteraction", player, value);
24765
24959
  player.syncChanges();
24766
24960
  }
24767
24961
  guiExit(player, { guiId, data }) {
@@ -24804,6 +24998,7 @@ let RpgMap = class extends RpgCommonMap {
24804
24998
  };
24805
24999
  await lastValueFrom(this.hooks.callHooks("server-maps-load", this));
24806
25000
  await lastValueFrom(this.hooks.callHooks("server-worldMaps-load", this));
25001
+ await lastValueFrom(this.hooks.callHooks("server-databaseHooks-load", this));
24807
25002
  map.events = map.events ?? [];
24808
25003
  if (map.id) {
24809
25004
  const mapFound = this.maps.find((m) => m.id === map.id);
@@ -25151,8 +25346,7 @@ let RpgMap = class extends RpgCommonMap {
25151
25346
  /**
25152
25347
  * Add data to the map's database
25153
25348
  *
25154
- * This method allows you to dynamically add items, classes, or any data to the map's database.
25155
- * By default, if an ID already exists, the operation is ignored to prevent overwriting existing data.
25349
+ * This method delegates to BaseRoom's implementation to avoid code duplication.
25156
25350
  *
25157
25351
  * @param id - Unique identifier for the data
25158
25352
  * @param data - The data to store (can be a class, object, or any value)
@@ -25176,17 +25370,12 @@ let RpgMap = class extends RpgCommonMap {
25176
25370
  * ```
25177
25371
  */
25178
25372
  addInDatabase(id, data, options) {
25179
- const database = this.database();
25180
- if (database[id] !== void 0 && !options?.force) {
25181
- return false;
25182
- }
25183
- database[id] = data;
25184
- return true;
25373
+ return BaseRoom.prototype.addInDatabase.call(this, id, data, options);
25185
25374
  }
25186
25375
  /**
25187
25376
  * Remove data from the map's database
25188
25377
  *
25189
- * This method allows you to remove items or data from the map's database.
25378
+ * This method delegates to BaseRoom's implementation to avoid code duplication.
25190
25379
  *
25191
25380
  * @param id - Unique identifier of the data to remove
25192
25381
  * @returns true if data was removed, false if ID didn't exist
@@ -25204,12 +25393,7 @@ let RpgMap = class extends RpgCommonMap {
25204
25393
  * ```
25205
25394
  */
25206
25395
  removeInDatabase(id) {
25207
- const database = this.database();
25208
- if (database[id] === void 0) {
25209
- return false;
25210
- }
25211
- delete database[id];
25212
- return true;
25396
+ return BaseRoom.prototype.removeInDatabase.call(this, id);
25213
25397
  }
25214
25398
  /**
25215
25399
  * Creates a dynamic event on the map
@@ -25577,6 +25761,19 @@ let RpgMap = class extends RpgCommonMap {
25577
25761
  }, holder);
25578
25762
  }
25579
25763
  }
25764
+ /**
25765
+ * Apply sync to the client
25766
+ *
25767
+ * This method applies sync to the client by calling the `$applySync()` method.
25768
+ *
25769
+ * @example
25770
+ * ```ts
25771
+ * map.applySyncToClient();
25772
+ * ```
25773
+ */
25774
+ applySyncToClient() {
25775
+ this.$applySync();
25776
+ }
25580
25777
  /**
25581
25778
  * Create a shape dynamically on the map
25582
25779
  *
@@ -25983,9 +26180,15 @@ var __decorateClass = (decorators, target, key, kind) => {
25983
26180
  if (kind && result) __defProp(target, key, result);
25984
26181
  return result;
25985
26182
  };
25986
- let LobbyRoom = class {
25987
- constructor() {
26183
+ let LobbyRoom = class extends BaseRoom {
26184
+ constructor(room) {
26185
+ super();
25988
26186
  this.players = signal({});
26187
+ this.autoSync = true;
26188
+ const isTest = room.env.TEST === "true" ? true : false;
26189
+ if (isTest) {
26190
+ this.autoSync = false;
26191
+ }
25989
26192
  }
25990
26193
  onJoin(player, conn) {
25991
26194
  player.map = this;
@@ -26338,6 +26541,19 @@ function provideServerModules(modules) {
26338
26541
  }
26339
26542
  };
26340
26543
  }
26544
+ if (module.database && typeof module.database === "object") {
26545
+ const database = { ...module.database };
26546
+ module = {
26547
+ ...module,
26548
+ databaseHooks: {
26549
+ load: (engine) => {
26550
+ for (const key in database) {
26551
+ engine.addInDatabase(key, database[key]);
26552
+ }
26553
+ }
26554
+ }
26555
+ };
26556
+ }
26341
26557
  return module;
26342
26558
  });
26343
26559
  return modules2;