h1z1-server 0.47.2-9 → 0.47.2

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-9",
3
+ "version": "0.47.2",
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",
@@ -1188,10 +1188,10 @@ export function packVehicleReferenceData(obj: any) {
1188
1188
  }
1189
1189
 
1190
1190
  export function generateOldInteractionComponent() {
1191
- const raw = [
1192
- 0x00, 0x00, 0xa0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1193
- ];
1194
- return Buffer.from(raw);
1191
+ const floatBuf = Buffer.allocUnsafe(4);
1192
+ floatBuf.writeFloatLE(3.0, 0);
1193
+ const raw = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
1194
+ return Buffer.concat([floatBuf, Buffer.from(raw)]);
1195
1195
  }
1196
1196
  export function generateOldNpcComponent() {
1197
1197
  const old = [
@@ -1214,18 +1214,19 @@ export function generateOldNpcComponent() {
1214
1214
  ];
1215
1215
  return Buffer.from([...old, ...raw]);
1216
1216
  }
1217
- export function generateWorldItemRepData() {
1217
+ export function generateWorldItemRepData(nameId: number = 0) {
1218
+ const data = Buffer.allocUnsafe(16);
1219
+ data.writeUintLE(nameId, 12, 4);
1218
1220
  const raw = [
1219
1221
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1220
1222
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1221
1223
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1222
1224
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1223
1225
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1224
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1225
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
1226
- // /\ Set isWorldItem byte
1226
+ 0x00, 0x01
1227
+ // /\ Set isWorldItem byte
1227
1228
  ];
1228
- return Buffer.from(raw);
1229
+ return Buffer.concat([data, Buffer.from(raw)]);
1229
1230
  }
1230
1231
  export function packComponentNameString(name: string) {
1231
1232
  const stringBuffer = Buffer.from(name, "ascii");
@@ -19,6 +19,7 @@ import dgram, { RemoteInfo } from "node:dgram";
19
19
  const debug = require("debug")("LZConnection");
20
20
 
21
21
  export abstract class BaseLZConnection extends EventEmitter {
22
+ _serverAddress: string;
22
23
  _serverPort?: number;
23
24
  _protocol: LZConnectionProtocol;
24
25
  _udpLength: number = 512;
@@ -29,6 +30,7 @@ export abstract class BaseLZConnection extends EventEmitter {
29
30
  _pingTimer!: NodeJS.Timeout;
30
31
  protected constructor(serverPort?: number) {
31
32
  super();
33
+ this._serverAddress = process.env.SERVER_BIND_ADDRESS || "0.0.0.0";
32
34
  this._serverPort = serverPort;
33
35
  this._protocol = new LZConnectionProtocol();
34
36
  this._connection = dgram.createSocket("udp4");
@@ -72,7 +74,7 @@ export abstract class BaseLZConnection extends EventEmitter {
72
74
  );
73
75
 
74
76
  return await new Promise((resolve) => {
75
- this._connection.bind(this._serverPort, undefined, () => {
77
+ this._connection.bind(this._serverPort, this._serverAddress, () => {
76
78
  resolve();
77
79
  });
78
80
  });
@@ -25,6 +25,8 @@ import { PacketsQueue } from "./PacketsQueue";
25
25
  const debug = require("debug")("SOEServer");
26
26
 
27
27
  export class SOEServer extends EventEmitter {
28
+ _serverAddress: string;
29
+ _serverAddressV6: string;
28
30
  _serverPort: number;
29
31
  _cryptoKey: Uint8Array;
30
32
  _protocol!: Soeprotocol;
@@ -50,6 +52,8 @@ export class SOEServer extends EventEmitter {
50
52
  const oneMb = 1024 * 1024;
51
53
  Buffer.poolSize = oneMb;
52
54
  this._serverPort = serverPort;
55
+ this._serverAddress = process.env.SERVER_BIND_ADDRESS || "0.0.0.0";
56
+ this._serverAddressV6 = process.env.SERVER_BIND_ADDRESS_V6 || "::";
53
57
  this._cryptoKey = cryptoKey;
54
58
  this._maxMultiBufferSize = this._udpLength - 4 - this._crcLength;
55
59
  this._connection = dgram.createSocket({
@@ -463,9 +467,9 @@ export class SOEServer extends EventEmitter {
463
467
  this._connectionv6.on("message", (data, remote) => {
464
468
  this.onMessage(data, remote);
465
469
  });
466
- this._connection.bind(this._serverPort);
470
+ this._connection.bind(this._serverPort, this._serverAddress);
467
471
  if (!process.env.DISABLE_IPV6) {
468
- this._connectionv6.bind(this._serverPort);
472
+ this._connectionv6.bind(this._serverPort, this._serverAddressV6);
469
473
  }
470
474
  }
471
475
 
@@ -119,6 +119,10 @@ export abstract class BaseFullCharacter extends BaseLightweightCharacter {
119
119
  gender: number;
120
120
  hoodState: string = "Up";
121
121
 
122
+ // Updates from constructionPermissionsManager, avoids checking all buildings every inventory access.
123
+ /** The guid of the building the character is inside of */
124
+ insideBuilding: string = "";
125
+
122
126
  /** The default items that will spawn on and with the BaseFullCharacter */
123
127
  defaultLoadout: LoadoutKit = [];
124
128
  constructor(
@@ -287,6 +287,10 @@ export class ConstructionChildEntity extends BaseLightweightCharacter {
287
287
  const p = position[1] + 2.4;
288
288
  this.boundsOn = getCubeBounds(position, 5, 5, angle, p, p + 1.8);
289
289
 
290
+ break;
291
+ case Items.METAL_DOORWAY:
292
+ case Items.METAL_WALL:
293
+ this.fixedPosition = movePoint(position, -(rotation[1] + 1.575), 2.5);
290
294
  break;
291
295
  }
292
296
 
@@ -17,7 +17,12 @@ import { ZoneServer2016 } from "../zoneserver";
17
17
  import { BaseItem } from "../classes/baseItem";
18
18
  import { BaseLightweightCharacter } from "./baselightweightcharacter";
19
19
  import { ZoneClient2016 } from "../classes/zoneclient";
20
- import { randomIntFromInterval } from "../../../utils/utils";
20
+ import {
21
+ checkLineThroughDoorway,
22
+ randomIntFromInterval,
23
+ rotateAroundPivot,
24
+ wallInterceptsLine
25
+ } from "../../../utils/utils";
21
26
  import {
22
27
  AddLightweightNpc,
23
28
  ClientUpdateProximateItems
@@ -25,6 +30,9 @@ import {
25
30
  import { LoadoutContainer } from "../classes/loadoutcontainer";
26
31
  import { BaseFullCharacter } from "./basefullcharacter";
27
32
  import { LOADOUT_CONTAINER_GUID } from "../../../utils/constants";
33
+ import { ConstructionDoor } from "./constructiondoor";
34
+ import { ConstructionParentEntity } from "./constructionparententity";
35
+ import { ConstructionChildEntity } from "./constructionchildentity";
28
36
 
29
37
  // TODO find a better way to handle items not inside containers
30
38
  function transferItemObject(
@@ -129,6 +137,7 @@ export class ItemObject extends BaseLightweightCharacter {
129
137
  npcRenderDistance = 25;
130
138
  spawnerId = 0;
131
139
  item: BaseItem;
140
+ insideBuilding: string = "";
132
141
  isWorldItem: boolean = false;
133
142
  creationTime: number = 0;
134
143
  triggerExplosionShots = Math.floor(Math.random() * 3) + 2; // random number 2-4 neccesary shots
@@ -224,6 +233,130 @@ export class ItemObject extends BaseLightweightCharacter {
224
233
  }
225
234
  }
226
235
 
236
+ checkBuildingObstruct(
237
+ server: ZoneServer2016,
238
+ character: Float32Array,
239
+ foundation: ConstructionParentEntity | undefined
240
+ ): boolean {
241
+ const charPos = new Float32Array(character);
242
+
243
+ const itemPos = this.state.position;
244
+ charPos[1] += 1.8;
245
+
246
+ const charInFoundation: boolean =
247
+ foundation instanceof ConstructionParentEntity;
248
+
249
+ if (!charInFoundation) {
250
+ foundation =
251
+ server._constructionFoundations[this.insideBuilding] ??
252
+ server._constructionSimple[this.insideBuilding]?.getParent(server);
253
+ }
254
+
255
+ if (!foundation) return false;
256
+
257
+ if (
258
+ foundation.itemDefinitionId == Items.SHACK ||
259
+ foundation.itemDefinitionId == Items.SHACK_BASIC || // TODO
260
+ foundation.itemDefinitionId == Items.SHACK_SMALL
261
+ ) {
262
+ if (!foundation.isSecured) return false;
263
+
264
+ if (charInFoundation && foundation.isInside(itemPos)) return false;
265
+
266
+ return true;
267
+ }
268
+
269
+ if (
270
+ foundation.itemDefinitionId != Items.FOUNDATION_EXPANSION &&
271
+ foundation.itemDefinitionId != Items.FOUNDATION &&
272
+ foundation.itemDefinitionId != Items.GROUND_TAMPER
273
+ ) {
274
+ return false;
275
+ }
276
+
277
+ if (foundation.itemDefinitionId == Items.FOUNDATION_EXPANSION)
278
+ foundation = foundation.getParentFoundation(server);
279
+
280
+ if (!foundation) return false;
281
+
282
+ const allShelters = {
283
+ ...foundation.occupiedShelterSlots,
284
+ ...Object.assign(
285
+ {},
286
+ ...Object.values(foundation.occupiedExpansionSlots).map(
287
+ (exp) => exp.occupiedShelterSlots
288
+ )
289
+ )
290
+ };
291
+
292
+ const allWalls = {
293
+ ...foundation.occupiedWallSlots,
294
+ ...Object.assign(
295
+ {},
296
+ ...Object.values(foundation.occupiedExpansionSlots).map(
297
+ (exp) => exp.occupiedWallSlots
298
+ )
299
+ )
300
+ };
301
+
302
+ for (const w in allWalls) {
303
+ const wall = allWalls[w];
304
+ const wallStart = new Float32Array<ArrayBufferLike>(wall.state.position);
305
+ let wallEnd = new Float32Array(wall.fixedPosition);
306
+
307
+ wallEnd[0] = 2 * wallEnd[0] - wallStart[0]; // doorEnd is currently the midpoint
308
+ wallEnd[2] = 2 * wallEnd[2] - wallStart[2]; // extend it to the end
309
+ wallEnd[1] += 3.5;
310
+
311
+ if (wall instanceof ConstructionDoor && wall.isOpen)
312
+ wallEnd = rotateAroundPivot(wallStart, wallEnd, -Math.PI / 2);
313
+
314
+ if (wallInterceptsLine(charPos, itemPos, wallStart, wallEnd)) {
315
+ if (
316
+ wall.itemDefinitionId == Items.METAL_DOORWAY &&
317
+ checkLineThroughDoorway(charPos, itemPos, wall)
318
+ )
319
+ continue;
320
+ else return true;
321
+ }
322
+ }
323
+
324
+ for (const s in allShelters) {
325
+ const shelter: ConstructionChildEntity = allShelters[s];
326
+
327
+ if (!shelter.cubebounds) continue;
328
+ const walls: [Float32Array, Float32Array][] = [
329
+ [
330
+ new Float32Array(shelter.cubebounds[0]),
331
+ new Float32Array(shelter.cubebounds[5])
332
+ ],
333
+
334
+ [
335
+ new Float32Array(shelter.cubebounds[1]),
336
+ new Float32Array(shelter.cubebounds[6])
337
+ ],
338
+
339
+ [
340
+ new Float32Array(shelter.cubebounds[2]),
341
+ new Float32Array(shelter.cubebounds[7])
342
+ ],
343
+
344
+ [
345
+ new Float32Array(shelter.cubebounds[3]),
346
+ new Float32Array(shelter.cubebounds[4])
347
+ ]
348
+ ];
349
+
350
+ for (const wall of walls) {
351
+ if (wallInterceptsLine(charPos, itemPos, ...wall)) {
352
+ return !checkLineThroughDoorway(charPos, itemPos, shelter);
353
+ }
354
+ }
355
+ }
356
+
357
+ return false;
358
+ }
359
+
227
360
  OnInteractionString(server: ZoneServer2016, client: ZoneClient2016): void {
228
361
  server.sendData(client, "Command.InteractionString", {
229
362
  guid: this.characterId,
@@ -2418,9 +2418,11 @@ export class ConstructionManager {
2418
2418
  client: Client
2419
2419
  ) {
2420
2420
  let hide = false;
2421
-
2421
+ client.character.insideBuilding = "";
2422
2422
  for (const object of client.spawnedEntities) {
2423
2423
  if (object instanceof ConstructionParentEntity) {
2424
+ if (object.isInside(client.character.state.position))
2425
+ client.character.insideBuilding = object.characterId;
2424
2426
  if (this.checkFoundationPermission(server, client, object)) {
2425
2427
  hide = true;
2426
2428
  continue;
@@ -2428,6 +2430,8 @@ export class ConstructionManager {
2428
2430
  }
2429
2431
 
2430
2432
  if (object instanceof ConstructionChildEntity) {
2433
+ if (object.isInside(client.character.state.position))
2434
+ client.character.insideBuilding = object.characterId;
2431
2435
  if (
2432
2436
  this.checkConstructionChildEntityPermission(server, client, object)
2433
2437
  ) {
@@ -21,6 +21,7 @@ import { BaseItem } from "../classes/baseItem";
21
21
  import { BaseLootableEntity } from "../entities/baselootableentity";
22
22
  import { ChallengeType } from "./challengemanager";
23
23
  import { ItemObject } from "../entities/itemobject";
24
+ import { ClientUpdateProximateItems } from "types/zone2016packets";
24
25
  const debug = require("debug")("ZoneServer");
25
26
 
26
27
  interface CraftComponentDSEntry {
@@ -179,6 +180,13 @@ export class CraftManager {
179
180
  const entity = server.getEntity(item.ownerCharacterId);
180
181
  if (entity instanceof ItemObject) {
181
182
  entity.item.stackCount -= count;
183
+ const client = server.getClientByCharId(itemDS.character.characterId);
184
+ if (!client) return true;
185
+ server.sendData<ClientUpdateProximateItems>(
186
+ client,
187
+ "ClientUpdate.ProximateItems",
188
+ server.getProximityItems(client)
189
+ );
182
190
  return true;
183
191
  }
184
192
 
@@ -261,6 +261,7 @@ import { ChallengeManager, ChallengeType } from "./managers/challengemanager";
261
261
  import { RandomEventsManager } from "./managers/randomeventsmanager";
262
262
  import { AiManager } from "./managers/aimanager";
263
263
  import { AirdropManager } from "./managers/airdropmanager";
264
+ import { generateWorldItemRepData } from "../../packets/ClientProtocol/ClientProtocol_1080/shared";
264
265
  //import { TaskManager } from "./managers/tasksmanager";
265
266
 
266
267
  const spawnLocations2 = require("../../../data/2016/zoneData/Z1_gridSpawns.json"),
@@ -1297,17 +1298,37 @@ export class ZoneServer2016 extends EventEmitter {
1297
1298
 
1298
1299
  getProximityItems(client: Client): ClientUpdateProximateItems {
1299
1300
  const proximityItems: ClientUpdateProximateItems = { items: [] };
1301
+ let foundation: ConstructionParentEntity | undefined =
1302
+ this._constructionFoundations[client.character.insideBuilding] ??
1303
+ this._constructionSimple[
1304
+ client.character.insideBuilding
1305
+ ]?.getParentFoundation(this);
1300
1306
 
1301
1307
  for (const object of client.spawnedEntities) {
1302
1308
  if (object instanceof ItemObject) {
1309
+ let yDistance = 1;
1310
+ if (
1311
+ client.character.state.position[1] <
1312
+ object.state.position[1] - 0.5
1313
+ ) {
1314
+ yDistance = 1.8;
1315
+ }
1303
1316
  if (
1304
1317
  isPosInRadiusWithY(
1305
1318
  this.proximityItemsDistance,
1306
1319
  client.character.state.position,
1307
1320
  object.state.position,
1308
- 1
1321
+ yDistance
1309
1322
  )
1310
1323
  ) {
1324
+ if (
1325
+ object.checkBuildingObstruct(
1326
+ this,
1327
+ client.character.state.position,
1328
+ foundation
1329
+ )
1330
+ )
1331
+ continue;
1311
1332
  const proximityItem = {
1312
1333
  itemDefinitionId: object.item.itemDefinitionId,
1313
1334
  associatedCharacterGuid: object.characterId,
@@ -4100,7 +4121,11 @@ export class ZoneServer2016 extends EventEmitter {
4100
4121
  transientId: entity.transientId,
4101
4122
  stringSize: "ClientNpcComponent".length,
4102
4123
  componentName: "ClientNpcComponent",
4103
- properties: [{}]
4124
+ properties: [
4125
+ {
4126
+ bufferData: generateWorldItemRepData(entity.nameId)
4127
+ }
4128
+ ]
4104
4129
  }
4105
4130
  );
4106
4131
  }
@@ -6712,6 +6737,7 @@ export class ZoneServer2016 extends EventEmitter {
6712
6737
  );
6713
6738
 
6714
6739
  if (!obj) return;
6740
+ obj.insideBuilding = character.insideBuilding;
6715
6741
  this.executeFuncForAllReadyClientsInRange((c) => {
6716
6742
  c.spawnedEntities.add(obj);
6717
6743
  this.addLightweightNpc(c, obj);
@@ -33,6 +33,7 @@ import { Resolver } from "node:dns";
33
33
  import { ZoneClient2016 } from "servers/ZoneServer2016/classes/zoneclient";
34
34
  import * as crypto from "crypto";
35
35
  import { ZoneClient } from "servers/ZoneServer2015/classes/zoneclient";
36
+ import { ConstructionDoor } from "../servers/ZoneServer2016/entities/constructiondoor";
36
37
 
37
38
  const startTime = Date.now();
38
39
 
@@ -319,6 +320,108 @@ export function getAngle(position1: Float32Array, position2: Float32Array) {
319
320
  return angle;
320
321
  }
321
322
 
323
+ /**
324
+ * Rotates point around pivot by angle
325
+ *
326
+ * @param pivot - The pivot to rotate around
327
+ * @param point - The point that will be rotated
328
+ * @param angle - The angle to rotate
329
+ * @returns - A new point at the new position
330
+ */
331
+ export function rotateAroundPivot(
332
+ pivot: Float32Array,
333
+ point: Float32Array,
334
+ angle: number
335
+ ): Float32Array {
336
+ // Calculate the vector from pivot
337
+ const vX = point[0] - pivot[0];
338
+ const vY = point[1] - pivot[1];
339
+ const vZ = point[2] - pivot[2];
340
+
341
+ const cos = Math.cos(angle);
342
+ const sin = Math.sin(angle);
343
+
344
+ // Do matrix rotation and then add back the pivot
345
+ return new Float32Array([
346
+ pivot[0] + vX * cos - vZ * sin,
347
+ pivot[1] + vY,
348
+ pivot[2] + vX * sin + vZ * cos
349
+ ]);
350
+ }
351
+
352
+ /**
353
+ * Checks if a line goes through a open or missing door slot.
354
+ *
355
+ * @param a1 - The first point of the line
356
+ * @param a2 - The second point of the line
357
+ * @param doorway - The construction entity that has a door.
358
+ * @returns - Boolean if the line passes through a open door or a door slot with no door.
359
+ */
360
+ export function checkLineThroughDoorway(
361
+ a1: Float32Array,
362
+ a2: Float32Array,
363
+ doorway: ConstructionChildEntity
364
+ ): boolean {
365
+ const door = doorway.occupiedWallSlots[1];
366
+ if (door && door instanceof ConstructionDoor && !door.isOpen) return false;
367
+ let doorEnd: Float32Array;
368
+ let doorStart: Float32Array;
369
+
370
+ if (door instanceof ConstructionDoor && door.isOpen) {
371
+ doorEnd = new Float32Array(door.fixedPosition);
372
+ doorStart = new Float32Array(door.state.position);
373
+ } else {
374
+ // No door, calculate the door slot
375
+ const rotation = doorway.getSlotRotation(1, doorway.wallSlots);
376
+ doorStart = new Float32Array(
377
+ doorway.getSlotPosition(1, doorway.wallSlots)!
378
+ );
379
+ doorEnd = movePoint(doorStart!, -(rotation![1] + 1.575), 0.625);
380
+ }
381
+
382
+ doorEnd[0] = 2 * doorEnd[0] - doorStart[0]; // doorEnd is currently the midpoint
383
+ doorEnd[2] = 2 * doorEnd[2] - doorStart[2]; // extend it to the end
384
+ doorEnd[1] += 2;
385
+ doorStart![1] -= 0.5;
386
+ return wallInterceptsLine(a1, a2, doorStart!, doorEnd);
387
+ }
388
+
389
+ /**
390
+ * Check if the wall defined by b1, b2 is between the line a1, a2
391
+ *
392
+ * @param a1 - The first point of the line
393
+ * @param a2 - The second point of the line
394
+ * @param b1 - The bottom left corner of the wall
395
+ * @param b2 - The top right corner of the wall
396
+ * @returns - A boolean if the line is intercepted by the wall
397
+ */
398
+ export function wallInterceptsLine(
399
+ a1: Float32Array,
400
+ a2: Float32Array,
401
+ b1: Float32Array,
402
+ b2: Float32Array
403
+ ): boolean {
404
+ b1[1] -= 0.5; // extend the plane down a bit
405
+
406
+ const orient = (p: Float32Array, q: Float32Array, r: Float32Array) =>
407
+ (q[0] - p[0]) * (r[2] - p[2]) - (q[2] - p[2]) * (r[0] - p[0]);
408
+
409
+ const o1 = orient(a1, a2, b1);
410
+ const o2 = orient(a1, a2, b2);
411
+ const o3 = orient(b1, b2, a1);
412
+ const o4 = orient(b1, b2, a2);
413
+
414
+ const intersects2D = o1 * o2 < 0 && o3 * o4 < 0;
415
+ if (!intersects2D) return false;
416
+
417
+ const lineMinY = Math.min(a1[1], a2[1]);
418
+ const lineMaxY = Math.max(a1[1], a2[1]);
419
+ const wallMinY = Math.min(b1[1], b2[1]);
420
+ const wallMaxY = Math.max(b1[1], b2[1]);
421
+
422
+ return lineMaxY >= wallMinY && lineMinY <= wallMaxY;
423
+ }
424
+
322
425
  /**
323
426
  * Shuts down the zone server with a countdown timer and performs necessary cleanup operations.
324
427
  *