diff --git a/src/models/fika/config/IFikaConfigHeadless.ts b/src/models/fika/config/IFikaConfigHeadless.ts index 4a1d6c8a..9cd00ea7 100644 --- a/src/models/fika/config/IFikaConfigHeadless.ts +++ b/src/models/fika/config/IFikaConfigHeadless.ts @@ -6,4 +6,6 @@ export interface IFikaConfigHeadless { generate: boolean; forceIp: string; }; + /** If this is true, sets the headless's average level to that of the entire lobby, if set to false it will take the level of the requester. */ + setLevelToAverageOfLobby: boolean; } diff --git a/src/models/fika/headless/IHeadlessClientInfo.ts b/src/models/fika/headless/IHeadlessClientInfo.ts index b127f357..341f6c95 100644 --- a/src/models/fika/headless/IHeadlessClientInfo.ts +++ b/src/models/fika/headless/IHeadlessClientInfo.ts @@ -6,6 +6,8 @@ export interface IHeadlessClientInfo { webSocket: SPTWebSocket; /** State of the headless client */ state: EHeadlessStatus; + /** The players that are playing on this headless client, only set if the state is IN_RAID */ + players?: string[]; /** SessionID of the person who has requested the headless client, it will only ever be set if the status is IN_RAID */ requesterSessionID?: string; /** Allows for checking if the requester has been notified the match has started through the requester WebSocket so he can auto-join */ diff --git a/src/services/FikaMatchService.ts b/src/services/FikaMatchService.ts index 3bc8f2ea..93afbd08 100644 --- a/src/services/FikaMatchService.ts +++ b/src/services/FikaMatchService.ts @@ -200,7 +200,7 @@ export class FikaMatchService { locationData: locationData, status: EFikaMatchStatus.LOADING, timeout: 0, - players: new Map(), + players: new Map(), gameVersion: data.gameVersion, fikaVersion: data.fikaVersion, side: data.side, @@ -316,6 +316,10 @@ export class FikaMatchService { this.fikaInsuranceService.addPlayerToMatchId(matchId, playerId); + if (this.fikaHeadlessService.isHeadlessClient(matchId)) { + this.fikaHeadlessService.addPlayerToHeadlessMatch(matchId, playerId); + } + this.fikaPresenceService.updatePlayerPresence(playerId, this.fikaPresenceService.generateSetPresence(EFikaPlayerPresences.IN_RAID, this.fikaPresenceService.generateRaidPresence(match.locationData.Id, match.side, match.time))); } diff --git a/src/services/headless/FikaHeadlessService.ts b/src/services/headless/FikaHeadlessService.ts index 5b096e7c..75c0387d 100644 --- a/src/services/headless/FikaHeadlessService.ts +++ b/src/services/headless/FikaHeadlessService.ts @@ -2,6 +2,7 @@ import { inject, injectable } from "tsyringe"; import type { ILogger } from "@spt/models/spt/utils/ILogger"; +import { SaveServer } from "@spt/servers/SaveServer"; import { SPTWebSocket } from "@spt/servers/ws/SPTWebsocket"; import { EFikaHeadlessWSMessageTypes } from "../../models/enums/EFikaHeadlessWSMessageTypes"; import { EHeadlessStatus } from "../../models/enums/EHeadlessStatus"; @@ -9,6 +10,7 @@ import { IHeadlessClientInfo } from "../../models/fika/headless/IHeadlessClientI import { IStartHeadlessRequest } from "../../models/fika/routes/raid/headless/IStartHeadlessRequest"; import { IHeadlessRequesterJoinRaid } from "../../models/fika/websocket/headless/IHeadlessRequesterJoinRaid"; import { IStartHeadlessRaid } from "../../models/fika/websocket/headless/IHeadlessStartRaid"; +import { FikaConfig } from "../../utils/FikaConfig"; import { FikaHeadlessRequesterWebSocket } from "../../websockets/FikaHeadlessRequesterWebSocket"; import { FikaHeadlessProfileService } from "./FikaHeadlessProfileService"; @@ -19,7 +21,9 @@ export class FikaHeadlessService { constructor( @inject("FikaHeadlessProfileService") protected fikaHeadlessProfileService: FikaHeadlessProfileService, @inject("FikaHeadlessRequesterWebSocket") protected fikaHeadlessRequesterWebSocket: FikaHeadlessRequesterWebSocket, + @inject("SaveServer") protected saveServer: SaveServer, @inject("WinstonLogger") protected logger: ILogger, + @inject("FikaConfig") protected fikaConfig: FikaConfig, ) {} public addHeadlessClient(sessionID: string, webSocket: SPTWebSocket): void { @@ -48,6 +52,7 @@ export class FikaHeadlessService { } headlessClient.state = EHeadlessStatus.IN_RAID; + headlessClient.players = []; headlessClient.requesterSessionID = requesterSessionID; headlessClient.hasNotifiedRequester = false; @@ -88,6 +93,58 @@ export class FikaHeadlessService { headlessClient.hasNotifiedRequester = true; } + public addPlayerToHeadlessMatch(headlessClientId: string, sessionID: string): void { + const headlessClient = this.headlessClients.get(headlessClientId); + + if (!headlessClient || headlessClient?.state != EHeadlessStatus.IN_RAID) { + return; + } + + if (headlessClientId === sessionID) { + return; + } + + headlessClient.players.push(sessionID); + + if (!this.fikaConfig.getConfig().headless.setLevelToAverageOfLobby) { + // Doing this everytime is unecessary if we're not setting the average level so only set it once the original requester of the headless joins. + if (headlessClient.requesterSessionID === sessionID) { + this.setHeadlessLevel(headlessClientId); + } + } else { + this.setHeadlessLevel(headlessClientId); + } + } + + public setHeadlessLevel(headlessClientId: string): void { + const headlessClient = this.headlessClients.get(headlessClientId); + + if (!headlessClient || headlessClient?.state != EHeadlessStatus.IN_RAID) { + return; + } + + const headlessProfile = this.saveServer.getProfile(headlessClientId); + + // Set level of headless to that of the requester. + if (!this.fikaConfig.getConfig().headless.setLevelToAverageOfLobby) { + headlessProfile.characters.pmc.Info.Level = this.saveServer.getProfile(headlessClient.requesterSessionID).characters.pmc.Info.Level; + return; + } + + let baseHeadlessLevel = 0; + let players = headlessClient.players.length; + + for (const sessionID of headlessClient.players) { + baseHeadlessLevel += this.saveServer.getProfile(sessionID).characters.pmc.Info.Level; + } + + baseHeadlessLevel = Math.round(baseHeadlessLevel / players); + + this.logger.debug(`[${headlessClientId}] Setting headless level to: ${baseHeadlessLevel} | Players: ${players}`); + + headlessProfile.characters.pmc.Info.Level = baseHeadlessLevel; + } + /** End the raid for the specified headless client, sets the state back to READY so that he can be requested to host again. */ public endHeadlessRaid(headlessClientId: string): void { const headlessClient = this.headlessClients.get(headlessClientId); @@ -97,6 +154,7 @@ export class FikaHeadlessService { } headlessClient.state = EHeadlessStatus.READY; + headlessClient.players = null; headlessClient.requesterSessionID = null; headlessClient.hasNotifiedRequester = null; } diff --git a/src/utils/FikaConfig.ts b/src/utils/FikaConfig.ts index 68941c1c..219fa0c6 100644 --- a/src/utils/FikaConfig.ts +++ b/src/utils/FikaConfig.ts @@ -38,7 +38,7 @@ export class FikaConfig { sharedQuestProgression: false, canEditRaidSettings: true, enableTransits: true, - anyoneCanStartRaid: false + anyoneCanStartRaid: false, }, server: { SPT: { @@ -70,6 +70,7 @@ export class FikaConfig { generate: true, forceIp: "", }, + setLevelToAverageOfLobby: true, }, background: { enable: true,