Created new LocationLifecycleService
service. Moved raid start/end code into it.
Created temp `LegacyLocationLifecycleService` class to hold old raid start/end code until we can safely delete it
This commit is contained in:
parent
1c3fb5f926
commit
ca52f6d549
@ -4,7 +4,6 @@ import { IEmptyRequestData } from "@spt/models/eft/common/IEmptyRequestData";
|
|||||||
import { IGetBodyResponseData } from "@spt/models/eft/httpResponse/IGetBodyResponseData";
|
import { IGetBodyResponseData } from "@spt/models/eft/httpResponse/IGetBodyResponseData";
|
||||||
import { INullResponseData } from "@spt/models/eft/httpResponse/INullResponseData";
|
import { INullResponseData } from "@spt/models/eft/httpResponse/INullResponseData";
|
||||||
import { IEndLocalRaidRequestData } from "@spt/models/eft/match/IEndLocalRaidRequestData";
|
import { IEndLocalRaidRequestData } from "@spt/models/eft/match/IEndLocalRaidRequestData";
|
||||||
import { IEndOfflineRaidRequestData } from "@spt/models/eft/match/IEndOfflineRaidRequestData";
|
|
||||||
import { IGetRaidConfigurationRequestData } from "@spt/models/eft/match/IGetRaidConfigurationRequestData";
|
import { IGetRaidConfigurationRequestData } from "@spt/models/eft/match/IGetRaidConfigurationRequestData";
|
||||||
import { IGroupCharacter } from "@spt/models/eft/match/IGroupCharacter";
|
import { IGroupCharacter } from "@spt/models/eft/match/IGroupCharacter";
|
||||||
import { IMatchGroupCurrentResponse } from "@spt/models/eft/match/IMatchGroupCurrentResponse";
|
import { IMatchGroupCurrentResponse } from "@spt/models/eft/match/IMatchGroupCurrentResponse";
|
||||||
@ -233,17 +232,6 @@ export class MatchCallbacks
|
|||||||
return this.httpResponse.nullResponse();
|
return this.httpResponse.nullResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Handle client/match/offline/end */
|
|
||||||
public endOfflineRaid(
|
|
||||||
url: string,
|
|
||||||
info: IEndOfflineRaidRequestData,
|
|
||||||
sessionID: string,
|
|
||||||
): INullResponseData
|
|
||||||
{
|
|
||||||
this.matchController.endOfflineRaid(info, sessionID);
|
|
||||||
return this.httpResponse.nullResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Handle client/raid/configuration */
|
/** Handle client/raid/configuration */
|
||||||
public getRaidConfiguration(
|
public getRaidConfiguration(
|
||||||
url: string,
|
url: string,
|
||||||
@ -251,7 +239,7 @@ export class MatchCallbacks
|
|||||||
sessionID: string,
|
sessionID: string,
|
||||||
): INullResponseData
|
): INullResponseData
|
||||||
{
|
{
|
||||||
this.matchController.startOfflineRaid(info, sessionID);
|
this.matchController.configureOfflineRaid(info, sessionID);
|
||||||
return this.httpResponse.nullResponse();
|
return this.httpResponse.nullResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,21 +1,7 @@
|
|||||||
import { inject, injectable } from "tsyringe";
|
import { inject, injectable } from "tsyringe";
|
||||||
import { ApplicationContext } from "@spt/context/ApplicationContext";
|
import { ApplicationContext } from "@spt/context/ApplicationContext";
|
||||||
import { ContextVariableType } from "@spt/context/ContextVariableType";
|
import { ContextVariableType } from "@spt/context/ContextVariableType";
|
||||||
import { InraidController } from "@spt/controllers/InraidController";
|
import { IEndLocalRaidRequestData } from "@spt/models/eft/match/IEndLocalRaidRequestData";
|
||||||
import { LocationController } from "@spt/controllers/LocationController";
|
|
||||||
import { LocationLootGenerator } from "@spt/generators/LocationLootGenerator";
|
|
||||||
import { LootGenerator } from "@spt/generators/LootGenerator";
|
|
||||||
import { PlayerScavGenerator } from "@spt/generators/PlayerScavGenerator";
|
|
||||||
import { HealthHelper } from "@spt/helpers/HealthHelper";
|
|
||||||
import { InRaidHelper } from "@spt/helpers/InRaidHelper";
|
|
||||||
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
|
||||||
import { TraderHelper } from "@spt/helpers/TraderHelper";
|
|
||||||
import { ILocationBase } from "@spt/models/eft/common/ILocationBase";
|
|
||||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
|
||||||
import { Common } from "@spt/models/eft/common/tables/IBotBase";
|
|
||||||
import { Item } from "@spt/models/eft/common/tables/IItem";
|
|
||||||
import { IEndLocalRaidRequestData, IEndRaidResult } from "@spt/models/eft/match/IEndLocalRaidRequestData";
|
|
||||||
import { IEndOfflineRaidRequestData } from "@spt/models/eft/match/IEndOfflineRaidRequestData";
|
|
||||||
import { IGetRaidConfigurationRequestData } from "@spt/models/eft/match/IGetRaidConfigurationRequestData";
|
import { IGetRaidConfigurationRequestData } from "@spt/models/eft/match/IGetRaidConfigurationRequestData";
|
||||||
import { IMatchGroupStartGameRequest } from "@spt/models/eft/match/IMatchGroupStartGameRequest";
|
import { IMatchGroupStartGameRequest } from "@spt/models/eft/match/IMatchGroupStartGameRequest";
|
||||||
import { IMatchGroupStatusRequest } from "@spt/models/eft/match/IMatchGroupStatusRequest";
|
import { IMatchGroupStatusRequest } from "@spt/models/eft/match/IMatchGroupStatusRequest";
|
||||||
@ -24,84 +10,35 @@ import { IProfileStatusResponse } from "@spt/models/eft/match/IProfileStatusResp
|
|||||||
import { IStartLocalRaidRequestData } from "@spt/models/eft/match/IStartLocalRaidRequestData";
|
import { IStartLocalRaidRequestData } from "@spt/models/eft/match/IStartLocalRaidRequestData";
|
||||||
import { IStartLocalRaidResponseData } from "@spt/models/eft/match/IStartLocalRaidResponseData";
|
import { IStartLocalRaidResponseData } from "@spt/models/eft/match/IStartLocalRaidResponseData";
|
||||||
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
||||||
import { MessageType } from "@spt/models/enums/MessageType";
|
|
||||||
import { Traders } from "@spt/models/enums/Traders";
|
|
||||||
import { IHideoutConfig } from "@spt/models/spt/config/IHideoutConfig";
|
|
||||||
import { IInRaidConfig } from "@spt/models/spt/config/IInRaidConfig";
|
|
||||||
import { ILocationConfig } from "@spt/models/spt/config/ILocationConfig";
|
|
||||||
import { IMatchConfig } from "@spt/models/spt/config/IMatchConfig";
|
import { IMatchConfig } from "@spt/models/spt/config/IMatchConfig";
|
||||||
import { IPmcConfig } from "@spt/models/spt/config/IPmcConfig";
|
import { IPmcConfig } from "@spt/models/spt/config/IPmcConfig";
|
||||||
import { IRagfairConfig } from "@spt/models/spt/config/IRagfairConfig";
|
|
||||||
import { ITraderConfig } from "@spt/models/spt/config/ITraderConfig";
|
|
||||||
import { IRaidChanges } from "@spt/models/spt/location/IRaidChanges";
|
|
||||||
import { ILogger } from "@spt/models/spt/utils/ILogger";
|
import { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||||
import { ConfigServer } from "@spt/servers/ConfigServer";
|
import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||||
import { SaveServer } from "@spt/servers/SaveServer";
|
import { SaveServer } from "@spt/servers/SaveServer";
|
||||||
import { BotGenerationCacheService } from "@spt/services/BotGenerationCacheService";
|
import { LocationLifecycleService } from "@spt/services/LocationLifecycleService";
|
||||||
import { BotLootCacheService } from "@spt/services/BotLootCacheService";
|
|
||||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
|
||||||
import { InsuranceService } from "@spt/services/InsuranceService";
|
|
||||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
|
||||||
import { MailSendService } from "@spt/services/MailSendService";
|
|
||||||
import { MatchBotDetailsCacheService } from "@spt/services/MatchBotDetailsCacheService";
|
|
||||||
import { MatchLocationService } from "@spt/services/MatchLocationService";
|
import { MatchLocationService } from "@spt/services/MatchLocationService";
|
||||||
import { PmcChatResponseService } from "@spt/services/PmcChatResponseService";
|
|
||||||
import { ProfileSnapshotService } from "@spt/services/ProfileSnapshotService";
|
import { ProfileSnapshotService } from "@spt/services/ProfileSnapshotService";
|
||||||
import { RaidTimeAdjustmentService } from "@spt/services/RaidTimeAdjustmentService";
|
|
||||||
import { ICloner } from "@spt/utils/cloners/ICloner";
|
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||||
import { HashUtil } from "@spt/utils/HashUtil";
|
|
||||||
import { RandomUtil } from "@spt/utils/RandomUtil";
|
|
||||||
import { TimeUtil } from "@spt/utils/TimeUtil";
|
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class MatchController
|
export class MatchController
|
||||||
{
|
{
|
||||||
protected matchConfig: IMatchConfig;
|
protected matchConfig: IMatchConfig;
|
||||||
protected inRaidConfig: IInRaidConfig;
|
|
||||||
protected traderConfig: ITraderConfig;
|
|
||||||
protected pmcConfig: IPmcConfig;
|
protected pmcConfig: IPmcConfig;
|
||||||
protected ragfairConfig: IRagfairConfig;
|
|
||||||
protected hideoutConfig: IHideoutConfig;
|
|
||||||
protected locationConfig: ILocationConfig;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@inject("PrimaryLogger") protected logger: ILogger,
|
@inject("PrimaryLogger") protected logger: ILogger,
|
||||||
@inject("SaveServer") protected saveServer: SaveServer,
|
@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("DatabaseService") protected databaseService: DatabaseService,
|
|
||||||
@inject("LocationController") protected locationController: LocationController,
|
|
||||||
@inject("InraidController") protected inRaidController: InraidController,
|
|
||||||
@inject("InRaidHelper") protected inRaidHelper: InRaidHelper,
|
|
||||||
@inject("HealthHelper") protected healthHelper: HealthHelper,
|
|
||||||
@inject("MatchLocationService") protected matchLocationService: MatchLocationService,
|
@inject("MatchLocationService") protected matchLocationService: MatchLocationService,
|
||||||
@inject("MatchBotDetailsCacheService") protected matchBotDetailsCacheService: MatchBotDetailsCacheService,
|
|
||||||
@inject("PmcChatResponseService") protected pmcChatResponseService: PmcChatResponseService,
|
|
||||||
@inject("PlayerScavGenerator") protected playerScavGenerator: PlayerScavGenerator,
|
|
||||||
@inject("TraderHelper") protected traderHelper: TraderHelper,
|
|
||||||
@inject("BotLootCacheService") protected botLootCacheService: BotLootCacheService,
|
|
||||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
|
||||||
@inject("InsuranceService") protected insuranceService: InsuranceService,
|
|
||||||
@inject("ConfigServer") protected configServer: ConfigServer,
|
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||||
@inject("ProfileSnapshotService") protected profileSnapshotService: ProfileSnapshotService,
|
@inject("ProfileSnapshotService") protected profileSnapshotService: ProfileSnapshotService,
|
||||||
@inject("BotGenerationCacheService") protected botGenerationCacheService: BotGenerationCacheService,
|
|
||||||
@inject("MailSendService") protected mailSendService: MailSendService,
|
|
||||||
@inject("RaidTimeAdjustmentService") protected raidTimeAdjustmentService: RaidTimeAdjustmentService,
|
|
||||||
@inject("LootGenerator") protected lootGenerator: LootGenerator,
|
|
||||||
@inject("ApplicationContext") protected applicationContext: ApplicationContext,
|
@inject("ApplicationContext") protected applicationContext: ApplicationContext,
|
||||||
@inject("LocationLootGenerator") protected locationLootGenerator: LocationLootGenerator,
|
@inject("LocationLifecycleService") protected locationLifecycleService: LocationLifecycleService,
|
||||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.matchConfig = this.configServer.getConfig(ConfigTypes.MATCH);
|
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);
|
this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC);
|
||||||
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
|
|
||||||
this.hideoutConfig = this.configServer.getConfig(ConfigTypes.HIDEOUT);
|
|
||||||
this.locationConfig = this.configServer.getConfig(ConfigTypes.LOCATION);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getEnabled(): boolean
|
public getEnabled(): boolean
|
||||||
@ -153,7 +90,7 @@ export class MatchController
|
|||||||
* @param request Raid config request
|
* @param request Raid config request
|
||||||
* @param sessionID Session id
|
* @param sessionID Session id
|
||||||
*/
|
*/
|
||||||
public startOfflineRaid(request: IGetRaidConfigurationRequestData, sessionID: string): void
|
public configureOfflineRaid(request: IGetRaidConfigurationRequestData, sessionID: string): void
|
||||||
{
|
{
|
||||||
// Store request data for access during bot generation
|
// Store request data for access during bot generation
|
||||||
this.applicationContext.addValue(ContextVariableType.RAID_CONFIGURATION, request);
|
this.applicationContext.addValue(ContextVariableType.RAID_CONFIGURATION, request);
|
||||||
@ -189,519 +126,13 @@ export class MatchController
|
|||||||
return botDifficulty;
|
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.handleCoopExtract(sessionId, pmcData, extractName);
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
item.parentId = parentId;
|
|
||||||
mailableLoot.push(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle when a player extracts using a coop extract - add rep to fence
|
|
||||||
* @param sessionId Session/player id
|
|
||||||
* @param pmcData Profile
|
|
||||||
* @param extractName Name of extract taken
|
|
||||||
*/
|
|
||||||
protected handleCoopExtract(sessionId: string, pmcData: IPmcData, extractName: string): void
|
|
||||||
{
|
|
||||||
if (!pmcData.CoopExtractCounts)
|
|
||||||
{
|
|
||||||
pmcData.CoopExtractCounts = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure key exists for extract
|
|
||||||
if (!(extractName in pmcData.CoopExtractCounts))
|
|
||||||
{
|
|
||||||
pmcData.CoopExtractCounts[extractName] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increment extract count value
|
|
||||||
pmcData.CoopExtractCounts[extractName] += 1;
|
|
||||||
|
|
||||||
// Get new fence standing value
|
|
||||||
const newFenceStanding = this.getFenceStandingAfterExtract(
|
|
||||||
pmcData,
|
|
||||||
this.inRaidConfig.coopExtractBaseStandingGain,
|
|
||||||
pmcData.CoopExtractCounts[extractName],
|
|
||||||
);
|
|
||||||
const fenceId: string = Traders.FENCE;
|
|
||||||
pmcData.TradersInfo[fenceId].standing = newFenceStanding;
|
|
||||||
|
|
||||||
// Check if new standing has leveled up trader
|
|
||||||
this.traderHelper.lvlUp(fenceId, pmcData);
|
|
||||||
pmcData.TradersInfo[fenceId].loyaltyLevel = Math.max(pmcData.TradersInfo[fenceId].loyaltyLevel, 1);
|
|
||||||
|
|
||||||
// Copy updated fence rep values into scav profile to ensure consistency
|
|
||||||
const scavData: IPmcData = this.profileHelper.getScavProfile(sessionId);
|
|
||||||
scavData.TradersInfo[fenceId].standing = pmcData.TradersInfo[fenceId].standing;
|
|
||||||
scavData.TradersInfo[fenceId].loyaltyLevel = pmcData.TradersInfo[fenceId].loyaltyLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Was extract by car
|
|
||||||
* @param extractName name of extract
|
|
||||||
* @returns true if car extract
|
|
||||||
*/
|
|
||||||
protected extractWasViaCar(extractName: string): boolean
|
|
||||||
{
|
|
||||||
// exit name is undefined 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;
|
|
||||||
|
|
||||||
// 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 newFenceStanding = this.getFenceStandingAfterExtract(
|
|
||||||
pmcData,
|
|
||||||
this.inRaidConfig.carExtractBaseStandingGain,
|
|
||||||
pmcData.CarExtractCounts[extractName],
|
|
||||||
);
|
|
||||||
const fenceId: string = Traders.FENCE;
|
|
||||||
pmcData.TradersInfo[fenceId].standing = newFenceStanding;
|
|
||||||
|
|
||||||
// Check if new standing has leveled up trader
|
|
||||||
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]}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Copy updated fence rep values into scav profile to ensure consistency
|
|
||||||
const scavData: IPmcData = this.profileHelper.getScavProfile(sessionId);
|
|
||||||
scavData.TradersInfo[fenceId].standing = pmcData.TradersInfo[fenceId].standing;
|
|
||||||
scavData.TradersInfo[fenceId].loyaltyLevel = pmcData.TradersInfo[fenceId].loyaltyLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the fence rep gain from using a car or coop extract
|
|
||||||
* @param pmcData Profile
|
|
||||||
* @param baseGain amount gained for the first extract
|
|
||||||
* @param extractCount Number of times extract was taken
|
|
||||||
* @returns Fence standing after taking extract
|
|
||||||
*/
|
|
||||||
protected getFenceStandingAfterExtract(pmcData: IPmcData, baseGain: number, extractCount: number): number
|
|
||||||
{
|
|
||||||
// Get current standing
|
|
||||||
const fenceId: string = Traders.FENCE;
|
|
||||||
let fenceStanding = Number(pmcData.TradersInfo[fenceId].standing);
|
|
||||||
|
|
||||||
// get standing after taking extract x times, x.xx format, gain from extract can be no smaller than 0.01
|
|
||||||
fenceStanding += Math.max(baseGain / extractCount, 0.01);
|
|
||||||
|
|
||||||
// Ensure fence loyalty level is not above/below the range -7 to 15
|
|
||||||
const newFenceStanding = Math.min(Math.max(fenceStanding, -7), 15);
|
|
||||||
this.logger.debug(`Old vs new fence standing: ${pmcData.TradersInfo[fenceId].standing}, ${newFenceStanding}`);
|
|
||||||
|
|
||||||
return Number(newFenceStanding.toFixed(2));
|
|
||||||
}
|
|
||||||
|
|
||||||
public startLocalRaid(sessionId: string, request: IStartLocalRaidRequestData): IStartLocalRaidResponseData
|
public startLocalRaid(sessionId: string, request: IStartLocalRaidRequestData): IStartLocalRaidResponseData
|
||||||
{
|
{
|
||||||
const playerProfile = this.profileHelper.getPmcProfile(sessionId);
|
return this.locationLifecycleService.startLocalRaid(sessionId, request);
|
||||||
|
|
||||||
const result: IStartLocalRaidResponseData = {
|
|
||||||
serverId: `${request.location}.${request.playerSide}.${this.timeUtil.getTimestamp()}`, // TODO - does this need to be more verbose - investigate client?
|
|
||||||
serverSettings: this.databaseService.getLocationServices(), // TODO - is this per map or global?
|
|
||||||
profile: { insuredItems: playerProfile.InsuredItems },
|
|
||||||
locationLoot: this.generateLocationAndLoot(request.location),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Clear bot cache ready for a fresh raid
|
|
||||||
this.botGenerationCacheService.clearStoredBots();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a maps base location and loot
|
|
||||||
* @param name Map name
|
|
||||||
* @returns ILocationBase
|
|
||||||
*/
|
|
||||||
protected generateLocationAndLoot(name: string): ILocationBase
|
|
||||||
{
|
|
||||||
const location = this.databaseService.getLocation(name);
|
|
||||||
const locationBaseClone = this.cloner.clone(location.base);
|
|
||||||
|
|
||||||
// Update datetime property to now
|
|
||||||
locationBaseClone.UnixDateTime = this.timeUtil.getTimestamp();
|
|
||||||
|
|
||||||
// Don't generate loot for hideout
|
|
||||||
if (name === "hideout")
|
|
||||||
{
|
|
||||||
return locationBaseClone;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for a loot multipler adjustment in app context and apply if one is found
|
|
||||||
let locationConfigClone: ILocationConfig;
|
|
||||||
const raidAdjustments = this.applicationContext
|
|
||||||
.getLatestValue(ContextVariableType.RAID_ADJUSTMENTS)
|
|
||||||
?.getValue<IRaidChanges>();
|
|
||||||
if (raidAdjustments)
|
|
||||||
{
|
|
||||||
locationConfigClone = this.cloner.clone(this.locationConfig); // Clone values so they can be used to reset originals later
|
|
||||||
this.raidTimeAdjustmentService.makeAdjustmentsToMap(raidAdjustments, locationBaseClone);
|
|
||||||
}
|
|
||||||
|
|
||||||
const staticAmmoDist = this.cloner.clone(location.staticAmmo);
|
|
||||||
|
|
||||||
// Create containers and add loot to them
|
|
||||||
const staticLoot = this.locationLootGenerator.generateStaticContainers(locationBaseClone, staticAmmoDist);
|
|
||||||
locationBaseClone.Loot.push(...staticLoot);
|
|
||||||
|
|
||||||
// Add dynamic loot to output loot
|
|
||||||
const dynamicLootDistClone = this.cloner.clone(location.looseLoot);
|
|
||||||
const dynamicSpawnPoints = this.locationLootGenerator.generateDynamicLoot(
|
|
||||||
dynamicLootDistClone,
|
|
||||||
staticAmmoDist,
|
|
||||||
name,
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const spawnPoint of dynamicSpawnPoints)
|
|
||||||
{
|
|
||||||
locationBaseClone.Loot.push(spawnPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done generating, log results
|
|
||||||
this.logger.success(
|
|
||||||
this.localisationService.getText("location-dynamic_items_spawned_success", dynamicSpawnPoints.length),
|
|
||||||
);
|
|
||||||
this.logger.success(this.localisationService.getText("location-generated_success", name));
|
|
||||||
|
|
||||||
// Reset loot multipliers back to original values
|
|
||||||
if (raidAdjustments)
|
|
||||||
{
|
|
||||||
this.logger.debug("Resetting loot multipliers back to their original values");
|
|
||||||
this.locationConfig.staticLootMultiplier = locationConfigClone.staticLootMultiplier;
|
|
||||||
this.locationConfig.looseLootMultiplier = locationConfigClone.looseLootMultiplier;
|
|
||||||
|
|
||||||
this.applicationContext.clearValues(ContextVariableType.RAID_ADJUSTMENTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
return locationBaseClone;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public endLocalRaid(sessionId: string, request: IEndLocalRaidRequestData): void
|
public endLocalRaid(sessionId: string, request: IEndLocalRaidRequestData): void
|
||||||
{
|
{
|
||||||
const fullProfile = this.profileHelper.getFullProfile(sessionId);
|
return this.locationLifecycleService.endLocalRaid(sessionId, request);
|
||||||
const pmcProfile = fullProfile.characters.pmc;
|
|
||||||
const scavProfile = fullProfile.characters.scav;
|
|
||||||
const postRaidProfile = request.results.profile!;
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// Update profile
|
|
||||||
// Handle insurance
|
|
||||||
// Rep gain/loss?
|
|
||||||
// Quest status?
|
|
||||||
// Counters?
|
|
||||||
// Send PMC message to player if necessary
|
|
||||||
// Limb health
|
|
||||||
// Limb effects
|
|
||||||
// Skills
|
|
||||||
// Inventory - items not lost on death
|
|
||||||
// Stats
|
|
||||||
// stats/eft/aggressor - weird values (EFT.IProfileDataContainer.Nickname)
|
|
||||||
|
|
||||||
this.logger.debug(`Raid outcome: ${request.results.result}`);
|
|
||||||
|
|
||||||
// Set flea interval time to out-of-raid value
|
|
||||||
this.ragfairConfig.runIntervalSeconds = this.ragfairConfig.runIntervalValues.outOfRaid;
|
|
||||||
this.hideoutConfig.runIntervalSeconds = this.hideoutConfig.runIntervalValues.outOfRaid;
|
|
||||||
|
|
||||||
// ServerId has various info stored in it, delimited by a period
|
|
||||||
const serverDetails = request.serverId.split(".");
|
|
||||||
|
|
||||||
const locationName = serverDetails[0].toLowerCase();
|
|
||||||
const isPmc = serverDetails[1].toLowerCase() === "pmc";
|
|
||||||
const mapBase = this.databaseService.getLocation(locationName).base;
|
|
||||||
const isDead = this.isPlayerDead(request.results);
|
|
||||||
|
|
||||||
if (!isPmc)
|
|
||||||
{
|
|
||||||
this.handlePostRaidPlayerScav(sessionId, pmcProfile, scavProfile, isDead);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.handlePostRaidPmc(sessionId, pmcProfile, scavProfile, postRaidProfile, isDead, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected handlePostRaidPlayerScav(
|
|
||||||
sessionId: string,
|
|
||||||
pmcProfile: IPmcData,
|
|
||||||
scavProfile: IPmcData,
|
|
||||||
isDead: boolean,
|
|
||||||
): void
|
|
||||||
{
|
|
||||||
// Scav died, regen scav loadout and set timer
|
|
||||||
if (isDead)
|
|
||||||
{
|
|
||||||
this.playerScavGenerator.generate(sessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update last played property
|
|
||||||
pmcProfile.Info.LastTimePlayedAsSavage = this.timeUtil.getTimestamp();
|
|
||||||
|
|
||||||
// Force a profile save
|
|
||||||
this.saveServer.saveProfile(sessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected handlePostRaidPmc(
|
|
||||||
sessionId: string,
|
|
||||||
pmcProfile: IPmcData,
|
|
||||||
scavProfile: IPmcData,
|
|
||||||
postRaidProfile: IPmcData,
|
|
||||||
isDead: boolean,
|
|
||||||
request: IEndLocalRaidRequestData,
|
|
||||||
): void
|
|
||||||
{
|
|
||||||
// Update inventory
|
|
||||||
this.inRaidHelper.setInventory(sessionId, pmcProfile, postRaidProfile);
|
|
||||||
|
|
||||||
pmcProfile.Info.Level = postRaidProfile.Info.Level;
|
|
||||||
|
|
||||||
// Add experience points
|
|
||||||
pmcProfile.Info.Experience += postRaidProfile.Stats.Eft.TotalSessionExperience;
|
|
||||||
|
|
||||||
// Profile common/mastering skills
|
|
||||||
pmcProfile.Skills = postRaidProfile.Skills;
|
|
||||||
|
|
||||||
pmcProfile.Stats.Eft = postRaidProfile.Stats.Eft;
|
|
||||||
|
|
||||||
// Must occur after experience is set and stats copied over
|
|
||||||
pmcProfile.Stats.Eft.TotalSessionExperience = 0;
|
|
||||||
|
|
||||||
pmcProfile.Achievements = postRaidProfile.Achievements;
|
|
||||||
|
|
||||||
// Remove skill fatigue values
|
|
||||||
this.resetSkillPointsEarnedDuringRaid(pmcProfile.Skills.Common);
|
|
||||||
|
|
||||||
// Straight copy
|
|
||||||
pmcProfile.TaskConditionCounters = postRaidProfile.TaskConditionCounters;
|
|
||||||
|
|
||||||
pmcProfile.Encyclopedia = postRaidProfile.Encyclopedia;
|
|
||||||
|
|
||||||
// Must occur after encyclopedia updated
|
|
||||||
this.mergePmcAndScavEncyclopedias(pmcProfile, scavProfile);
|
|
||||||
|
|
||||||
// Handle temp, hydration, limb hp/effects
|
|
||||||
this.healthHelper.updateProfileHealthPostRaid(pmcProfile, postRaidProfile.Health, sessionId, isDead);
|
|
||||||
|
|
||||||
if (isDead)
|
|
||||||
{
|
|
||||||
this.pmcChatResponseService.sendKillerResponse(
|
|
||||||
sessionId,
|
|
||||||
pmcProfile,
|
|
||||||
postRaidProfile.Stats.Eft.Aggressor,
|
|
||||||
);
|
|
||||||
this.matchBotDetailsCacheService.clearCache();
|
|
||||||
|
|
||||||
this.inRaidHelper.deleteInventory(pmcProfile, sessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const victims = postRaidProfile.Stats.Eft.Victims.filter((victim) =>
|
|
||||||
["pmcbear", "pmcusec"].includes(victim.Role.toLowerCase()),
|
|
||||||
);
|
|
||||||
if (victims?.length > 0)
|
|
||||||
{
|
|
||||||
// Player killed PMCs, send some responses to them
|
|
||||||
this.pmcChatResponseService.sendVictimResponse(sessionId, victims, pmcProfile);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle items transferred via BTR to player
|
|
||||||
const btrKey = "BTRTransferStash";
|
|
||||||
const btrContainerAndItems = request.transferItems[btrKey] ?? [];
|
|
||||||
if (btrContainerAndItems.length > 0)
|
|
||||||
{
|
|
||||||
const itemsToSend = btrContainerAndItems.filter((item) => item._id !== btrKey);
|
|
||||||
this.btrItemDelivery(sessionId, Traders.BTR, itemsToSend);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.lostInsuredItems?.length > 0)
|
|
||||||
{
|
|
||||||
// TODO - refactor code to work
|
|
||||||
|
|
||||||
// Get array of insured items+child that were lost in raid
|
|
||||||
// const gearToStore = this.insuranceService.getGearLostInRaid(
|
|
||||||
// pmcProfile,
|
|
||||||
// postRaidRequest,
|
|
||||||
// preRaidGear,
|
|
||||||
// sessionId,
|
|
||||||
// isDead,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// this.insuranceService.storeGearLostInRaidToSendLater(
|
|
||||||
// sessionId,
|
|
||||||
// gearToStore,
|
|
||||||
// );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle singleplayer/traderServices/itemDelivery
|
|
||||||
*/
|
|
||||||
protected btrItemDelivery(sessionId: string, traderId: string, items: Item[]): void
|
|
||||||
{
|
|
||||||
const serverProfile = this.saveServer.getProfile(sessionId);
|
|
||||||
const pmcData = serverProfile.characters.pmc;
|
|
||||||
|
|
||||||
const dialogueTemplates = this.databaseService.getTrader(traderId).dialogue;
|
|
||||||
if (!dialogueTemplates)
|
|
||||||
{
|
|
||||||
this.logger.error(this.localisationService.getText("inraid-unable_to_deliver_item_no_trader_found", traderId));
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const messageId = this.randomUtil.getArrayValue(dialogueTemplates.itemsDelivered);
|
|
||||||
const messageStoreTime = this.timeUtil.getHoursAsSeconds(this.traderConfig.fence.btrDeliveryExpireHours);
|
|
||||||
|
|
||||||
// Remove any items that were returned by the item delivery, but also insured, from the player's insurance list
|
|
||||||
// This is to stop items being duplicated by being returned from both item delivery and insurance
|
|
||||||
const deliveredItemIds = items.map((item) => item._id);
|
|
||||||
pmcData.InsuredItems = pmcData.InsuredItems
|
|
||||||
.filter((insuredItem) => !deliveredItemIds.includes(insuredItem.itemId));
|
|
||||||
|
|
||||||
// Send the items to the player
|
|
||||||
this.mailSendService.sendLocalisedNpcMessageToPlayer(
|
|
||||||
sessionId,
|
|
||||||
this.traderHelper.getTraderById(traderId),
|
|
||||||
MessageType.BTR_ITEMS_DELIVERY,
|
|
||||||
messageId,
|
|
||||||
items,
|
|
||||||
messageStoreTime,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is the player dead after a raid - dead = anything other than "survived" / "runner"
|
|
||||||
* @param statusOnExit Exit value from offraidData object
|
|
||||||
* @returns true if dead
|
|
||||||
*/
|
|
||||||
protected isPlayerDead(results: IEndRaidResult): boolean
|
|
||||||
{
|
|
||||||
return ["killed", "missinginaction", "left"].includes(results.result.toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset the skill points earned in a raid to 0, ready for next raid
|
|
||||||
* @param commonSkills Profile common skills to update
|
|
||||||
*/
|
|
||||||
protected resetSkillPointsEarnedDuringRaid(commonSkills: Common[]): void
|
|
||||||
{
|
|
||||||
for (const skill of commonSkills)
|
|
||||||
{
|
|
||||||
skill.PointsEarnedDuringSession = 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* merge two dictionaries together
|
|
||||||
* Prioritise pair that has true as a value
|
|
||||||
* @param primary main dictionary
|
|
||||||
* @param secondary Secondary dictionary
|
|
||||||
*/
|
|
||||||
protected mergePmcAndScavEncyclopedias(primary: IPmcData, secondary: IPmcData): void
|
|
||||||
{
|
|
||||||
function extend(target: { [key: string]: boolean }, source: Record<string, boolean>)
|
|
||||||
{
|
|
||||||
for (const key in source)
|
|
||||||
{
|
|
||||||
if (Object.hasOwn(source, key))
|
|
||||||
{
|
|
||||||
target[key] = source[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
const merged = extend(extend({}, primary.Encyclopedia), secondary.Encyclopedia);
|
|
||||||
primary.Encyclopedia = merged;
|
|
||||||
secondary.Encyclopedia = merged;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,6 +214,7 @@ import { ItemBaseClassService } from "@spt/services/ItemBaseClassService";
|
|||||||
import { ItemFilterService } from "@spt/services/ItemFilterService";
|
import { ItemFilterService } from "@spt/services/ItemFilterService";
|
||||||
import { LocaleService } from "@spt/services/LocaleService";
|
import { LocaleService } from "@spt/services/LocaleService";
|
||||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||||
|
import { LocationLifecycleService } from "@spt/services/LocationLifecycleService";
|
||||||
import { MailSendService } from "@spt/services/MailSendService";
|
import { MailSendService } from "@spt/services/MailSendService";
|
||||||
import { MapMarkerService } from "@spt/services/MapMarkerService";
|
import { MapMarkerService } from "@spt/services/MapMarkerService";
|
||||||
import { MatchBotDetailsCacheService } from "@spt/services/MatchBotDetailsCacheService";
|
import { MatchBotDetailsCacheService } from "@spt/services/MatchBotDetailsCacheService";
|
||||||
@ -799,6 +800,9 @@ export class Container
|
|||||||
depContainer.register<AirdropService>("AirdropService", AirdropService, {
|
depContainer.register<AirdropService>("AirdropService", AirdropService, {
|
||||||
lifecycle: Lifecycle.Singleton,
|
lifecycle: Lifecycle.Singleton,
|
||||||
});
|
});
|
||||||
|
depContainer.register<LocationLifecycleService>("LocationLifecycleService", LocationLifecycleService, {
|
||||||
|
lifecycle: Lifecycle.Singleton,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static registerServers(depContainer: DependencyContainer): void
|
private static registerServers(depContainer: DependencyContainer): void
|
||||||
|
@ -231,13 +231,6 @@ export class MatchStaticRouter extends StaticRouter
|
|||||||
return this.matchCallbacks.notRaidReady(url, info, sessionID);
|
return this.matchCallbacks.notRaidReady(url, info, sessionID);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
new RouteAction(
|
|
||||||
"/client/match/offline/end",
|
|
||||||
async (url: string, info: any, sessionID: string, output: string): Promise<INullResponseData> =>
|
|
||||||
{
|
|
||||||
return this.matchCallbacks.endOfflineRaid(url, info, sessionID);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
new RouteAction(
|
new RouteAction(
|
||||||
"/client/putMetrics",
|
"/client/putMetrics",
|
||||||
async (url: string, info: any, sessionID: string, output: string): Promise<INullResponseData> =>
|
async (url: string, info: any, sessionID: string, output: string): Promise<INullResponseData> =>
|
||||||
@ -307,7 +300,7 @@ export class MatchStaticRouter extends StaticRouter
|
|||||||
{
|
{
|
||||||
return this.matchCallbacks.endLocalRaid(url, info, sessionID);
|
return this.matchCallbacks.endLocalRaid(url, info, sessionID);
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
276
project/src/services/LegacyLocationLifecycleService.ts
Normal file
276
project/src/services/LegacyLocationLifecycleService.ts
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
import { inject } from "tsyringe";
|
||||||
|
import { ApplicationContext } from "@spt/context/ApplicationContext";
|
||||||
|
import { LocationLootGenerator } from "@spt/generators/LocationLootGenerator";
|
||||||
|
import { LootGenerator } from "@spt/generators/LootGenerator";
|
||||||
|
import { PlayerScavGenerator } from "@spt/generators/PlayerScavGenerator";
|
||||||
|
import { HealthHelper } from "@spt/helpers/HealthHelper";
|
||||||
|
import { InRaidHelper } from "@spt/helpers/InRaidHelper";
|
||||||
|
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||||
|
import { TraderHelper } from "@spt/helpers/TraderHelper";
|
||||||
|
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||||
|
import { Item } from "@spt/models/eft/common/tables/IItem";
|
||||||
|
import { IEndOfflineRaidRequestData } from "@spt/models/eft/match/IEndOfflineRaidRequestData";
|
||||||
|
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
||||||
|
import { MessageType } from "@spt/models/enums/MessageType";
|
||||||
|
import { Traders } from "@spt/models/enums/Traders";
|
||||||
|
import { IHideoutConfig } from "@spt/models/spt/config/IHideoutConfig";
|
||||||
|
import { IInRaidConfig } from "@spt/models/spt/config/IInRaidConfig";
|
||||||
|
import { ILocationConfig } from "@spt/models/spt/config/ILocationConfig";
|
||||||
|
import { IMatchConfig } from "@spt/models/spt/config/IMatchConfig";
|
||||||
|
import { IRagfairConfig } from "@spt/models/spt/config/IRagfairConfig";
|
||||||
|
import { ITraderConfig } from "@spt/models/spt/config/ITraderConfig";
|
||||||
|
import { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||||
|
import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||||
|
import { SaveServer } from "@spt/servers/SaveServer";
|
||||||
|
import { BotGenerationCacheService } from "@spt/services/BotGenerationCacheService";
|
||||||
|
import { BotLootCacheService } from "@spt/services/BotLootCacheService";
|
||||||
|
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||||
|
import { InsuranceService } from "@spt/services/InsuranceService";
|
||||||
|
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||||
|
import { MailSendService } from "@spt/services/MailSendService";
|
||||||
|
import { MatchBotDetailsCacheService } from "@spt/services/MatchBotDetailsCacheService";
|
||||||
|
import { PmcChatResponseService } from "@spt/services/PmcChatResponseService";
|
||||||
|
import { RaidTimeAdjustmentService } from "@spt/services/RaidTimeAdjustmentService";
|
||||||
|
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||||
|
import { HashUtil } from "@spt/utils/HashUtil";
|
||||||
|
import { RandomUtil } from "@spt/utils/RandomUtil";
|
||||||
|
import { TimeUtil } from "@spt/utils/TimeUtil";
|
||||||
|
|
||||||
|
export class LegacyLocationLifecycleService
|
||||||
|
{
|
||||||
|
protected matchConfig: IMatchConfig;
|
||||||
|
protected inRaidConfig: IInRaidConfig;
|
||||||
|
protected traderConfig: ITraderConfig;
|
||||||
|
protected ragfairConfig: IRagfairConfig;
|
||||||
|
protected hideoutConfig: IHideoutConfig;
|
||||||
|
protected locationConfig: ILocationConfig;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@inject("PrimaryLogger") protected logger: ILogger,
|
||||||
|
@inject("HashUtil") protected hashUtil: HashUtil,
|
||||||
|
@inject("SaveServer") protected saveServer: SaveServer,
|
||||||
|
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
||||||
|
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
||||||
|
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
||||||
|
@inject("DatabaseService") protected databaseService: DatabaseService,
|
||||||
|
@inject("InRaidHelper") protected inRaidHelper: InRaidHelper,
|
||||||
|
@inject("HealthHelper") protected healthHelper: HealthHelper,
|
||||||
|
@inject("MatchBotDetailsCacheService") protected matchBotDetailsCacheService: MatchBotDetailsCacheService,
|
||||||
|
@inject("PmcChatResponseService") protected pmcChatResponseService: PmcChatResponseService,
|
||||||
|
@inject("PlayerScavGenerator") protected playerScavGenerator: PlayerScavGenerator,
|
||||||
|
@inject("TraderHelper") protected traderHelper: TraderHelper,
|
||||||
|
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||||
|
@inject("InsuranceService") protected insuranceService: InsuranceService,
|
||||||
|
@inject("BotLootCacheService") protected botLootCacheService: BotLootCacheService,
|
||||||
|
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||||
|
@inject("BotGenerationCacheService") protected botGenerationCacheService: BotGenerationCacheService,
|
||||||
|
@inject("MailSendService") protected mailSendService: MailSendService,
|
||||||
|
@inject("RaidTimeAdjustmentService") protected raidTimeAdjustmentService: RaidTimeAdjustmentService,
|
||||||
|
@inject("LootGenerator") protected lootGenerator: LootGenerator,
|
||||||
|
@inject("ApplicationContext") protected applicationContext: ApplicationContext,
|
||||||
|
@inject("LocationLootGenerator") protected locationLootGenerator: LocationLootGenerator,
|
||||||
|
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER);
|
||||||
|
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
|
||||||
|
this.hideoutConfig = this.configServer.getConfig(ConfigTypes.HIDEOUT);
|
||||||
|
this.locationConfig = this.configServer.getConfig(ConfigTypes.LOCATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle client/match/offline/end
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
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.handleCoopExtract(sessionId, pmcData, extractName);
|
||||||
|
this.sendCoopTakenFenceMessage(sessionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
// 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 newFenceStanding = this.getFenceStandingAfterExtract(
|
||||||
|
pmcData,
|
||||||
|
this.inRaidConfig.carExtractBaseStandingGain,
|
||||||
|
pmcData.CarExtractCounts[extractName],
|
||||||
|
);
|
||||||
|
const fenceId: string = Traders.FENCE;
|
||||||
|
pmcData.TradersInfo[fenceId].standing = newFenceStanding;
|
||||||
|
|
||||||
|
// Check if new standing has leveled up trader
|
||||||
|
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]}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Copy updated fence rep values into scav profile to ensure consistency
|
||||||
|
const scavData: IPmcData = this.profileHelper.getScavProfile(sessionId);
|
||||||
|
scavData.TradersInfo[fenceId].standing = pmcData.TradersInfo[fenceId].standing;
|
||||||
|
scavData.TradersInfo[fenceId].loyaltyLevel = pmcData.TradersInfo[fenceId].loyaltyLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the fence rep gain from using a car or coop extract
|
||||||
|
* @param pmcData Profile
|
||||||
|
* @param baseGain amount gained for the first extract
|
||||||
|
* @param extractCount Number of times extract was taken
|
||||||
|
* @returns Fence standing after taking extract
|
||||||
|
*/
|
||||||
|
protected getFenceStandingAfterExtract(pmcData: IPmcData, baseGain: number, extractCount: number): number
|
||||||
|
{
|
||||||
|
// Get current standing
|
||||||
|
const fenceId: string = Traders.FENCE;
|
||||||
|
let fenceStanding = Number(pmcData.TradersInfo[fenceId].standing);
|
||||||
|
|
||||||
|
// get standing after taking extract x times, x.xx format, gain from extract can be no smaller than 0.01
|
||||||
|
fenceStanding += Math.max(baseGain / extractCount, 0.01);
|
||||||
|
|
||||||
|
// Ensure fence loyalty level is not above/below the range -7 to 15
|
||||||
|
const newFenceStanding = Math.min(Math.max(fenceStanding, -7), 15);
|
||||||
|
this.logger.debug(`Old vs new fence standing: ${pmcData.TradersInfo[fenceId].standing}, ${newFenceStanding}`);
|
||||||
|
|
||||||
|
return Number(newFenceStanding.toFixed(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Was extract by car
|
||||||
|
* @param extractName name of extract
|
||||||
|
* @returns true if car extract
|
||||||
|
*/
|
||||||
|
protected extractWasViaCar(extractName: string): boolean
|
||||||
|
{
|
||||||
|
// exit name is undefined on death
|
||||||
|
if (!extractName)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extractName.toLowerCase().includes("v-ex"))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.inRaidConfig.carExtracts.includes(extractName.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle when a player extracts using a coop extract - add rep to fence
|
||||||
|
* @param sessionId Session/player id
|
||||||
|
* @param pmcData Profile
|
||||||
|
* @param extractName Name of extract taken
|
||||||
|
*/
|
||||||
|
protected handleCoopExtract(sessionId: string, pmcData: IPmcData, extractName: string): void
|
||||||
|
{
|
||||||
|
if (!pmcData.CoopExtractCounts)
|
||||||
|
{
|
||||||
|
pmcData.CoopExtractCounts = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure key exists for extract
|
||||||
|
if (!(extractName in pmcData.CoopExtractCounts))
|
||||||
|
{
|
||||||
|
pmcData.CoopExtractCounts[extractName] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment extract count value
|
||||||
|
pmcData.CoopExtractCounts[extractName] += 1;
|
||||||
|
|
||||||
|
// Get new fence standing value
|
||||||
|
const newFenceStanding = this.getFenceStandingAfterExtract(
|
||||||
|
pmcData,
|
||||||
|
this.inRaidConfig.coopExtractBaseStandingGain,
|
||||||
|
pmcData.CoopExtractCounts[extractName],
|
||||||
|
);
|
||||||
|
const fenceId: string = Traders.FENCE;
|
||||||
|
pmcData.TradersInfo[fenceId].standing = newFenceStanding;
|
||||||
|
|
||||||
|
// Check if new standing has leveled up trader
|
||||||
|
this.traderHelper.lvlUp(fenceId, pmcData);
|
||||||
|
pmcData.TradersInfo[fenceId].loyaltyLevel = Math.max(pmcData.TradersInfo[fenceId].loyaltyLevel, 1);
|
||||||
|
|
||||||
|
// Copy updated fence rep values into scav profile to ensure consistency
|
||||||
|
const scavData: IPmcData = this.profileHelper.getScavProfile(sessionId);
|
||||||
|
scavData.TradersInfo[fenceId].standing = pmcData.TradersInfo[fenceId].standing;
|
||||||
|
scavData.TradersInfo[fenceId].loyaltyLevel = pmcData.TradersInfo[fenceId].loyaltyLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
item.parentId = parentId;
|
||||||
|
mailableLoot.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
411
project/src/services/LocationLifecycleService.ts
Normal file
411
project/src/services/LocationLifecycleService.ts
Normal file
@ -0,0 +1,411 @@
|
|||||||
|
import { inject, injectable } from "tsyringe";
|
||||||
|
import { ApplicationContext } from "@spt/context/ApplicationContext";
|
||||||
|
import { ContextVariableType } from "@spt/context/ContextVariableType";
|
||||||
|
import { LocationLootGenerator } from "@spt/generators/LocationLootGenerator";
|
||||||
|
import { LootGenerator } from "@spt/generators/LootGenerator";
|
||||||
|
import { PlayerScavGenerator } from "@spt/generators/PlayerScavGenerator";
|
||||||
|
import { HealthHelper } from "@spt/helpers/HealthHelper";
|
||||||
|
import { InRaidHelper } from "@spt/helpers/InRaidHelper";
|
||||||
|
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||||
|
import { TraderHelper } from "@spt/helpers/TraderHelper";
|
||||||
|
import { ILocationBase } from "@spt/models/eft/common/ILocationBase";
|
||||||
|
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||||
|
import { Common } from "@spt/models/eft/common/tables/IBotBase";
|
||||||
|
import { Item } from "@spt/models/eft/common/tables/IItem";
|
||||||
|
import { IEndLocalRaidRequestData, IEndRaidResult } from "@spt/models/eft/match/IEndLocalRaidRequestData";
|
||||||
|
import { IStartLocalRaidRequestData } from "@spt/models/eft/match/IStartLocalRaidRequestData";
|
||||||
|
import { IStartLocalRaidResponseData } from "@spt/models/eft/match/IStartLocalRaidResponseData";
|
||||||
|
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
||||||
|
import { MessageType } from "@spt/models/enums/MessageType";
|
||||||
|
import { Traders } from "@spt/models/enums/Traders";
|
||||||
|
import { IHideoutConfig } from "@spt/models/spt/config/IHideoutConfig";
|
||||||
|
import { IInRaidConfig } from "@spt/models/spt/config/IInRaidConfig";
|
||||||
|
import { ILocationConfig } from "@spt/models/spt/config/ILocationConfig";
|
||||||
|
import { IMatchConfig } from "@spt/models/spt/config/IMatchConfig";
|
||||||
|
import { IRagfairConfig } from "@spt/models/spt/config/IRagfairConfig";
|
||||||
|
import { ITraderConfig } from "@spt/models/spt/config/ITraderConfig";
|
||||||
|
import { IRaidChanges } from "@spt/models/spt/location/IRaidChanges";
|
||||||
|
import { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||||
|
import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||||
|
import { SaveServer } from "@spt/servers/SaveServer";
|
||||||
|
import { BotGenerationCacheService } from "@spt/services/BotGenerationCacheService";
|
||||||
|
import { BotLootCacheService } from "@spt/services/BotLootCacheService";
|
||||||
|
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||||
|
import { InsuranceService } from "@spt/services/InsuranceService";
|
||||||
|
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||||
|
import { MailSendService } from "@spt/services/MailSendService";
|
||||||
|
import { MatchBotDetailsCacheService } from "@spt/services/MatchBotDetailsCacheService";
|
||||||
|
import { PmcChatResponseService } from "@spt/services/PmcChatResponseService";
|
||||||
|
import { RaidTimeAdjustmentService } from "@spt/services/RaidTimeAdjustmentService";
|
||||||
|
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||||
|
import { RandomUtil } from "@spt/utils/RandomUtil";
|
||||||
|
import { TimeUtil } from "@spt/utils/TimeUtil";
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class LocationLifecycleService
|
||||||
|
{
|
||||||
|
protected matchConfig: IMatchConfig;
|
||||||
|
protected inRaidConfig: IInRaidConfig;
|
||||||
|
protected traderConfig: ITraderConfig;
|
||||||
|
protected ragfairConfig: IRagfairConfig;
|
||||||
|
protected hideoutConfig: IHideoutConfig;
|
||||||
|
protected locationConfig: ILocationConfig;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@inject("PrimaryLogger") protected logger: ILogger,
|
||||||
|
@inject("SaveServer") protected saveServer: SaveServer,
|
||||||
|
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
||||||
|
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
||||||
|
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
||||||
|
@inject("DatabaseService") protected databaseService: DatabaseService,
|
||||||
|
@inject("InRaidHelper") protected inRaidHelper: InRaidHelper,
|
||||||
|
@inject("HealthHelper") protected healthHelper: HealthHelper,
|
||||||
|
@inject("MatchBotDetailsCacheService") protected matchBotDetailsCacheService: MatchBotDetailsCacheService,
|
||||||
|
@inject("PmcChatResponseService") protected pmcChatResponseService: PmcChatResponseService,
|
||||||
|
@inject("PlayerScavGenerator") protected playerScavGenerator: PlayerScavGenerator,
|
||||||
|
@inject("TraderHelper") protected traderHelper: TraderHelper,
|
||||||
|
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||||
|
@inject("InsuranceService") protected insuranceService: InsuranceService,
|
||||||
|
@inject("BotLootCacheService") protected botLootCacheService: BotLootCacheService,
|
||||||
|
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||||
|
@inject("BotGenerationCacheService") protected botGenerationCacheService: BotGenerationCacheService,
|
||||||
|
@inject("MailSendService") protected mailSendService: MailSendService,
|
||||||
|
@inject("RaidTimeAdjustmentService") protected raidTimeAdjustmentService: RaidTimeAdjustmentService,
|
||||||
|
@inject("LootGenerator") protected lootGenerator: LootGenerator,
|
||||||
|
@inject("ApplicationContext") protected applicationContext: ApplicationContext,
|
||||||
|
@inject("LocationLootGenerator") protected locationLootGenerator: LocationLootGenerator,
|
||||||
|
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER);
|
||||||
|
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
|
||||||
|
this.hideoutConfig = this.configServer.getConfig(ConfigTypes.HIDEOUT);
|
||||||
|
this.locationConfig = this.configServer.getConfig(ConfigTypes.LOCATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public startLocalRaid(sessionId: string, request: IStartLocalRaidRequestData): IStartLocalRaidResponseData
|
||||||
|
{
|
||||||
|
const playerProfile = this.profileHelper.getPmcProfile(sessionId);
|
||||||
|
|
||||||
|
const result: IStartLocalRaidResponseData = {
|
||||||
|
serverId: `${request.location}.${request.playerSide}.${this.timeUtil.getTimestamp()}`, // TODO - does this need to be more verbose - investigate client?
|
||||||
|
serverSettings: this.databaseService.getLocationServices(), // TODO - is this per map or global?
|
||||||
|
profile: { insuredItems: playerProfile.InsuredItems },
|
||||||
|
locationLoot: this.generateLocationAndLoot(request.location),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clear bot cache ready for a fresh raid
|
||||||
|
this.botGenerationCacheService.clearStoredBots();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a maps base location and loot
|
||||||
|
* @param name Map name
|
||||||
|
* @returns ILocationBase
|
||||||
|
*/
|
||||||
|
protected generateLocationAndLoot(name: string): ILocationBase
|
||||||
|
{
|
||||||
|
const location = this.databaseService.getLocation(name);
|
||||||
|
const locationBaseClone = this.cloner.clone(location.base);
|
||||||
|
|
||||||
|
// Update datetime property to now
|
||||||
|
locationBaseClone.UnixDateTime = this.timeUtil.getTimestamp();
|
||||||
|
|
||||||
|
// Don't generate loot for hideout
|
||||||
|
if (name === "hideout")
|
||||||
|
{
|
||||||
|
return locationBaseClone;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for a loot multipler adjustment in app context and apply if one is found
|
||||||
|
let locationConfigClone: ILocationConfig;
|
||||||
|
const raidAdjustments = this.applicationContext
|
||||||
|
.getLatestValue(ContextVariableType.RAID_ADJUSTMENTS)
|
||||||
|
?.getValue<IRaidChanges>();
|
||||||
|
if (raidAdjustments)
|
||||||
|
{
|
||||||
|
locationConfigClone = this.cloner.clone(this.locationConfig); // Clone values so they can be used to reset originals later
|
||||||
|
this.raidTimeAdjustmentService.makeAdjustmentsToMap(raidAdjustments, locationBaseClone);
|
||||||
|
}
|
||||||
|
|
||||||
|
const staticAmmoDist = this.cloner.clone(location.staticAmmo);
|
||||||
|
|
||||||
|
// Create containers and add loot to them
|
||||||
|
const staticLoot = this.locationLootGenerator.generateStaticContainers(locationBaseClone, staticAmmoDist);
|
||||||
|
locationBaseClone.Loot.push(...staticLoot);
|
||||||
|
|
||||||
|
// Add dynamic loot to output loot
|
||||||
|
const dynamicLootDistClone = this.cloner.clone(location.looseLoot);
|
||||||
|
const dynamicSpawnPoints = this.locationLootGenerator.generateDynamicLoot(
|
||||||
|
dynamicLootDistClone,
|
||||||
|
staticAmmoDist,
|
||||||
|
name,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const spawnPoint of dynamicSpawnPoints)
|
||||||
|
{
|
||||||
|
locationBaseClone.Loot.push(spawnPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done generating, log results
|
||||||
|
this.logger.success(
|
||||||
|
this.localisationService.getText("location-dynamic_items_spawned_success", dynamicSpawnPoints.length),
|
||||||
|
);
|
||||||
|
this.logger.success(this.localisationService.getText("location-generated_success", name));
|
||||||
|
|
||||||
|
// Reset loot multipliers back to original values
|
||||||
|
if (raidAdjustments)
|
||||||
|
{
|
||||||
|
this.logger.debug("Resetting loot multipliers back to their original values");
|
||||||
|
this.locationConfig.staticLootMultiplier = locationConfigClone.staticLootMultiplier;
|
||||||
|
this.locationConfig.looseLootMultiplier = locationConfigClone.looseLootMultiplier;
|
||||||
|
|
||||||
|
this.applicationContext.clearValues(ContextVariableType.RAID_ADJUSTMENTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
return locationBaseClone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public endLocalRaid(sessionId: string, request: IEndLocalRaidRequestData): void
|
||||||
|
{
|
||||||
|
// Clear bot loot cache
|
||||||
|
this.botLootCacheService.clearCache();
|
||||||
|
|
||||||
|
const fullProfile = this.profileHelper.getFullProfile(sessionId);
|
||||||
|
const pmcProfile = fullProfile.characters.pmc;
|
||||||
|
const scavProfile = fullProfile.characters.scav;
|
||||||
|
const postRaidProfile = request.results.profile!;
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// Update profile
|
||||||
|
// Handle insurance
|
||||||
|
// Rep gain/loss?
|
||||||
|
// Quest status?
|
||||||
|
// Counters?
|
||||||
|
// Send PMC message to player if necessary
|
||||||
|
// Limb health
|
||||||
|
// Limb effects
|
||||||
|
// Skills
|
||||||
|
// Inventory - items not lost on death
|
||||||
|
// Stats
|
||||||
|
// stats/eft/aggressor - weird values (EFT.IProfileDataContainer.Nickname)
|
||||||
|
|
||||||
|
this.logger.debug(`Raid outcome: ${request.results.result}`);
|
||||||
|
|
||||||
|
// Set flea interval time to out-of-raid value
|
||||||
|
this.ragfairConfig.runIntervalSeconds = this.ragfairConfig.runIntervalValues.outOfRaid;
|
||||||
|
this.hideoutConfig.runIntervalSeconds = this.hideoutConfig.runIntervalValues.outOfRaid;
|
||||||
|
|
||||||
|
// ServerId has various info stored in it, delimited by a period
|
||||||
|
const serverDetails = request.serverId.split(".");
|
||||||
|
|
||||||
|
const locationName = serverDetails[0].toLowerCase();
|
||||||
|
const isPmc = serverDetails[1].toLowerCase() === "pmc";
|
||||||
|
const mapBase = this.databaseService.getLocation(locationName).base;
|
||||||
|
const isDead = this.isPlayerDead(request.results);
|
||||||
|
|
||||||
|
if (!isPmc)
|
||||||
|
{
|
||||||
|
this.handlePostRaidPlayerScav(sessionId, pmcProfile, scavProfile, isDead);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handlePostRaidPmc(sessionId, pmcProfile, scavProfile, postRaidProfile, isDead, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handlePostRaidPlayerScav(
|
||||||
|
sessionId: string,
|
||||||
|
pmcProfile: IPmcData,
|
||||||
|
scavProfile: IPmcData,
|
||||||
|
isDead: boolean,
|
||||||
|
): void
|
||||||
|
{
|
||||||
|
// Scav died, regen scav loadout and set timer
|
||||||
|
if (isDead)
|
||||||
|
{
|
||||||
|
this.playerScavGenerator.generate(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update last played property
|
||||||
|
pmcProfile.Info.LastTimePlayedAsSavage = this.timeUtil.getTimestamp();
|
||||||
|
|
||||||
|
// Force a profile save
|
||||||
|
this.saveServer.saveProfile(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handlePostRaidPmc(
|
||||||
|
sessionId: string,
|
||||||
|
pmcProfile: IPmcData,
|
||||||
|
scavProfile: IPmcData,
|
||||||
|
postRaidProfile: IPmcData,
|
||||||
|
isDead: boolean,
|
||||||
|
request: IEndLocalRaidRequestData,
|
||||||
|
): void
|
||||||
|
{
|
||||||
|
// Update inventory
|
||||||
|
this.inRaidHelper.setInventory(sessionId, pmcProfile, postRaidProfile);
|
||||||
|
|
||||||
|
pmcProfile.Info.Level = postRaidProfile.Info.Level;
|
||||||
|
|
||||||
|
// Add experience points
|
||||||
|
pmcProfile.Info.Experience += postRaidProfile.Stats.Eft.TotalSessionExperience;
|
||||||
|
|
||||||
|
// Profile common/mastering skills
|
||||||
|
pmcProfile.Skills = postRaidProfile.Skills;
|
||||||
|
|
||||||
|
pmcProfile.Stats.Eft = postRaidProfile.Stats.Eft;
|
||||||
|
|
||||||
|
// Must occur after experience is set and stats copied over
|
||||||
|
pmcProfile.Stats.Eft.TotalSessionExperience = 0;
|
||||||
|
|
||||||
|
pmcProfile.Achievements = postRaidProfile.Achievements;
|
||||||
|
|
||||||
|
// Remove skill fatigue values
|
||||||
|
this.resetSkillPointsEarnedDuringRaid(pmcProfile.Skills.Common);
|
||||||
|
|
||||||
|
// Straight copy
|
||||||
|
pmcProfile.TaskConditionCounters = postRaidProfile.TaskConditionCounters;
|
||||||
|
|
||||||
|
pmcProfile.Encyclopedia = postRaidProfile.Encyclopedia;
|
||||||
|
|
||||||
|
// Must occur after encyclopedia updated
|
||||||
|
this.mergePmcAndScavEncyclopedias(pmcProfile, scavProfile);
|
||||||
|
|
||||||
|
// Handle temp, hydration, limb hp/effects
|
||||||
|
this.healthHelper.updateProfileHealthPostRaid(pmcProfile, postRaidProfile.Health, sessionId, isDead);
|
||||||
|
|
||||||
|
if (isDead)
|
||||||
|
{
|
||||||
|
this.pmcChatResponseService.sendKillerResponse(
|
||||||
|
sessionId,
|
||||||
|
pmcProfile,
|
||||||
|
postRaidProfile.Stats.Eft.Aggressor,
|
||||||
|
);
|
||||||
|
this.matchBotDetailsCacheService.clearCache();
|
||||||
|
|
||||||
|
this.inRaidHelper.deleteInventory(pmcProfile, sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const victims = postRaidProfile.Stats.Eft.Victims.filter((victim) =>
|
||||||
|
["pmcbear", "pmcusec"].includes(victim.Role.toLowerCase()),
|
||||||
|
);
|
||||||
|
if (victims?.length > 0)
|
||||||
|
{
|
||||||
|
// Player killed PMCs, send some responses to them
|
||||||
|
this.pmcChatResponseService.sendVictimResponse(sessionId, victims, pmcProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle items transferred via BTR to player
|
||||||
|
const btrKey = "BTRTransferStash";
|
||||||
|
const btrContainerAndItems = request.transferItems[btrKey] ?? [];
|
||||||
|
if (btrContainerAndItems.length > 0)
|
||||||
|
{
|
||||||
|
const itemsToSend = btrContainerAndItems.filter((item) => item._id !== btrKey);
|
||||||
|
this.btrItemDelivery(sessionId, Traders.BTR, itemsToSend);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.lostInsuredItems?.length > 0)
|
||||||
|
{
|
||||||
|
// TODO - refactor code to work
|
||||||
|
|
||||||
|
// Get array of insured items+child that were lost in raid
|
||||||
|
// const gearToStore = this.insuranceService.getGearLostInRaid(
|
||||||
|
// pmcProfile,
|
||||||
|
// postRaidRequest,
|
||||||
|
// preRaidGear,
|
||||||
|
// sessionId,
|
||||||
|
// isDead,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// this.insuranceService.storeGearLostInRaidToSendLater(
|
||||||
|
// sessionId,
|
||||||
|
// gearToStore,
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle singleplayer/traderServices/itemDelivery
|
||||||
|
*/
|
||||||
|
protected btrItemDelivery(sessionId: string, traderId: string, items: Item[]): void
|
||||||
|
{
|
||||||
|
const serverProfile = this.saveServer.getProfile(sessionId);
|
||||||
|
const pmcData = serverProfile.characters.pmc;
|
||||||
|
|
||||||
|
const dialogueTemplates = this.databaseService.getTrader(traderId).dialogue;
|
||||||
|
if (!dialogueTemplates)
|
||||||
|
{
|
||||||
|
this.logger.error(this.localisationService.getText("inraid-unable_to_deliver_item_no_trader_found", traderId));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const messageId = this.randomUtil.getArrayValue(dialogueTemplates.itemsDelivered);
|
||||||
|
const messageStoreTime = this.timeUtil.getHoursAsSeconds(this.traderConfig.fence.btrDeliveryExpireHours);
|
||||||
|
|
||||||
|
// Remove any items that were returned by the item delivery, but also insured, from the player's insurance list
|
||||||
|
// This is to stop items being duplicated by being returned from both item delivery and insurance
|
||||||
|
const deliveredItemIds = items.map((item) => item._id);
|
||||||
|
pmcData.InsuredItems = pmcData.InsuredItems
|
||||||
|
.filter((insuredItem) => !deliveredItemIds.includes(insuredItem.itemId));
|
||||||
|
|
||||||
|
// Send the items to the player
|
||||||
|
this.mailSendService.sendLocalisedNpcMessageToPlayer(
|
||||||
|
sessionId,
|
||||||
|
this.traderHelper.getTraderById(traderId),
|
||||||
|
MessageType.BTR_ITEMS_DELIVERY,
|
||||||
|
messageId,
|
||||||
|
items,
|
||||||
|
messageStoreTime,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the player dead after a raid - dead = anything other than "survived" / "runner"
|
||||||
|
* @param statusOnExit Exit value from offraidData object
|
||||||
|
* @returns true if dead
|
||||||
|
*/
|
||||||
|
protected isPlayerDead(results: IEndRaidResult): boolean
|
||||||
|
{
|
||||||
|
return ["killed", "missinginaction", "left"].includes(results.result.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the skill points earned in a raid to 0, ready for next raid
|
||||||
|
* @param commonSkills Profile common skills to update
|
||||||
|
*/
|
||||||
|
protected resetSkillPointsEarnedDuringRaid(commonSkills: Common[]): void
|
||||||
|
{
|
||||||
|
for (const skill of commonSkills)
|
||||||
|
{
|
||||||
|
skill.PointsEarnedDuringSession = 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* merge two dictionaries together
|
||||||
|
* Prioritise pair that has true as a value
|
||||||
|
* @param primary main dictionary
|
||||||
|
* @param secondary Secondary dictionary
|
||||||
|
*/
|
||||||
|
protected mergePmcAndScavEncyclopedias(primary: IPmcData, secondary: IPmcData): void
|
||||||
|
{
|
||||||
|
function extend(target: { [key: string]: boolean }, source: Record<string, boolean>)
|
||||||
|
{
|
||||||
|
for (const key in source)
|
||||||
|
{
|
||||||
|
if (Object.hasOwn(source, key))
|
||||||
|
{
|
||||||
|
target[key] = source[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
const merged = extend(extend({}, primary.Encyclopedia), secondary.Encyclopedia);
|
||||||
|
primary.Encyclopedia = merged;
|
||||||
|
secondary.Encyclopedia = merged;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user