324 lines
12 KiB
TypeScript
324 lines
12 KiB
TypeScript
import { inject, injectable } from "tsyringe";
|
|
|
|
import { ApplicationContext } from "@spt-aki/context/ApplicationContext";
|
|
import { ContextVariableType } from "@spt-aki/context/ContextVariableType";
|
|
import { LootGenerator } from "@spt-aki/generators/LootGenerator";
|
|
import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper";
|
|
import { TraderHelper } from "@spt-aki/helpers/TraderHelper";
|
|
import { IPmcData } from "@spt-aki/models/eft/common/IPmcData";
|
|
import { Item } from "@spt-aki/models/eft/common/tables/IItem";
|
|
import { ICreateGroupRequestData } from "@spt-aki/models/eft/match/ICreateGroupRequestData";
|
|
import { IEndOfflineRaidRequestData } from "@spt-aki/models/eft/match/IEndOfflineRaidRequestData";
|
|
import { IGetGroupStatusRequestData } from "@spt-aki/models/eft/match/IGetGroupStatusRequestData";
|
|
import { IGetProfileRequestData } from "@spt-aki/models/eft/match/IGetProfileRequestData";
|
|
import { IGetRaidConfigurationRequestData } from "@spt-aki/models/eft/match/IGetRaidConfigurationRequestData";
|
|
import { IJoinMatchRequestData } from "@spt-aki/models/eft/match/IJoinMatchRequestData";
|
|
import { IJoinMatchResult } from "@spt-aki/models/eft/match/IJoinMatchResult";
|
|
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
|
|
import { MessageType } from "@spt-aki/models/enums/MessageType";
|
|
import { Traders } from "@spt-aki/models/enums/Traders";
|
|
import { IInRaidConfig } from "@spt-aki/models/spt/config/IInRaidConfig";
|
|
import { IMatchConfig } from "@spt-aki/models/spt/config/IMatchConfig";
|
|
import { IPmcConfig } from "@spt-aki/models/spt/config/IPmcConfig";
|
|
import { ITraderConfig } from "@spt-aki/models/spt/config/ITraderConfig";
|
|
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
|
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
|
|
import { SaveServer } from "@spt-aki/servers/SaveServer";
|
|
import { BotGenerationCacheService } from "@spt-aki/services/BotGenerationCacheService";
|
|
import { BotLootCacheService } from "@spt-aki/services/BotLootCacheService";
|
|
import { MailSendService } from "@spt-aki/services/MailSendService";
|
|
import { MatchLocationService } from "@spt-aki/services/MatchLocationService";
|
|
import { ProfileSnapshotService } from "@spt-aki/services/ProfileSnapshotService";
|
|
import { HashUtil } from "@spt-aki/utils/HashUtil";
|
|
import { RandomUtil } from "@spt-aki/utils/RandomUtil";
|
|
import { TimeUtil } from "@spt-aki/utils/TimeUtil";
|
|
|
|
@injectable()
|
|
export class MatchController
|
|
{
|
|
protected matchConfig: IMatchConfig;
|
|
protected inraidConfig: IInRaidConfig;
|
|
protected traderConfig: ITraderConfig;
|
|
protected pmcConfig: IPmcConfig;
|
|
|
|
constructor(
|
|
@inject("WinstonLogger") protected logger: ILogger,
|
|
@inject("SaveServer") protected saveServer: SaveServer,
|
|
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
|
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
|
@inject("HashUtil") protected hashUtil: HashUtil,
|
|
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
|
@inject("MatchLocationService") protected matchLocationService: MatchLocationService,
|
|
@inject("TraderHelper") protected traderHelper: TraderHelper,
|
|
@inject("BotLootCacheService") protected botLootCacheService: BotLootCacheService,
|
|
@inject("ConfigServer") protected configServer: ConfigServer,
|
|
@inject("ProfileSnapshotService") protected profileSnapshotService: ProfileSnapshotService,
|
|
@inject("BotGenerationCacheService") protected botGenerationCacheService: BotGenerationCacheService,
|
|
@inject("MailSendService") protected mailSendService: MailSendService,
|
|
@inject("LootGenerator") protected lootGenerator: LootGenerator,
|
|
@inject("ApplicationContext") protected applicationContext: ApplicationContext
|
|
)
|
|
{
|
|
this.matchConfig = this.configServer.getConfig(ConfigTypes.MATCH);
|
|
this.inraidConfig = this.configServer.getConfig(ConfigTypes.IN_RAID);
|
|
this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER);
|
|
this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC);
|
|
}
|
|
|
|
public getEnabled(): boolean
|
|
{
|
|
return this.matchConfig.enabled;
|
|
}
|
|
|
|
/** Handle raid/profile/list */
|
|
public getProfile(info: IGetProfileRequestData): IPmcData[]
|
|
{
|
|
if (info.profileId.includes("pmcAID"))
|
|
{
|
|
return this.profileHelper.getCompleteProfile(info.profileId.replace("pmcAID", "AID"));
|
|
}
|
|
|
|
if (info.profileId.includes("scavAID"))
|
|
{
|
|
return this.profileHelper.getCompleteProfile(info.profileId.replace("scavAID", "AID"));
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
/** Handle client/match/group/create */
|
|
public createGroup(sessionID: string, info: ICreateGroupRequestData): any
|
|
{
|
|
return this.matchLocationService.createGroup(sessionID, info);
|
|
}
|
|
|
|
/** Handle client/match/group/delete */
|
|
public deleteGroup(info: any): void
|
|
{
|
|
this.matchLocationService.deleteGroup(info);
|
|
}
|
|
|
|
/** Handle match/group/start_game */
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
public joinMatch(info: IJoinMatchRequestData, sessionId: string): IJoinMatchResult
|
|
{
|
|
const output: IJoinMatchResult = {
|
|
maxPveCountExceeded: false,
|
|
profiles: []
|
|
};
|
|
|
|
// get list of players joining into the match
|
|
output.profiles.push({
|
|
profileid: "TODO",
|
|
profileToken: "TODO",
|
|
status: "MatchWait",
|
|
sid: "",
|
|
ip: "",
|
|
port: 0,
|
|
version: "live",
|
|
location: "TODO get location",
|
|
raidMode: "Online",
|
|
mode: "deathmatch",
|
|
shortid: null,
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
additional_info: null
|
|
});
|
|
|
|
return output;
|
|
}
|
|
|
|
/** Handle client/match/group/status */
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
public getGroupStatus(info: IGetGroupStatusRequestData): any
|
|
{
|
|
return {
|
|
players: [],
|
|
maxPveCountExceeded: false
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Handle /client/raid/configuration
|
|
* @param request Raid config request
|
|
* @param sessionID Session id
|
|
*/
|
|
public startOfflineRaid(request: IGetRaidConfigurationRequestData, sessionID: string): void
|
|
{
|
|
// Store request data for access during bot generation
|
|
this.applicationContext.addValue(ContextVariableType.RAID_CONFIGURATION, request);
|
|
|
|
//TODO: add code to strip PMC of equipment now they've started the raid
|
|
|
|
// Set pmcs to difficulty set in pre-raid screen if override in bot config isnt enabled
|
|
if (!this.pmcConfig.useDifficultyOverride)
|
|
{
|
|
this.pmcConfig.difficulty = this.convertDifficultyDropdownIntoBotDifficulty(request.wavesSettings.botDifficulty);
|
|
}
|
|
|
|
// Store the profile as-is for later use on the post-raid exp screen
|
|
const currentProfile = this.saveServer.getProfile(sessionID);
|
|
this.profileSnapshotService.storeProfileSnapshot(sessionID, currentProfile);
|
|
}
|
|
|
|
/**
|
|
* Convert a difficulty value from pre-raid screen to a bot difficulty
|
|
* @param botDifficulty dropdown difficulty value
|
|
* @returns bot difficulty
|
|
*/
|
|
protected convertDifficultyDropdownIntoBotDifficulty(botDifficulty: string): string
|
|
{
|
|
// Edge case medium - must be altered
|
|
if (botDifficulty.toLowerCase() === "medium")
|
|
{
|
|
return "normal";
|
|
}
|
|
|
|
return botDifficulty;
|
|
}
|
|
|
|
/** Handle client/match/offline/end */
|
|
public endOfflineRaid(info: IEndOfflineRaidRequestData, sessionId: string): void
|
|
{
|
|
const pmcData: IPmcData = this.profileHelper.getPmcProfile(sessionId);
|
|
const extractName = info.exitName;
|
|
|
|
// Save time spent in raid
|
|
pmcData.Stats.Eft.TotalInGameTime += info.raidSeconds;
|
|
|
|
// Clean up cached bots now raid is over
|
|
this.botGenerationCacheService.clearStoredBots();
|
|
|
|
// Clear bot loot cache
|
|
this.botLootCacheService.clearCache();
|
|
|
|
if (this.extractWasViaCar(extractName))
|
|
{
|
|
this.handleCarExtract(extractName, pmcData, sessionId);
|
|
}
|
|
|
|
if (extractName && this.extractWasViaCoop(extractName) && this.traderConfig.fence.coopExtractGift.sendGift)
|
|
{
|
|
this.sendCoopTakenFenceMessage(sessionId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Did player take a COOP extract
|
|
* @param extractName Name of extract player took
|
|
* @returns True if coop extract
|
|
*/
|
|
protected extractWasViaCoop(extractName: string): boolean
|
|
{
|
|
// No extract name, not a coop extract
|
|
if (!extractName)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return (this.inraidConfig.coopExtracts.includes(extractName.trim()));
|
|
}
|
|
|
|
protected sendCoopTakenFenceMessage(sessionId: string): void
|
|
{
|
|
// Generate reward for taking coop extract
|
|
const loot = this.lootGenerator.createRandomLoot(this.traderConfig.fence.coopExtractGift);
|
|
const mailableLoot: Item[] = [];
|
|
|
|
const parentId = this.hashUtil.generate();
|
|
for (const item of loot)
|
|
{
|
|
mailableLoot.push(
|
|
{
|
|
_id: item.id,
|
|
_tpl: item.tpl,
|
|
slotId: "main",
|
|
parentId: parentId,
|
|
upd: {
|
|
StackObjectsCount: item.stackCount,
|
|
SpawnedInSession: true
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
// Send message from fence giving player reward generated above
|
|
this.mailSendService.sendLocalisedNpcMessageToPlayer(
|
|
sessionId,
|
|
this.traderHelper.getTraderById(Traders.FENCE),
|
|
MessageType.MESSAGE_WITH_ITEMS,
|
|
this.randomUtil.getArrayValue(this.traderConfig.fence.coopExtractGift.messageLocaleIds),
|
|
mailableLoot,
|
|
this.timeUtil.getHoursAsSeconds(this.traderConfig.fence.coopExtractGift.giftExpiryHours)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Was extract by car
|
|
* @param extractName name of extract
|
|
* @returns true if car extract
|
|
*/
|
|
protected extractWasViaCar(extractName: string): boolean
|
|
{
|
|
// exit name is null on death
|
|
if (!extractName)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (extractName.toLowerCase().includes("v-ex"))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return this.inraidConfig.carExtracts.includes(extractName.trim());
|
|
}
|
|
|
|
/**
|
|
* Handle when a player extracts using a car - Add rep to fence
|
|
* @param extractName name of the extract used
|
|
* @param pmcData Player profile
|
|
* @param sessionId Session id
|
|
*/
|
|
protected handleCarExtract(extractName: string, pmcData: IPmcData, sessionId: string): void
|
|
{
|
|
// Ensure key exists for extract
|
|
if (!(extractName in pmcData.CarExtractCounts))
|
|
{
|
|
pmcData.CarExtractCounts[extractName] = 0;
|
|
}
|
|
|
|
// Increment extract count value
|
|
pmcData.CarExtractCounts[extractName] += 1;
|
|
|
|
const fenceId: string = Traders.FENCE;
|
|
this.updateFenceStandingInProfile(pmcData, fenceId, extractName);
|
|
|
|
this.traderHelper.lvlUp(fenceId, pmcData);
|
|
pmcData.TradersInfo[fenceId].loyaltyLevel = Math.max(pmcData.TradersInfo[fenceId].loyaltyLevel, 1);
|
|
|
|
this.logger.debug(`Car extract: ${extractName} used, total times taken: ${pmcData.CarExtractCounts[extractName]}`);
|
|
}
|
|
|
|
/**
|
|
* Update players fence trader standing value in profile
|
|
* @param pmcData Player profile
|
|
* @param fenceId Id of fence trader
|
|
* @param extractName Name of extract used
|
|
*/
|
|
protected updateFenceStandingInProfile(pmcData: IPmcData, fenceId: string, extractName: string): void
|
|
{
|
|
let fenceStanding = Number(pmcData.TradersInfo[fenceId].standing);
|
|
|
|
// Not exact replica of Live behaviour
|
|
// Simplified for now, no real reason to do the whole (unconfirmed) extra 0.01 standing per day regeneration mechanic
|
|
const baseGain: number = this.inraidConfig.carExtractBaseStandingGain;
|
|
const extractCount: number = pmcData.CarExtractCounts[extractName];
|
|
|
|
fenceStanding += Math.max(baseGain / extractCount, 0.01);
|
|
|
|
// Ensure fence loyalty level is not above/below the range -7 - 15
|
|
const newFenceStanding = Math.min(Math.max(fenceStanding, -7), 15);
|
|
this.logger.debug(`Old vs new fence standing: ${pmcData.TradersInfo[fenceId].standing}, ${newFenceStanding}`);
|
|
pmcData.TradersInfo[fenceId].standing = newFenceStanding;
|
|
}
|
|
} |