Further improvements to post-raid event handling

This commit is contained in:
Dev 2024-07-05 13:32:46 +01:00
parent 85e86b969d
commit 7e64a4be66
8 changed files with 299 additions and 36 deletions

View File

@ -42,11 +42,12 @@ export class InraidCallbacks
*/
public saveProgress(url: string, info: ISaveProgressRequestData, sessionID: string): INullResponseData
{
this.inraidController.savePostRaidProgress(info, sessionID);
this.inraidController.savePostRaidProgressLegacy(info, sessionID);
return this.httpResponse.nullResponse();
}
/**
* TODO - remove
* Handle singleplayer/settings/raid/endstate
* @returns
*/

View File

@ -139,9 +139,8 @@ export class MatchCallbacks
return this.httpResponse.getBody(true);
}
/** @deprecated - not called on raid start/end or game start/exit */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public putMetrics(url: string, info: IPutMetricsRequestData, sessionId: string): INullResponseData
public putMetrics(url: string, request: IPutMetricsRequestData, sessionId: string): INullResponseData
{
return this.httpResponse.nullResponse();
}

View File

@ -120,7 +120,7 @@ export class InraidController
* @param offraidData post-raid request data
* @param sessionID Session id
*/
public savePostRaidProgress(offraidData: ISaveProgressRequestData, sessionID: string): void
public savePostRaidProgressLegacy(offraidData: ISaveProgressRequestData, sessionID: string): void
{
this.logger.debug(`Raid outcome: ${offraidData.exit}`);
@ -159,7 +159,7 @@ export class InraidController
const serverPmcProfile = serverProfile.characters.pmc;
const serverScavProfile = serverProfile.characters.scav;
const isDead = this.isPlayerDead(postRaidRequest.exit);
const isDead = true;// this.isPlayerDead(postRaidRequest.exit);
const preRaidGear = this.inRaidHelper.getPlayerGear(serverPmcProfile.Inventory.items);
serverProfile.inraid.character = "pmc";
@ -355,7 +355,7 @@ export class InraidController
{
const serverPmcProfile = this.profileHelper.getPmcProfile(sessionID);
const serverScavProfile = this.profileHelper.getScavProfile(sessionID);
const isDead = this.isPlayerDead(postRaidRequest.exit);
const isDead = true;// this.isPlayerDead(postRaidRequest.exit);
const preRaidScavCharismaProgress = this.profileHelper.getSkillFromProfile(
serverScavProfile,
SkillTypes.CHARISMA,
@ -537,16 +537,6 @@ export class InraidController
}
}
/**
* Is the player dead after a raid - dead is anything other than "survived" / "runner"
* @param statusOnExit exit value from offraidData object
* @returns true if dead
*/
protected isPlayerDead(statusOnExit: PlayerRaidEndState): boolean
{
return statusOnExit !== PlayerRaidEndState.SURVIVED && statusOnExit !== PlayerRaidEndState.RUNNER;
}
/**
* Mark inventory items as FiR if player survived raid, otherwise remove FiR from them
* @param offraidData Save Progress Request

View File

@ -90,13 +90,13 @@ export class LocationController
}
// Check for a loot multipler adjustment in app context and apply if one is found
let locationConfigCopy: ILocationConfig;
let locationConfigClone: ILocationConfig;
const raidAdjustments = this.applicationContext
.getLatestValue(ContextVariableType.RAID_ADJUSTMENTS)
?.getValue<IRaidChanges>();
if (raidAdjustments)
{
locationConfigCopy = this.cloner.clone(this.locationConfig); // Clone values so they can be used to reset originals later
locationConfigClone = this.cloner.clone(this.locationConfig); // Clone values so they can be used to reset originals later
this.raidTimeAdjustmentService.makeAdjustmentsToMap(raidAdjustments, locationBaseClone);
}
@ -128,8 +128,8 @@ export class LocationController
if (raidAdjustments)
{
this.logger.debug("Resetting loot multipliers back to their original values");
this.locationConfig.staticLootMultiplier = locationConfigCopy.staticLootMultiplier;
this.locationConfig.looseLootMultiplier = locationConfigCopy.looseLootMultiplier;
this.locationConfig.staticLootMultiplier = locationConfigClone.staticLootMultiplier;
this.locationConfig.looseLootMultiplier = locationConfigClone.looseLootMultiplier;
this.applicationContext.clearValues(ContextVariableType.RAID_ADJUSTMENTS);
}

View File

@ -1,13 +1,17 @@
import { inject, injectable } from "tsyringe";
import { ApplicationContext } from "@spt/context/ApplicationContext";
import { ContextVariableType } from "@spt/context/ContextVariableType";
import { InraidController } from "@spt/controllers/InraidController";
import { LocationController } from "@spt/controllers/LocationController";
import { LootGenerator } from "@spt/generators/LootGenerator";
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 { Common } from "@spt/models/eft/common/tables/IBotBase";
import { Item } from "@spt/models/eft/common/tables/IItem";
import { IEndLocalRaidRequestData } from "@spt/models/eft/match/IEndLocalRaidRequestData";
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 { IMatchGroupStartGameRequest } from "@spt/models/eft/match/IMatchGroupStartGameRequest";
@ -19,9 +23,11 @@ import { IStartLocalRaidResponseData } from "@spt/models/eft/match/IStartLocalRa
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 { IMatchConfig } from "@spt/models/spt/config/IMatchConfig";
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 { ILogger } from "@spt/models/spt/utils/ILogger";
import { ConfigServer } from "@spt/servers/ConfigServer";
@ -43,6 +49,8 @@ export class MatchController
protected inRaidConfig: IInRaidConfig;
protected traderConfig: ITraderConfig;
protected pmcConfig: IPmcConfig;
protected ragfairConfig: IRagfairConfig;
protected hideoutConfig: IHideoutConfig;
constructor(
@inject("PrimaryLogger") protected logger: ILogger,
@ -53,6 +61,9 @@ export class MatchController
@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("TraderHelper") protected traderHelper: TraderHelper,
@inject("BotLootCacheService") protected botLootCacheService: BotLootCacheService,
@ -68,6 +79,8 @@ export class MatchController
this.inRaidConfig = this.configServer.getConfig(ConfigTypes.IN_RAID);
this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER);
this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC);
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
this.hideoutConfig = this.configServer.getConfig(ConfigTypes.HIDEOUT);
}
public getEnabled(): boolean
@ -359,7 +372,7 @@ export class MatchController
const playerProfile = this.profileHelper.getPmcProfile(sessionId);
const result: IStartLocalRaidResponseData = {
serverId: this.hashUtil.generate(), // TODO - does this need to be more verbose - investigate client?
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.locationController.generate(request.location),
@ -370,7 +383,10 @@ export class MatchController
public endLocalRaid(sessionId: string, request: IEndLocalRaidRequestData): void
{
const playerProfile = this.profileHelper.getPmcProfile(sessionId);
const fullProfile = this.profileHelper.getFullProfile(sessionId);
const pmcProfile = fullProfile.characters.pmc;
const scavProfile = fullProfile.characters.scav;
const postRaidProfile = request.results.profile!;
// TODO:
// Update profile
@ -382,7 +398,101 @@ export class MatchController
// Limb health
// Limb effects
// Skills
// Inventory
// 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 map = this.databaseService.getLocation(locationName).base;
const isDead = this.isPlayerDead(request.results);
// 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);
}
/**
* 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"].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;
}
}

View File

@ -1,5 +1,6 @@
import { inject, injectable } from "tsyringe";
import { IPmcData } from "@spt/models/eft/common/IPmcData";
import { BodyPartsHealth, Health } from "@spt/models/eft/common/tables/IBotBase";
import { ISyncHealthRequestData } from "@spt/models/eft/health/ISyncHealthRequestData";
import { Effects, ISptProfile } from "@spt/models/eft/profile/ISptProfile";
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
@ -66,6 +67,113 @@ export class HealthHelper
return profile;
}
/**
* Update player profile vitality values with changes from client request object
* @param pmcData Player profile
* @param postRaidHealth Post raid data
* @param sessionID Session id
* @param isDead Is player dead
* @param addEffects Should effects be added to profile (default - true)
* @param deleteExistingEffects Should all prior effects be removed before apply new ones (default - true)
*/
public updateProfileHealthPostRaid(
pmcData: IPmcData,
postRaidHealth: Health,
sessionID: string,
isDead: boolean,
): void
{
const fullProfile = this.saveServer.getProfile(sessionID);
this.storeHydrationEnergyTempInProfile(
fullProfile,
postRaidHealth.Hydration.Current,
postRaidHealth.Energy.Current,
postRaidHealth.Temperature.Current);
// Store limb effects from post-raid in profile
for (const bodyPart in postRaidHealth.BodyParts)
{
// Effects
if (postRaidHealth.BodyParts[bodyPart].Effects)
{
fullProfile.vitality.effects[bodyPart] = postRaidHealth.BodyParts[bodyPart].Effects;
}
// Limb hp
if (!isDead)
{
// Player alive, not is limb alive
fullProfile.vitality.health[bodyPart] = postRaidHealth.BodyParts[bodyPart].Current;
}
else
{
fullProfile.vitality.health[bodyPart]
= pmcData.Health.BodyParts[bodyPart].Health.Maximum * this.healthConfig.healthMultipliers.death;
}
}
this.transferPostRaidLimbEffectsToProfile(postRaidHealth.BodyParts, pmcData);
// Adjust hydration/energy/temp and limb hp using temp storage hydated above
this.saveHealth(pmcData, sessionID);
// Reset temp storage
this.resetVitality(sessionID);
// Update last edited timestamp
pmcData.Health.UpdateTime = this.timeUtil.getTimestamp();
}
protected storeHydrationEnergyTempInProfile(
fullProfile: ISptProfile,
hydration: number,
energy: number,
temprature: number): void
{
fullProfile.vitality.health.Hydration = hydration;
fullProfile.vitality.health.Energy = energy;
fullProfile.vitality.health.Temperature = temprature;
}
/**
* Take body part effects from client profile and apply to server profile
* @param postRaidBodyParts Post-raid body part data
* @param profileData Player profile on server
*/
protected transferPostRaidLimbEffectsToProfile(
postRaidBodyParts: BodyPartsHealth,
profileData: IPmcData,
): void
{
// Iterate over each body part
for (const bodyPartId in postRaidBodyParts)
{
// Get effects on body part from profile
const bodyPartEffects = postRaidBodyParts[bodyPartId].Effects;
for (const effect in bodyPartEffects)
{
const effectDetails = bodyPartEffects[effect];
// Null guard
if (!profileData.Health.BodyParts[bodyPartId].Effects)
{
profileData.Health.BodyParts[bodyPartId].Effects = {};
}
// Already exists on server profile, skip
const profileBodyPartEffects = profileData.Health.BodyParts[bodyPartId].Effects;
if (profileBodyPartEffects[effect])
{
continue;
}
// Add effect to server profile
profileBodyPartEffects[effect] = { Time: effectDetails.Time ?? -1 };
}
}
}
/**
* Update player profile vitality values with changes from client request object
* @param pmcData Player profile
@ -83,13 +191,14 @@ export class HealthHelper
): void
{
const postRaidBodyParts = request.Health; // post raid health settings
const profile = this.saveServer.getProfile(sessionID);
const profileHealth = profile.vitality.health;
const profileEffects = profile.vitality.effects;
const fullProfile = this.saveServer.getProfile(sessionID);
const profileEffects = fullProfile.vitality.effects;
profileHealth.Hydration = request.Hydration!;
profileHealth.Energy = request.Energy!;
profileHealth.Temperature = request.Temperature!;
this.storeHydrationEnergyTempInProfile(
fullProfile,
request.Hydration!,
request.Energy!,
request.Temperature!);
// Process request data into profile
for (const bodyPart in postRaidBodyParts)
@ -103,11 +212,11 @@ export class HealthHelper
if (request.IsAlive)
{
// Player alive, not is limb alive
profileHealth[bodyPart] = postRaidBodyParts[bodyPart].Current;
fullProfile.vitality.health[bodyPart] = postRaidBodyParts[bodyPart].Current;
}
else
{
profileHealth[bodyPart]
fullProfile.vitality.health[bodyPart]
= pmcData.Health.BodyParts[bodyPart].Health.Maximum * this.healthConfig.healthMultipliers.death;
}
}

View File

@ -5,7 +5,7 @@ import { Item } from "../common/tables/IItem";
export interface IEndLocalRaidRequestData
{
serverId: string
result: IEndRaidResult
results: IEndRaidResult
lostInsuredItems: Item[]
transferItems: Record<string, Item[]>
}
@ -13,6 +13,7 @@ export interface IEndLocalRaidRequestData
export interface IEndRaidResult
{
profile: IPmcData
result: string
ExitStatus: ExitStatus
killerId: string
killerAid: string

View File

@ -2,10 +2,63 @@ export interface IPutMetricsRequestData
{
sid: string
settings: any
SharedSettings: any
HardwareDescription: any
SharedSettings: ISharedSettings
HardwareDescription: IHardwareDescription
Location: string
Metrics: any
ClientEvents: any
ClientEvents: IClientEvents
SpikeSamples: any[]
mode: string
}
export interface ISharedSettings
{
StatedFieldOfView: number
}
export interface IHardwareDescription
{
deviceUniqueIdentifier: string
systemMemorySize: number
graphicsDeviceID: number
graphicsDeviceName: string
graphicsDeviceType: string
graphicsDeviceVendor: string
graphicsDeviceVendorID: number
graphicsDeviceVersion: string
graphicsMemorySize: number
graphicsMultiThreaded: boolean
graphicsShaderLevel: number
operatingSystem: string
processorCount: number
processorFrequency: number
processorType: string
driveType: string
swapDriveType: string
}
export interface IClientEvents
{
MatchingCompleted: number
MatchingCompletedReal: number
LocationLoaded: number
LocationLoadedReal: number
GamePrepared: number
GamePreparedReal: number
GameCreated: number
GameCreatedReal: number
GamePooled: number
GamePooledReal: number
GameRunned: number
GameRunnedReal: number
GameSpawn: number
GameSpawnReal: number
PlayerSpawnEvent: number
PlayerSpawnEventReal: number
GameSpawned: number
GameSpawnedReal: number
GameStarting: number
GameStartingReal: number
GameStarted: number
GameStartedReal: number
}