h1z1-server 0.47.2-0 → 0.47.2-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "h1z1-server",
3
- "version": "0.47.2-0",
3
+ "version": "0.47.2-1",
4
4
  "description": "Library for emulating h1z1 servers",
5
5
  "author": "Quentin Gruber <quentingruber@gmail.com> (http://github.com/quentingruber)",
6
6
  "license": "GPL-3.0-only",
@@ -2221,10 +2221,14 @@ export const basePackets: PacketStructures = [
2221
2221
  ],
2222
2222
  ["ClientMetrics", 0x45, {}],
2223
2223
  [
2224
- "Command.TogglePlayerInterfaces",
2224
+ "FirstTimeEvent.NotifySystem",
2225
2225
  0x4601,
2226
2226
  {
2227
- fields: []
2227
+ fields: [
2228
+ { name: "unknownDword1", type: "int32", defaultValue: 0 },
2229
+ { name: "unknownBoolean1", type: "boolean", defaultValue: false },
2230
+ { name: "displayElement", type: "int32", defaultValue: 0 }
2231
+ ]
2228
2232
  }
2229
2233
  ],
2230
2234
  [
@@ -273,7 +273,7 @@ const weaponDefinitionSchema: PacketFields = [
273
273
  defaultValue: 0
274
274
  },
275
275
  { name: "FIRE_DETECT_RANGE", type: "uint16", defaultValue: 0 },
276
- { name: "EFFECT_GROUP", type: "float", defaultValue: 0 },
276
+ { name: "EFFECT_GROUP", type: "uint32", defaultValue: 0 },
277
277
  { name: "PLAYER_STATE_GROUP_ID", type: "float", defaultValue: 0 },
278
278
  { name: "MOVEMENT_MODIFIER", type: "float", defaultValue: 0 },
279
279
  { name: "TURN_MODIFIER", type: "float", defaultValue: 0 },
@@ -21,7 +21,7 @@ import { SOEOutputChannels } from "servers/SoeServer/soeoutputstream";
21
21
  const debug = require("debug")("GatewayServer");
22
22
 
23
23
  export class GatewayServer extends EventEmitter {
24
- private _soeServer: SOEServer;
24
+ public _soeServer: SOEServer;
25
25
  private _protocol: GatewayProtocol;
26
26
  private _crcLength: crc_length_options;
27
27
  private _udpLength: number;
@@ -40,6 +40,7 @@ export default class SOEClient {
40
40
  waitingQueue: PacketsQueue = new PacketsQueue();
41
41
  protocolName: string = "unset";
42
42
  unAckData: Map<number, number> = new Map();
43
+ lostPackets: number[] = [];
43
44
  lastAckSend: wrappedUint16 = new wrappedUint16(-1);
44
45
  inputStream: SOEInputStream;
45
46
  outputStream: SOEOutputStream;
@@ -58,6 +59,8 @@ export default class SOEClient {
58
59
  sendingTimer: NodeJS.Timeout | null = null;
59
60
  private _statsResetTimer: NodeJS.Timer;
60
61
  delayedLogicalPackets: LogicalPacket[] = [];
62
+ finishedLoading: boolean = false;
63
+ statsResettedRecently: boolean = false;
61
64
  constructor(remote: RemoteInfo, crcSeed: number, cryptoKey: Uint8Array) {
62
65
  this.soeClientId = SOEClient.getClientId(remote);
63
66
  this.address = remote.address;
@@ -71,7 +74,7 @@ export default class SOEClient {
71
74
  } else {
72
75
  this.outputStream = new SOEOutputStream(cryptoKey);
73
76
  }
74
- this._statsResetTimer = setInterval(() => this._resetStats(), 60000);
77
+ this._statsResetTimer = setInterval(() => this._resetStats(), 10000);
75
78
  }
76
79
  static getClientId(remote: RemoteInfo): string {
77
80
  return remote.address + ":" + remote.port;
@@ -80,26 +83,78 @@ export default class SOEClient {
80
83
  // wierd stuff with the new global Symbol used with the using keyword, skipping that headache for now
81
84
  clearInterval(this._statsResetTimer as unknown as number);
82
85
  }
86
+ private getDynamicWaitTime(): number {
87
+ if (!this.finishedLoading) return 30;
88
+
89
+ const ping = this.avgPing || 0;
90
+ const minPing = 50;
91
+ const maxPing = 250;
92
+ const minWait = 4;
93
+ const maxWait = 12;
94
+
95
+ // Calculate ratio (0–1) and clamp to range
96
+ const ratio = Math.max(
97
+ 0,
98
+ Math.min(1, (ping - minPing) / (maxPing - minPing))
99
+ );
100
+
101
+ // Calculate wait time in the range 4–12 ms
102
+ const waitTime = minWait + ratio * (maxWait - minWait);
103
+
104
+ return Math.round(waitTime);
105
+ }
83
106
  private _resetStats() {
107
+ // Reset network statistics and set a temporary flag
84
108
  this.stats.totalPhysicalPacketSent = 0;
85
109
  this.stats.packetsOutOfOrder = 0;
86
110
  this.stats.packetResend = 0;
87
111
  this.stats.totalLogicalPacketSent = 0;
112
+ this.statsResettedRecently = true;
113
+ setTimeout(() => {
114
+ this.statsResettedRecently = false;
115
+ }, 5000);
116
+ }
117
+
118
+ seqDiff(a: number, b: number): number {
119
+ // Difference with uint16 (65536) overflow handling
120
+ return (a - b + 65536) % 65536;
121
+ }
122
+
123
+ getNetworkQuality(): number {
124
+ const lastAckSeq = this.outputStream.lastAck.get();
125
+ let packetLoss = 0;
126
+
127
+ // Count lost packets within the last 100 sequence numbers
128
+ for (const sequence of this.lostPackets) {
129
+ if (this.seqDiff(lastAckSeq, sequence) <= 100) {
130
+ packetLoss++;
131
+ }
132
+ }
133
+
134
+ return packetLoss;
88
135
  }
89
136
  getNetworkStats(): string[] {
90
- const {
91
- totalPhysicalPacketSent: totalPacketSent,
92
- packetResend,
93
- packetsOutOfOrder
94
- } = this.stats;
95
- const packetLossRate =
96
- Number((packetResend / totalPacketSent).toFixed(3)) * 100;
137
+ const { totalLogicalPacketSent, packetResend, packetsOutOfOrder } =
138
+ this.stats;
139
+ const totalPackets = totalLogicalPacketSent + packetResend;
140
+
97
141
  const packetOutOfOrderRate =
98
- Number((packetsOutOfOrder / totalPacketSent).toFixed(3)) * 100;
142
+ totalPackets > 0
143
+ ? Number(((packetsOutOfOrder / totalPackets) * 100).toFixed(1))
144
+ : 0;
145
+
146
+ const lastAckSeq = this.outputStream.lastAck.get();
147
+ let packetLoss = 0;
148
+
149
+ for (const sequence of this.lostPackets) {
150
+ if (this.seqDiff(lastAckSeq, sequence) <= 100) packetLoss++;
151
+ }
152
+
99
153
  return [
100
- `Packet loss rate ${packetLossRate}%`,
101
- `Packet outOfOrder rate ${packetOutOfOrderRate}%`,
102
- `Avg ping ${this.avgPing}ms`
154
+ `Packet loss rate: ${packetLoss}%`,
155
+ `Out-of-order rate: ${packetOutOfOrderRate}%`,
156
+ `Avg ping: ${this.avgPing}ms`,
157
+ `Wait Time: ${this.getDynamicWaitTime().toFixed(2)}ms`
103
158
  ];
104
159
  }
105
160
  addPing(ping: number) {
@@ -35,8 +35,8 @@ export class SOEServer extends EventEmitter {
35
35
  private _connectionv6: dgram.Socket;
36
36
  private readonly _crcSeed: number = Math.floor(Math.random() * 255);
37
37
  private _crcLength: crc_length_options = 2;
38
- _waitTimeMs: number = 24;
39
- keepAliveTimeoutTime: number = 40000;
38
+ _waitTimeMs: number = 2;
39
+ keepAliveTimeoutTime: number = 30000;
40
40
  private readonly _maxMultiBufferSize: number;
41
41
  private _resendTimeout: number = 250;
42
42
  private _maxResentTries: number = 24;
@@ -95,6 +95,25 @@ export class SOEServer extends EventEmitter {
95
95
  }
96
96
  }
97
97
 
98
+ private getDynamicWaitTime(client: SOEClient): number {
99
+ if (!client.finishedLoading) return 30;
100
+
101
+ const ping = client.avgPing || 0;
102
+ const minPing = 50;
103
+ const maxPing = 250;
104
+ const minWait = 4;
105
+ const maxWait = 12;
106
+
107
+ // Calculate ratio (0–1) and clamp to range
108
+ const ratio = Math.max(
109
+ 0,
110
+ Math.min(1, (ping - minPing) / (maxPing - minPing))
111
+ );
112
+
113
+ // Calculate wait time in the range 4–12 ms
114
+ return Math.round(minWait + ratio * (maxWait - minWait));
115
+ }
116
+
98
117
  getNetworkStats() {
99
118
  const avgServerLag =
100
119
  this.avgEventLoopLag > 1
@@ -136,48 +155,50 @@ export class SOEServer extends EventEmitter {
136
155
  }
137
156
  }
138
157
 
139
- // Get an array of packet that we need to resend
158
+ // Get an array of packets that need to be resent
140
159
  getResends(client: Client): LogicalPacket[] {
141
160
  const currentTime = Date.now();
142
161
  const resends: LogicalPacket[] = [];
143
- const resendedSequences: Set<number> = new Set();
162
+ const resendedSequences = new Set<number>();
163
+ const lastAck = client.outputStream.lastAck.get();
164
+
165
+ // Helper to calculate resend timeout
166
+ const getResendTimeout = () =>
167
+ Math.min(Math.max(client.avgPing + this._resendTimeout, 100), 400);
168
+
169
+ // Track lost packets for this round to avoid duplicates
170
+ const lostPacketsSet = new Set(client.lostPackets);
171
+
144
172
  for (const [sequence, time] of client.unAckData) {
145
- // if the packet is too old then we resend it
146
- if (time + this._resendTimeout + client.avgPing < currentTime) {
173
+ if (time + getResendTimeout() < currentTime) {
147
174
  const dataCache = client.outputStream.getDataCache(sequence);
148
175
  if (dataCache) {
149
- if (dataCache.resendCounter >= this._maxResentTries) {
150
- continue;
151
- }
176
+ if (dataCache.resendCounter >= this._maxResentTries) continue;
152
177
  dataCache.resendCounter++;
153
178
  client.stats.packetResend++;
154
179
  const logicalPacket = this.createLogicalPacket(
155
180
  dataCache.fragment ? SoeOpcode.DataFragment : SoeOpcode.Data,
156
- { sequence: sequence, data: dataCache.data }
181
+ { sequence, data: dataCache.data }
157
182
  );
158
183
  if (logicalPacket) {
159
184
  resendedSequences.add(sequence);
160
185
  resends.push(logicalPacket);
186
+ if (
187
+ logicalPacket.sequence !== undefined &&
188
+ !lostPacketsSet.has(logicalPacket.sequence)
189
+ ) {
190
+ client.lostPackets.push(logicalPacket.sequence);
191
+ lostPacketsSet.add(logicalPacket.sequence);
192
+ }
161
193
  }
162
- } else {
163
- // If the data cache is not found it means that the packet has been acked
164
194
  }
165
195
  }
166
196
  }
167
197
 
168
- // check for possible accerated resends
198
+ // Accelerated resends for out-of-order packets
169
199
  for (const sequence of client.outputStream.outOfOrder) {
170
- if (sequence < client.outputStream.lastAck.get()) {
171
- continue;
172
- }
173
-
174
- // resend every packets between the last ack and the out of order packet
175
- for (
176
- let index = client.outputStream.lastAck.get();
177
- index < sequence;
178
- index++
179
- ) {
180
- // If that sequence has been out of order acked or resended then we don't resend it again
200
+ if (sequence < lastAck) continue;
201
+ for (let index = lastAck; index < sequence; index++) {
181
202
  if (
182
203
  client.outputStream.outOfOrder.has(index) ||
183
204
  resendedSequences.has(index)
@@ -193,16 +214,13 @@ export class SOEServer extends EventEmitter {
193
214
  if (logicalPacket) {
194
215
  resendedSequences.add(index);
195
216
  resends.push(logicalPacket);
217
+ client.stats.packetsOutOfOrder++;
196
218
  }
197
- } else {
198
- // well if it's not in the cache then it means that it has been acked
199
219
  }
200
220
  }
201
221
  }
202
222
 
203
- // clear out of order array
204
223
  client.outputStream.outOfOrder.clear();
205
-
206
224
  return resends;
207
225
  }
208
226
 
@@ -296,9 +314,10 @@ export class SOEServer extends EventEmitter {
296
314
  // activate the sending timer if it's not already activated
297
315
  private _activateSendingTimer(client: SOEClient, additonalTime: number = 0) {
298
316
  if (!client.sendingTimer) {
317
+ const waitTime = this.getDynamicWaitTime(client) + additonalTime;
299
318
  client.sendingTimer = setTimeout(() => {
300
319
  this.sendingProcess(client);
301
- }, this._waitTimeMs + additonalTime);
320
+ }, waitTime);
302
321
  }
303
322
  }
304
323
 
@@ -376,7 +395,6 @@ export class SOEServer extends EventEmitter {
376
395
  );
377
396
  break;
378
397
  case "OutOfOrder":
379
- client.stats.packetsOutOfOrder++;
380
398
  client.outputStream.outOfOrder.add(packet.sequence);
381
399
  //client.outputStream.singleAck(packet.sequence, client.unAckData)
382
400
  break;
@@ -567,18 +585,17 @@ export class SOEServer extends EventEmitter {
567
585
  return appPackets;
568
586
  }
569
587
  private sendingProcess(client: Client) {
570
- // If there is a pending sending timer then we clear it
588
+ // Clear any pending sending timer
571
589
  this._clearSendingTimer(client);
572
- if (client.isDeleted) {
573
- return;
574
- }
590
+ if (client.isDeleted) return;
591
+
575
592
  const resends = this.getResends(client);
576
593
  for (const resend of resends) {
577
594
  client.stats.totalLogicalPacketSent++;
578
595
  if (this._canBeBufferedIntoQueue(resend, client.waitingQueue)) {
579
596
  client.waitingQueue.addPacket(resend);
580
597
  } else {
581
- const waitingQueuePacket = this.getClientWaitQueuePacket(
598
+ let waitingQueuePacket = this.getClientWaitQueuePacket(
582
599
  client,
583
600
  client.waitingQueue
584
601
  );
@@ -588,7 +605,6 @@ export class SOEServer extends EventEmitter {
588
605
  if (this._canBeBufferedIntoQueue(resend, client.waitingQueue)) {
589
606
  client.waitingQueue.addPacket(resend);
590
607
  } else {
591
- // if it still can't be buffered it means that the packet is too big so we send it directly
592
608
  this._sendAndBuildPhysicalPacket(client, resend);
593
609
  }
594
610
  }
@@ -599,55 +615,48 @@ export class SOEServer extends EventEmitter {
599
615
  client.delayedLogicalPackets.push(...appPackets);
600
616
  }
601
617
 
602
- if (client.delayedLogicalPackets.length > 0) {
603
- for (
604
- let index = 0;
605
- index < client.delayedLogicalPackets.length;
606
- index++
607
- ) {
608
- const packet = client.delayedLogicalPackets.shift();
609
- if (!packet) {
610
- break;
618
+ // Process delayed logical packets
619
+ while (client.delayedLogicalPackets.length > 0) {
620
+ const packet = client.delayedLogicalPackets.shift();
621
+ if (!packet) break;
622
+ if (this._canBeBufferedIntoQueue(packet, client.waitingQueue)) {
623
+ client.waitingQueue.addPacket(packet);
624
+ } else {
625
+ let waitingQueuePacket = this.getClientWaitQueuePacket(
626
+ client,
627
+ client.waitingQueue
628
+ );
629
+ if (waitingQueuePacket) {
630
+ this._sendAndBuildPhysicalPacket(client, waitingQueuePacket);
611
631
  }
612
632
  if (this._canBeBufferedIntoQueue(packet, client.waitingQueue)) {
613
633
  client.waitingQueue.addPacket(packet);
614
634
  } else {
615
- // sends the already buffered packets
616
- const waitingQueuePacket = this.getClientWaitQueuePacket(
617
- client,
618
- client.waitingQueue
619
- );
620
- if (waitingQueuePacket) {
621
- this._sendAndBuildPhysicalPacket(client, waitingQueuePacket);
622
- }
623
- if (this._canBeBufferedIntoQueue(packet, client.waitingQueue)) {
624
- client.waitingQueue.addPacket(packet);
625
- } else {
626
- // if it still can't be buffered it means that the packet is too big so we send it directly
627
- this._sendAndBuildPhysicalPacket(client, packet);
628
- }
635
+ this._sendAndBuildPhysicalPacket(client, packet);
629
636
  }
630
637
  }
631
638
  }
639
+ // Handle ack packet
632
640
  const ackPacket = this.getAck(client);
633
641
  if (ackPacket) {
634
642
  client.stats.totalLogicalPacketSent++;
635
643
  if (this._canBeBufferedIntoQueue(ackPacket, client.waitingQueue)) {
636
644
  client.waitingQueue.addPacket(ackPacket);
637
645
  } else {
638
- const waitingQueuePacket = this.getClientWaitQueuePacket(
646
+ let waitingQueuePacket = this.getClientWaitQueuePacket(
639
647
  client,
640
648
  client.waitingQueue
641
649
  );
642
650
  if (waitingQueuePacket) {
643
651
  this._sendAndBuildPhysicalPacket(client, waitingQueuePacket);
644
652
  }
645
- // no additionnal check needed here because ack packets have a fixed size
653
+ // Ack packets are always small enough to fit after clearing
646
654
  client.waitingQueue.addPacket(ackPacket);
647
655
  }
648
656
  }
649
- // if there is still some packets in the queue then we send them
650
- const waitingQueuePacket = this.getClientWaitQueuePacket(
657
+
658
+ // Send any remaining packets in the waiting queue
659
+ let waitingQueuePacket = this.getClientWaitQueuePacket(
651
660
  client,
652
661
  client.waitingQueue
653
662
  );
@@ -655,6 +664,11 @@ export class SOEServer extends EventEmitter {
655
664
  this._sendAndBuildPhysicalPacket(client, waitingQueuePacket);
656
665
  }
657
666
 
667
+ // Trim lostPackets to last 100 if needed
668
+ if (client.lostPackets.length > 100) {
669
+ client.lostPackets.splice(0, client.lostPackets.length - 100);
670
+ }
671
+
658
672
  if (client.unAckData.size > 0) {
659
673
  this._activateSendingTimer(client);
660
674
  }
@@ -83,6 +83,7 @@ export class ZoneClient2016 {
83
83
  avgPingLen: number = 4;
84
84
  pingWarnings: number = 0;
85
85
  isWeaponLock: boolean = false;
86
+ lastMovementImpared: number = 0;
86
87
  avgPingReady: boolean = false;
87
88
  chunkRenderDistance: number = 400;
88
89
  routineCounter: number = 0;
@@ -48,7 +48,7 @@ function getRenderDistance(actorModelId: number) {
48
48
  range = 1000;
49
49
  break;
50
50
  }
51
- return range ? range : undefined;
51
+ return range ? range : 1000;
52
52
  }
53
53
 
54
54
  export abstract class BaseEntity {
@@ -944,20 +944,29 @@ export abstract class BaseFullCharacter extends BaseLightweightCharacter {
944
944
 
945
945
  pGetAttachmentSlot(slotId: number) {
946
946
  const slot = this._equipment[slotId];
947
- return slot
948
- ? {
949
- modelName: slot.modelName.replace(
950
- /Up|Down/g,
951
- this.hoodState == "Down" ? "Up" : "Down"
952
- ),
953
- effectId: slot.effectId || 0,
954
- textureAlias: slot.textureAlias || "",
955
- tintAlias: slot.tintAlias || "Default",
956
- decalAlias: slot.decalAlias || "#",
957
- slotId: slot.slotId,
958
- SHADER_PARAMETER_GROUP: slot?.SHADER_PARAMETER_GROUP ?? []
959
- }
960
- : undefined;
947
+ if (!slot) return undefined;
948
+
949
+ const shaderGroup = slot.SHADER_PARAMETER_GROUP ?? [];
950
+ let shaderParams = shaderGroup;
951
+
952
+ if (slotId == 3 && shaderGroup.length == 4) {
953
+ shaderParams =
954
+ this.hoodState == "Down"
955
+ ? [shaderGroup[0], shaderGroup[1]]
956
+ : [shaderGroup[2], shaderGroup[3]];
957
+ }
958
+
959
+ const hoodReplace = this.hoodState == "Down" ? "Up" : "Down";
960
+
961
+ return {
962
+ modelName: slot.modelName.replace(/Up|Down/g, hoodReplace),
963
+ effectId: slot.effectId || 0,
964
+ textureAlias: slot.textureAlias || "",
965
+ tintAlias: slot.tintAlias || "Default",
966
+ decalAlias: slot.decalAlias || "#",
967
+ slotId: slot.slotId,
968
+ SHADER_PARAMETER_GROUP: shaderParams
969
+ };
961
970
  }
962
971
 
963
972
  pGetAttachmentSlots() {
@@ -115,7 +115,7 @@ export class Character2016 extends BaseFullCharacter {
115
115
  /** Used to update the status of the players resources */
116
116
  resourcesUpdater?: any;
117
117
  factionId = 2;
118
- //isInInventory: boolean = false;
118
+ isInInventory: boolean = false;
119
119
  playTime: number = 0;
120
120
  lastDropPlaytime: number = 0;
121
121
  set godMode(state: boolean) {
@@ -45,7 +45,7 @@ function getRenderDistance(itemDefinitionId: number) {
45
45
  range = 420;
46
46
  break;
47
47
  }
48
- return range;
48
+ return range ? range : 1000;
49
49
  }
50
50
 
51
51
  import { BaseLightweightCharacter } from "./baselightweightcharacter";
@@ -42,7 +42,7 @@ import { LoadoutContainer } from "../classes/loadoutcontainer";
42
42
 
43
43
  export class Npc extends BaseFullCharacter {
44
44
  health: number;
45
- npcRenderDistance = 80;
45
+ npcRenderDistance = 100;
46
46
  spawnerId: number;
47
47
  deathTime: number = 0;
48
48
  npcId: number = 0;
@@ -90,7 +90,7 @@ export class TrapEntity extends BaseSimpleNpc {
90
90
  case Items.PUNJI_STICK_ROW:
91
91
  this.server.aiManager.addEntity(this);
92
92
  this.cooldown = 500;
93
- this.triggerRadiusX = 1;
93
+ this.triggerRadiusX = 0.8;
94
94
  this.triggerRadiusY = 0.5;
95
95
  break;
96
96
  case Items.SNARE:
@@ -373,6 +373,7 @@ export class Vehicle2016 extends BaseLootableEntity {
373
373
  break;
374
374
  }
375
375
  }
376
+ server.aiManager.addEntity(this);
376
377
  }
377
378
 
378
379
  getSeatCount() {
@@ -1344,6 +1345,7 @@ export class Vehicle2016 extends BaseLootableEntity {
1344
1345
  // TODO: Have to revisit when the heightmap is implemented server side.
1345
1346
  // fix floating vehicle lootbags
1346
1347
  server.worldObjectManager.createLootbag(server, this);
1348
+ server.aiManager.removeEntity(this);
1347
1349
  return deleted;
1348
1350
  }
1349
1351
 
@@ -124,6 +124,7 @@ export const commands: Array<Command> = [
124
124
  client: Client,
125
125
  args: Array<string>
126
126
  ) => {
127
+ if (server._soloMode) return;
127
128
  const collection = server._db.collection(DB_COLLECTIONS.KILLS);
128
129
  const query = await collection
129
130
  .aggregate([
@@ -343,6 +344,74 @@ export const commands: Array<Command> = [
343
344
  );
344
345
  }
345
346
  },
347
+ {
348
+ name: "findloot",
349
+ permissionLevel: PermissionLevels.MODERATOR,
350
+ execute: (server: ZoneServer2016, client: Client, args: Array<string>) => {
351
+ if (!args[0] || !args[1]) {
352
+ server.sendChatText(client, "[ERROR] Usage /findloot [itemId] [range]");
353
+ return;
354
+ }
355
+ let totalCount = 0;
356
+ for (const a in server._lootableConstruction) {
357
+ const lootableConstrucion = server._lootableConstruction[a];
358
+ if (
359
+ !isPosInRadius(
360
+ Number(args[1]),
361
+ client.character.state.position,
362
+ lootableConstrucion.state.position
363
+ )
364
+ )
365
+ continue;
366
+ let count = 0;
367
+ for (const b in lootableConstrucion._containers) {
368
+ const container = lootableConstrucion._containers[b];
369
+ for (const c in container.items) {
370
+ const item = container.items[c];
371
+ if (item.itemDefinitionId == Number(args[0])) {
372
+ count += item.stackCount;
373
+ totalCount += item.stackCount;
374
+ }
375
+ }
376
+ }
377
+ if (count) {
378
+ server.sendChatText(
379
+ client,
380
+ `COUNT: ${count} POSITION: [ ${lootableConstrucion.state.position[0]} ${lootableConstrucion.state.position[1]} ${lootableConstrucion.state.position[2]} ]`
381
+ );
382
+ }
383
+ }
384
+ for (const a in server._worldLootableConstruction) {
385
+ const lootableConstrucion = server._worldLootableConstruction[a];
386
+ if (
387
+ !isPosInRadius(
388
+ Number(args[1]),
389
+ client.character.state.position,
390
+ lootableConstrucion.state.position
391
+ )
392
+ )
393
+ continue;
394
+ let count = 0;
395
+ for (const b in lootableConstrucion._containers) {
396
+ const container = lootableConstrucion._containers[b];
397
+ for (const c in container.items) {
398
+ const item = container.items[c];
399
+ if (item.itemDefinitionId == Number(args[0])) {
400
+ count += item.stackCount;
401
+ totalCount += item.stackCount;
402
+ }
403
+ }
404
+ }
405
+ if (count) {
406
+ server.sendChatText(
407
+ client,
408
+ `COUNT: ${count} POSITION: [ ${lootableConstrucion.state.position[0]} ${lootableConstrucion.state.position[1]} ${lootableConstrucion.state.position[2]} ]`
409
+ );
410
+ }
411
+ }
412
+ server.sendChatText(client, `TOTAL COUNT: ${totalCount}`);
413
+ }
414
+ },
346
415
  {
347
416
  name: "netstats",
348
417
  permissionLevel: PermissionLevels.DEFAULT,
@@ -171,14 +171,15 @@ export class CraftManager {
171
171
  return true;
172
172
 
173
173
  const item: any = itemDS.item;
174
- if (item?.stackCount - count <= 0) {
174
+ const remainder = item?.stackCount - count;
175
+ if (remainder <= 0) {
175
176
  return server.deleteEntity(item.ownerCharacterId, server._spawnedItems);
176
- } else if (item?.stackCount) {
177
- const e = server.getEntity(item.ownerCharacterId);
178
- if (e && e instanceof ItemObject) {
179
- e.item.stackCount -= count;
180
- return true;
181
- }
177
+ }
178
+
179
+ const entity = server.getEntity(item.ownerCharacterId);
180
+ if (entity instanceof ItemObject) {
181
+ entity.item.stackCount -= count;
182
+ return true;
182
183
  }
183
184
 
184
185
  return false;