diff --git a/data/2016/zoneData/Z1_POIs.json b/data/2016/zoneData/Z1_POIs.json index e681b699b..436574569 100644 --- a/data/2016/zoneData/Z1_POIs.json +++ b/data/2016/zoneData/Z1_POIs.json @@ -6,40 +6,22 @@ "range": 500, "bounds": [ [ - [ -508.45, -493.85 ], - [ -32.12, -564.99 ], - [ 7.95, -1634.05 ], - [ -606.68, -1580.10 ] + [ 688.8599853515625, -1381.3299560546875 ], + [ -519.4099731445312, -1395.1300048828125 ], + [ -514.1300048828125, -703.780029296875 ], + [ 677.9600219726562, -708.0900268554688 ] ], [ - [ -195.925, -216.16 ], - [ -219.09, -767.49 ], - [ 564.67, -754.89 ], - [ 556.09, -188.00 ] + [ 366.1538391113281, -885 ], + [ 380, -348.0769348144531 ], + [ -145.76922607421875, -345.3846130371094 ], + [ -488.8461608886719, -879.6153564453125 ] ], [ - [ 754.74, -760.32 ], - [ 736.30, -1491.11 ], - [ -21.73, -1564.52 ], - [ -45.77, -736.92 ] - ], - [ - [ -407.59, -549.97 ], - [ -538.90, -506.60 ], - [ -720.03, -1039.20 ], - [ -496.45, -1063.49 ] - ], - [ - [ -662.36, -692.92 ], - [ -546.75, -739.91 ], - [ -628.59, -1062.99 ], - [ -749.83, -1049.17 ] - ], - [ - [ -582.38, -1273.47 ], - [ -531.07, -1061.91 ], - [ -796.00, -1021.89 ], - [ -789.04, -1272.35] + [ -544.20, -779.00 ], + [ -438.20, -854.73 ], + [ -469.12, -966.19 ], + [ -611.42, -928.52 ] ] ], "position": [ @@ -49,38 +31,6 @@ 1 ] }, - { - "POIid": 2, - "POIname": "Zimm's", - "stringId": 14353, - "range": 250, - "bounds": [ - [ - [ 2317.03, -909.71 ], - [ 2068, -912.40 ], - [ 2040.04, -1203.37 ], - [ 2300.89, -1156.77 ] - ], - [ - [ 2369.15, -1116.69 ], - [ 2250.10, -1113.76 ], - [ 2251.99, -954.96 ], - [ 2399.56, -947.94 ] - ], - [ - [ 1881.54, -1064.03 ], - [ 1927.35, -1060.55 ], - [ 1929.91, -1106.32 ], - [ 1878.03, -1109.03 ] - ] - ], - "position": [ - 2208.55, - 47.41, - -1011.94, - 1 - ] - }, { "POIid": 3, "POIname": "Bubba's Truck Stop", @@ -88,10 +38,10 @@ "range": 250, "bounds": [ [ - [ -82.59, 2699.30 ], - [ -616.30, 2697.42 ], - [ -525.13, 2429.28 ], - [ -276.57, 2447.77 ] + [ -361.24, 2709.03 ], + [ -351.14, 2459.93 ], + [ -604.77, 2430.73 ], + [ -607, 2714.86 ] ] ], "position": [ @@ -108,16 +58,10 @@ "range": 200, "bounds": [ [ - [ -428.31, -2040.41 ], - [ -425.06, -1404.85 ], - [ -859.58, -1339.96 ], - [ -911.67, -2000.13 ] - ], - [ - [ -565.32, -1210.83 ], - [ -731.70, -1237.01 ], - [ -743.26, -1411.39 ], - [ -547.48, -1442.33 ] + [ -802.030029296875, -1946.239990234375 ], + [ -529.55, -1946.23 ], + [ -529.55, -1423.489990234375 ], + [ -802.4400024414062, -1423.9599609375 ] ] ], "shackBounds": [ @@ -142,28 +86,10 @@ "range": 300, "bounds": [ [ - [ -1734.30, 1836.78 ], - [ -1159.91, 1836.89 ], - [ -1160.15, 1449.29 ], - [ -1700.67, 1371.70 ] - ], - [ - [ -1161.65, 1450.69 ], - [ -966.13, 1449.96 ], - [ -970.32, 2093.09 ], - [ -1174.87, 2101.27 ] - ], - [ - [ -1780.92, 1831.59 ], - [ -1491.28, 2358.28 ], - [ -1129.33, 2284.56 ], - [ -1159.90, 1781.59 ] - ], - [ - [ -1489.73, 2327.43 ], - [ -1770.00, 2312.08 ], - [ -1844.17, 1702.59], - [ -1444.43, 1672.12 ] + [ -1012.21, 1689.32 ], + [ -1562.59, 1679.51 ], + [ -1572.25, 2142.52 ], + [ -1020.29, 2169.41 ] ] ], "position": [ @@ -200,10 +126,10 @@ "range": 100, "bounds": [ [ - [ -1979.94, -2321.00 ], - [ -1994.83, -2064.46 ], - [ -1820.05, -2092.85 ], - [ -1819.19, -2323.70 ] + [ -1973.04, -2304 ], + [ -1972.03, -2100.18 ], + [ -1837.69, -2099.13 ], + [ -1831.2, -2296.72 ] ] ], "position": [ @@ -272,16 +198,10 @@ "range": 150, "bounds": [ [ - [ -893.38, 1102.40 ], - [ -427.67, 797.01 ], - [ -185.03, 1031.67 ], - [ -588.13, 1445.32 ] - ], - [ - [ -497.73, 1437.52 ], - [ -427.95, 1479.34 ], - [ -402.98, 1410.06 ], - [ -442.30, 1360.75 ] + [ -581.12, 815.99 ], + [ -827.71, 1075.91 ], + [ -553.56, 1372.34 ], + [ -286.56, 1114.66 ] ] ], "position": [ @@ -310,60 +230,11 @@ "range": 400, "bounds": [ [ - [ 1667.29, 1922.72 ], - [ 1640.72, 2698.08 ], - [ 2446.94, 2662.86 ], - [ 2375, 1902.01 ] - ], - [ - [ 1670.75, 2561.05 ], - [ 2054.95, 2581.53 ], - [ 2057.12, 2719.01 ], - [ 1665.10, 2718.46 ] - ], - [ - [ 2355.76, 1858.40 ], - [ 2276.89, 1826.08 ], - [ 2296.39, 1746.52 ], - [ 2392.80, 1784.22 ] - ], - [ - [ 2294.19, 1263.97 ], - [ 2365.30, 1174.27 ], - [ 2318.05, 1127.55 ], - [ 2248.64, 1162.73 ] - ], - [ - [ 2600.94, 898.77 ], - [ 2709.45, 914.59 ], - [ 2705.96, 795.19 ], - [ 2605.97, 791.18 ] - ], - [ - [ 2274.40, 842.75 ], - [ 2178.78, 830.04 ], - [ 2170.24, 723.07 ], - [ 2281.06, 724.65 ] - ], - [ - [ 2012.48, 471.61 ], - [ 2002.23, 384.99 ], - [ 2081.47, 376.49 ], - [ 2086.74, 471.65 ] - ], - [ - [ 1880.86, 170.74 ], - [ 1766.22, 151.57 ], - [ 1765.63, 278.16 ], - [ 1848.98, 332.02 ] - ], - [ - [ 1560.43, -564.56 ], - [ 1571.88, -642.51 ], - [ 1673.48, -629.54 ], - [ 1656.20, -549.60 ] + [ 1783.84, 2037.79 ], + [ 1794.8, 2523.56 ], + [ 2356.19, 2534.53 ], + [ 2325.39, 2041.18 ] ] - ], "position": [ 2020.91, @@ -397,14 +268,6 @@ "POIname": "Wake Hills Hamlet", "stringId": 9578, "range": 100, - "bounds": [ - [ - [ -3245.02, -1290.06 ], - [ -3254.89, -1604.42 ], - [ -2971.63, -1610.36 ], - [ -2980.50, -1329.87 ] - ] - ], "position": [ -3165.71, 67.58, @@ -431,10 +294,10 @@ "range": 350, "bounds": [ [ - [ 2013.54, -2982.74 ], - [ 1966.49, -2661.38 ], - [ 1600.39, -2674.76], - [ 1615.91, -2992.76 ] + [ 1853.75, -2859.73 ], + [ 1768.57, -2859.05 ], + [ 1763.60, -2729.51 ], + [ 1889.52, -2729.51 ] ] ], "position": [ @@ -455,12 +318,6 @@ [ 1051.0999755859375, -2515.39990234375 ], [ 1056.3900146484375, -2796.889892578125 ], [ 559.989990234375, -2806.1298828125 ] - ], - [ - [ 1022.37, -2733.68 ], - [ 1104.92, -2734.90 ], - [ 1097.28, -2606.72 ], - [ 1006.34, -2601.56 ] ] ], "position": [ @@ -497,16 +354,10 @@ "range": 200, "bounds": [ [ - [ 2748.34, -2337.22 ], - [ 2931.17, -2342.39 ], - [ 2916.66, -2505.60 ], - [ 2668.29, -2494.45 ] - ], - [ - [ 2599.80, -2500.69 ], - [ 2725.49, -2783.78 ], - [ 3006.36, -2552.48 ], - [ 2926.54, -2384.30 ] + [ 2827.72, -2368.44 ], + [ 2956.69, -2502.24 ], + [ 2734.79, -2737.14 ], + [ 2580.71, -2582.54 ] ] ], "position": [ @@ -520,97 +371,12 @@ "POIid": 21, "POIname": "Military Base 2", "stringId": 9550, - "range": 200, + "range": 150, "position": [ -2566.21, 42.91, -328.69, 1 ] - }, - { - "POIid": 22, - "POIname": "Eastwood", - "stringId": 14354, - "range": 250, - "bounds": [ - [ - [ 683.51, 3201.91 ], - [ 771.74, 3409.03 ], - [ 1088.14, 3411.00 ], - [ 1082.29, 3067.88 ] - ] - ], - "position": [ - 894.50, - 78.56, - 3226.43, - 1 - ] - }, - { - "POIid": 23, - "POIname": "Dartmouth", - "stringId": 14354, - "range": 250, - "bounds": [ - [ - [ -3041.38, -816.85 ], - [ -3051.68, -629.32 ], - [ -2797.75, -631.14 ], - [ -2837.11, -852.91 ] - ], - [ - [ -2361.95, -1053.46 ], - [ -2560.10, -1173.38 ], - [ -2863.04, -845.56 ], - [ -2770.88, -629.57 ] - ], - [ - [ -2593.37, 318.93 ], - [ -2620.91, -65.00 ], - [ -2915.08, -17.83 ], - [ -2941.39, 359.68 ] - ], - [ - [ -2887.11, -164.85 ], - [ -2807.83, -173.01 ], - [ -2798.75, -88.19 ], - [ -2894.21, -74.21 ] - ], - [ - [ -2889.50, -1255.28 ], - [ -2797.20, -1274.69 ], - [ -2781.35, -1195.17 ], - [ -2873.81, -1175.45 ] - ] - - ], - "position": [ - 894.50, - 78.56, - 3226.43, - 1 - ] - }, - { - "POIid": 24, - "POIname": "Crescent", - "stringId": 14354, - "range": 250, - "bounds": [ - [ - [ -2642.03, 2893.60 ], - [ -2801.52, 2797.84 ], - [ -2625.80, 2585.24 ], - [ -2503.78, 2705.70 ] - ] - ], - "position": [ - 894.50, - 78.56, - 3226.43, - 1 - ] } ] diff --git a/package-lock.json b/package-lock.json index f78a34468..bdf4041ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "h1z1-server", - "version": "0.45.3-1", + "version": "0.45.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "h1z1-server", - "version": "0.45.3-1", + "version": "0.45.3", "hasInstallScript": true, "license": "GPL-3.0-only", "dependencies": { diff --git a/package.json b/package.json index 1cfe45eb4..88e34f050 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "h1z1-server", - "version": "0.45.3-1", + "version": "0.45.3", "description": "Library for emulating h1z1 servers", "author": "Quentin Gruber (http://github.com/quentingruber)", "license": "GPL-3.0-only", diff --git a/src/servers/ZoneServer2016/entities/character.ts b/src/servers/ZoneServer2016/entities/character.ts index 8098b5215..309ba47cc 100644 --- a/src/servers/ZoneServer2016/entities/character.ts +++ b/src/servers/ZoneServer2016/entities/character.ts @@ -102,9 +102,9 @@ interface MeleeHit { characterId: string; } export class Character2016 extends BaseFullCharacter { - /** The players in-game name */ + /** The players in-game name and clan */ name!: string; - + clan!: string; /** The location the player spawned at */ spawnLocation?: string; @@ -403,6 +403,18 @@ export class Character2016 extends BaseFullCharacter { this.materialType = MaterialTypes.FLESH; } + async getClan(server: ZoneServer2016): Promise { + const clanData = await server.getPlayerClan(this.characterId); + this.clan = clanData ? `[${clanData.tag}]` : ""; + } + + async updateClanTag(server: ZoneServer2016, clanTag: string): Promise { + this.clan = clanTag ? `[${clanTag}]` : ""; + const client = server.getClientByCharId(this.characterId); + + //logic to update clan tag for all clients + } + getShaderGroup() { switch (this.headActor) { case "SurvivorMale_Head_02.adr": @@ -971,11 +983,13 @@ export class Character2016 extends BaseFullCharacter { * Gets the LightweightPC packet fields for use in SelfSendToClient and AddLightweightPC */ pGetLightweight() { + const characterName = this.clan ? `${this.clan} ${this.name}` : this.name; + return { ...super.pGetLightweight(), rotation: this.state.lookAt, identity: { - characterName: this.name + characterName: characterName }, shaderGroupId: this.getShaderGroup() }; diff --git a/src/servers/ZoneServer2016/handlers/commands/commands.ts b/src/servers/ZoneServer2016/handlers/commands/commands.ts index 67eef5914..95f188ade 100644 --- a/src/servers/ZoneServer2016/handlers/commands/commands.ts +++ b/src/servers/ZoneServer2016/handlers/commands/commands.ts @@ -3514,7 +3514,18 @@ export const commands: Array = [ console.log(server.weatherManager.weather); server.weatherManager.sendUpdateToAll(server, client, false); } + }, + { + name: "clan", + permissionLevel: PermissionLevels.DEFAULT, + keepCase: true, + execute: async ( + server: ZoneServer2016, + client: Client, + args: Array + ) => { + server.clanManager.handleClanCommand(server, client, args); + } } - //#endregion ]; diff --git a/src/servers/ZoneServer2016/managers/clanmanager.ts b/src/servers/ZoneServer2016/managers/clanmanager.ts new file mode 100644 index 000000000..7ae1d8097 --- /dev/null +++ b/src/servers/ZoneServer2016/managers/clanmanager.ts @@ -0,0 +1,329 @@ +// ====================================================================== +// +// GNU GENERAL PUBLIC LICENSE +// Version 3, 29 June 2007 +// copyright (C) 2020 - 2021 Quentin Gruber +// copyright (C) 2021 - 2024 H1emu community +// +// https://github.com/QuentinGruber/h1z1-server +// https://www.npmjs.com/package/h1z1-server +// +// Based on https://github.com/psemu/soe-network +// ====================================================================== + +import { ZoneServer2016 } from "../zoneserver"; +import { ZoneClient2016 as Client } from "../classes/zoneclient"; +import { DB_COLLECTIONS } from "../../../utils/enums"; + +export class ClanManager { + async handleClanCommand( + server: ZoneServer2016, + client: Client, + args: Array + ) { + if (args.length === 0) { + server.sendChatText( + client, + "Usage: /clan [arguments]" + ); + return; + } + + const subCommand = args[0].toLowerCase(); + const clansCollection = server._db?.collection(DB_COLLECTIONS.CLANS); + + // In-memory join requests with expiration + if (!server._joinRequests) { + server._joinRequests = new Map(); + } + const joinRequests = server._joinRequests; + + const currentClan = await server.getPlayerClan( + client.character.characterId + ); + + const isOwner = + currentClan?.owner.includes(client.character.characterId) ?? false; + + switch (subCommand) { + case "create": + if (args.length < 2) { + server.sendChatText(client, "Usage: /clan create "); + return; + } + const createTag = args[1]; + + if (createTag.length < 2 || createTag.length > 5) { + server.sendChatText( + client, + "Clan tag must be between 2 and 5 characters." + ); + return; + } + + // Check if the player is already in a clan + const existingClanCreate = await server.getPlayerClan( + client.character.characterId + ); + if (existingClanCreate) { + server.sendChatText( + client, + "You are already in a clan. Leave your current clan before creating a new one." + ); + return; + } + + // Create the new clan + await clansCollection.insertOne({ + tag: createTag, + owner: client.character.characterId, + members: [client.character.characterId] + }); + server.sendChatText(client, `Clan ${createTag} created successfully.`); + break; + + case "join": + if (args.length < 2) { + server.sendChatText(client, "Usage: /clan join "); + return; + } + const joinTag = args[1]; + + // Check if the player is already in a clan + const existingClanJoin = await server.getPlayerClan( + client.character.characterId + ); + if (existingClanJoin) { + server.sendChatText( + client, + "You are already in a clan. Leave your current clan before requesting to join a new one." + ); + return; + } + + // Check if the clan exists + const targetClan = await clansCollection.findOne({ tag: joinTag }); + if (!targetClan) { + server.sendChatText(client, `Clan ${joinTag} does not exist.`); + return; + } + + // Check if the client has already sent a join request + if (joinRequests.has(client.character.characterId)) { + server.sendChatText( + client, + "You have already sent a join request. Please wait for it to be processed." + ); + return; + } + + // Add a join request to in-memory storage + joinRequests.set(client.character.characterId, { + clanTag: joinTag, + characterId: client.character.characterId, + characterName: client.character.name, + timestamp: Date.now() + }); + + // Notify the clan owner + const ownerClient = server.getClientByCharId(targetClan.owner); + if (ownerClient) { + server.sendChatText( + ownerClient, + `${client.character.name} has requested to join your clan (${joinTag}). Use /clan accept or /clan yes to accept.` + ); + } + + server.sendChatText( + client, + `Your request to join clan ${joinTag} has been sent. It will expire in 2 minutes.` + ); + + // Schedule request removal after 2 minutes + setTimeout( + () => { + if ( + joinRequests.get(client.character.characterId)?.clanTag === + joinTag + ) { + joinRequests.delete(client.character.characterId); + server.sendChatText( + client, + `Your join request to clan ${joinTag} has expired.` + ); + } + }, + 2 * 60 * 1000 + ); + + break; + + case "accept": + case "yes": + if (args.length < 2) { + server.sendChatText( + client, + "Usage: /clan accept or /clan yes " + ); + return; + } + + if (!isOwner) { + server.sendChatText( + client, + "You are not the owner of the clan. Only the owner can accept members." + ); + return; + } + + const acceptName = args[1]; + const joinRequest = Array.from(joinRequests.values()).find( + (request: any) => + request.clanTag === currentClan?.tag && + request.characterName === acceptName + ); + + if (!joinRequest) { + server.sendChatText( + client, + `No valid join request from ${acceptName} found for your clan.` + ); + return; + } + + // Add the player to the clan + await clansCollection.updateOne( + { tag: currentClan?.tag ?? "" }, + { $addToSet: { members: joinRequest.characterId } } + ); + + // Remove the join request from memory + joinRequests.delete(joinRequest.characterId); + + // Notify the new member + const newMemberClient = server.getClientByCharId( + joinRequest.characterId + ); + if (newMemberClient) { + server.sendChatText( + newMemberClient, + `Your request to join clan ${currentClan?.tag ?? ""} has been accepted.` + ); + } + + server.sendChatText( + client, + `${acceptName} has been added to your clan.` + ); + break; + + case "decline": + case "no": + if (args.length < 2) { + server.sendChatText( + client, + "Usage: /clan decline or /clan no " + ); + return; + } + + if (!isOwner) { + server.sendChatText( + client, + "You are not the owner of the clan. Only the owner can decline members." + ); + return; + } + + const declineName = args[1]; + const declineRequest = Array.from(joinRequests.values()).find( + (request: any) => + request.clanTag === currentClan?.tag && + request.characterName === declineName + ); + + if (!declineRequest) { + server.sendChatText( + client, + `No valid join request from ${declineName} found for your clan.` + ); + return; + } + + // Remove the join request from memory + joinRequests.delete(declineRequest.characterId); + + // Notify the declined member + const declinedMemberClient = server.getClientByCharId( + declineRequest.characterId + ); + if (declinedMemberClient) { + server.sendChatText( + declinedMemberClient, + `Your request to join clan ${currentClan?.tag ?? ""} has been declined.` + ); + } + + server.sendChatText( + client, + `${declineName}'s request to join your clan has been declined.` + ); + break; + + case "cancel": + // Check if the client has a pending join request + if (!joinRequests.has(client.character.characterId)) { + server.sendChatText( + client, + "You have no pending join request to cancel." + ); + return; + } + + // Remove the join request from memory + joinRequests.delete(client.character.characterId); + + server.sendChatText(client, "Your join request has been cancelled."); + break; + + case "leave": + if (isOwner) { + // Owner cannot leave the clan without disbanding it + server.sendChatText( + client, + "You are the owner of the clan. Use /clan disband to disband the clan before leaving." + ); + return; + } + // If not the owner, simply leave the clan + await clansCollection.updateOne( + { tag: currentClan?.tag ?? "" }, + { $pull: { members: client.character.characterId } as any } + ); + server.sendChatText(client, "You have left the clan successfully."); + break; + + case "disband": + if (!isOwner) { + server.sendChatText( + client, + "You are not the owner of the clan. Only the owner can disband the clan." + ); + return; + } + // Delete the clan regardless of the number of members + await clansCollection.deleteOne({ + owner: client.character.characterId + }); + + server.sendChatText(client, "Clan disbanded successfully."); + break; + default: + server.sendChatText( + client, + "Unknown subcommand. Usage: /clan [arguments]" + ); + break; + } + } +} diff --git a/src/servers/ZoneServer2016/zoneserver.ts b/src/servers/ZoneServer2016/zoneserver.ts index 136be3a3e..8d4441890 100644 --- a/src/servers/ZoneServer2016/zoneserver.ts +++ b/src/servers/ZoneServer2016/zoneserver.ts @@ -231,6 +231,7 @@ import { RconMessageType } from "./managers/rconmanager"; import { GroupManager } from "./managers/groupmanager"; +import { ClanManager } from "./managers/clanmanager"; import { SpeedTreeManager } from "./managers/speedtreemanager"; import { ConstructionManager } from "./managers/constructionmanager"; import { FairPlayManager } from "./managers/fairplaymanager"; @@ -307,6 +308,16 @@ export class ZoneServer2016 extends EventEmitter { /** Total amount of clients on the server */ readonly _clients: { [characterId: string]: Client } = {}; + _joinRequests: Map< + string, + { + clanTag: string; + characterId: string; + characterName: string; + timestamp: number; + } + > = new Map(); + /** Global dictionaries for all entities */ _characters: EntityDictionary = {}; _npcs: EntityDictionary = {}; @@ -399,6 +410,7 @@ export class ZoneServer2016 extends EventEmitter { chatManager: ChatManager; rconManager: RConManager; groupManager: GroupManager; + clanManager: ClanManager; speedtreeManager: SpeedTreeManager; constructionManager: ConstructionManager; fairPlayManager: FairPlayManager; @@ -512,6 +524,7 @@ export class ZoneServer2016 extends EventEmitter { this.chatManager = new ChatManager(); this.rconManager = new RConManager(); this.groupManager = new GroupManager(); + this.clanManager = new ClanManager(); this.speedtreeManager = new SpeedTreeManager(); this.rewardManager = new RewardManager(this); this.constructionManager = new ConstructionManager(); @@ -712,6 +725,12 @@ export class ZoneServer2016 extends EventEmitter { } } + async getPlayerClan(characterId: string): Promise { + return await this._db + ?.collection(DB_COLLECTIONS.CLANS) + .findOne({ members: characterId }); + } + async reloadCommandCache() { delete require.cache[require.resolve("./handlers/commands/commandhandler")]; const CommandHandler = ( @@ -1673,6 +1692,9 @@ export class ZoneServer2016 extends EventEmitter { client.character.playTime = savedCharacter.playTime || 0; client.character.lastDropPlaytime = savedCharacter.lastDropPlayTime || 0; + // Load and set the clan data + await client.character.getClan(this); + let newCharacter = false; if (_.isEqual(savedCharacter.position, [0, 0, 0, 1])) { // if position hasn't changed diff --git a/src/utils/enums.ts b/src/utils/enums.ts index 8c1750972..196352868 100644 --- a/src/utils/enums.ts +++ b/src/utils/enums.ts @@ -54,6 +54,7 @@ export enum DB_COLLECTIONS { CONSTRUCTION = "construction", CROPS = "crops", TRAPS = "traps", + CLANS = "clans", FINGERPRINTS = "fingerprints", PROPS = "props", SERVERS = "servers",