Server/project/src/helpers/HealthHelper.ts
2023-03-03 15:23:46 +00:00

259 lines
8.8 KiB
TypeScript

import { inject, injectable } from "tsyringe";
import { IPmcData } from "../models/eft/common/IPmcData";
import { ISyncHealthRequestData } from "../models/eft/health/ISyncHealthRequestData";
import { Effects, IAkiProfile } from "../models/eft/profile/IAkiProfile";
import { ConfigTypes } from "../models/enums/ConfigTypes";
import { IHealthConfig } from "../models/spt/config/IHealthConfig";
import { ILogger } from "../models/spt/utils/ILogger";
import { ConfigServer } from "../servers/ConfigServer";
import { SaveServer } from "../servers/SaveServer";
import { JsonUtil } from "../utils/JsonUtil";
import { TimeUtil } from "../utils/TimeUtil";
@injectable()
export class HealthHelper
{
protected healthConfig: IHealthConfig;
constructor(
@inject("JsonUtil") protected jsonUtil: JsonUtil,
@inject("WinstonLogger") protected logger: ILogger,
@inject("TimeUtil") protected timeUtil: TimeUtil,
@inject("SaveServer") protected saveServer: SaveServer,
@inject("ConfigServer") protected configServer: ConfigServer
)
{
this.healthConfig = this.configServer.getConfig(ConfigTypes.HEALTH);
}
/**
* Resets the profiles vitality/health and vitality/effects properties to their defaults
* @param sessionID Session Id
* @returns updated profile
*/
public resetVitality(sessionID: string): IAkiProfile
{
const profile = this.saveServer.getProfile(sessionID);
if (!profile.vitality) // Occurs on newly created profiles
{
profile.vitality = {
health: null,
effects: null
};
}
profile.vitality.health = {
Hydration: 0,
Energy: 0,
Temperature: 0,
Head: 0,
Chest: 0,
Stomach: 0,
LeftArm: 0,
RightArm: 0,
LeftLeg: 0,
RightLeg: 0
};
profile.vitality.effects = {
Head: {},
Chest: {},
Stomach: {},
LeftArm: {},
RightArm: {},
LeftLeg: {},
RightLeg: {}
};
return profile;
}
/**
* Update player profile with changes from request object
* @param pmcData Player profile
* @param request Heal request
* @param sessionID Session id
* @param addEffects Should effects be added or removed (default - add)
*/
public saveVitality(pmcData: IPmcData, request: ISyncHealthRequestData, sessionID: string, addEffects = true, deleteExistingEffects = true): 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;
profileHealth.Hydration = request.Hydration;
profileHealth.Energy = request.Energy;
profileHealth.Temperature = request.Temperature;
// Transfer properties from request to profile
for (const bodyPart in postRaidBodyParts)
{
// Transfer effects from request to profile
if (postRaidBodyParts[bodyPart].Effects)
{
profileEffects[bodyPart] = postRaidBodyParts[bodyPart].Effects;
}
if (request.IsAlive === true) // is player alive, not is limb alive
{
profileHealth[bodyPart] = postRaidBodyParts[bodyPart].Current;
}
else
{
profileHealth[bodyPart] = pmcData.Health.BodyParts[bodyPart].Health.Maximum * this.healthConfig.healthMultipliers.death;
}
}
// Add effects to body parts
if (addEffects)
{
this.saveEffects(pmcData, sessionID, this.jsonUtil.clone(this.saveServer.getProfile(sessionID).vitality.effects), deleteExistingEffects);
}
// Adjust hydration/energy/temp and limb hp
this.saveHealth(pmcData, sessionID);
this.resetVitality(sessionID);
pmcData.Health.UpdateTime = this.timeUtil.getTimestamp();
}
/**
* Adjust hydration/energy/temperate and body part hp values in player profile to values in profile.vitality
* @param pmcData Profile to update
* @param sessionId Session id
*/
protected saveHealth(pmcData: IPmcData, sessionID: string): void
{
if (!this.healthConfig.save.health)
{
return;
}
const profileHealth = this.saveServer.getProfile(sessionID).vitality.health;
for (const healthModifier in profileHealth)
{
let target = profileHealth[healthModifier];
if (["Hydration", "Energy", "Temperature"].includes(healthModifier))
{
// Set resources
if (target > pmcData.Health[healthModifier].Maximum)
{
target = pmcData.Health[healthModifier].Maximum;
}
pmcData.Health[healthModifier].Current = Math.round(target);
}
else
{
// Over max, limit
if (target > pmcData.Health.BodyParts[healthModifier].Health.Maximum)
{
target = pmcData.Health.BodyParts[healthModifier].Health.Maximum;
}
// Part was zeroed out in raid
if (target === 0)
{
// Blacked body part
target = Math.round(pmcData.Health.BodyParts[healthModifier].Health.Maximum * this.healthConfig.healthMultipliers.blacked);
}
pmcData.Health.BodyParts[healthModifier].Health.Current = Math.round(target);
}
}
}
/**
* Save effects to profile
* Works by removing all effects and adding them back from profile
* Removes empty 'Effects' objects if found
* @param pmcData Player profile
* @param sessionId Session id
* @param bodyPartsWithEffects dict of body parts with effects that should be added to profile
* @param addEffects Should effects be added back to profile
*/
protected saveEffects(pmcData: IPmcData, sessionId: string, bodyPartsWithEffects: Effects, deleteExistingEffects = true): void
{
if (!this.healthConfig.save.effects)
{
return;
}
for (const bodyPart in bodyPartsWithEffects)
{
// clear effects from profile bodyPart
if (deleteExistingEffects)
{
delete pmcData.Health.BodyParts[bodyPart].Effects;
}
for (const effectType in bodyPartsWithEffects[bodyPart])
{
if (typeof effectType !== "string")
{
this.logger.warning(`Effect ${effectType} on body part ${bodyPart} not a string, report this`);
}
// // data can be index or the effect string (e.g. "Fracture") itself
// const effect = /^-?\d+$/.test(effectValue) // is an int
// ? nodeEffects[bodyPart][effectValue]
// : effectValue;
let time = bodyPartsWithEffects[bodyPart][effectType];
if (time)
{
// Sometimes the value can be Infinity instead of -1, blame HealthListener.cs in modules
if (time === "Infinity")
{
this.logger.warning(`Effect ${effectType} found with value of Infinity, changed to -1, this is an issue with HealthListener.cs`);
time = -1;
}
this.addEffect(pmcData, bodyPart, effectType, time);
}
else
{
this.addEffect(pmcData, bodyPart, effectType);
}
}
}
}
/**
* Add effect to body part in profile
* @param pmcData Player profile
* @param effectBodyPart body part to edit
* @param effectType Effect to add to body part
* @param duration How long the effect has left in seconds (-1 by default, no duration).
*/
protected addEffect(pmcData: IPmcData, effectBodyPart: string, effectType: string, duration = -1): void
{
const profileBodyPart = pmcData.Health.BodyParts[effectBodyPart];
if (!profileBodyPart.Effects)
{
profileBodyPart.Effects = {};
}
profileBodyPart.Effects[effectType] = { Time: duration };
// Delete empty property to prevent client bugs
if (this.isEmpty(profileBodyPart.Effects))
{
delete profileBodyPart.Effects;
}
}
protected isEmpty(map: any): boolean
{
for (const key in map)
{
if (key in map)
{
return false;
}
}
return true;
}
}