4ac12ef70a
These are the formatting & linting configuration changes from the `3.8.0` branch and the changes that they make to the overall project. The majority of these changes are from running two commands: `npm run lint:fix` `npm run style:fix` This has already been run on the `3.8.0` branch and this PR should make `master` play nicer when it comes to merges going forward. There are now four VSCode plugins recommended for server development. They've been added to the workspace file and a user should get a UI notification when the workspace is opened if they're not installed. The four plugins are: https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig https://marketplace.visualstudio.com/items?itemName=dprint.dprint https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint https://marketplace.visualstudio.com/items?itemName=biomejs.biome Once installed they should just work within the workspace. Also, be sure to `npm i` to get the new dprint application. Co-authored-by: Refringe <brownelltyler@gmail.com> Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/168
280 lines
9.3 KiB
TypeScript
280 lines
9.3 KiB
TypeScript
import { inject, injectable } from "tsyringe";
|
|
|
|
import { IPmcData } from "@spt-aki/models/eft/common/IPmcData";
|
|
import { ISyncHealthRequestData } from "@spt-aki/models/eft/health/ISyncHealthRequestData";
|
|
import { Effects, IAkiProfile } from "@spt-aki/models/eft/profile/IAkiProfile";
|
|
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
|
|
import { IHealthConfig } from "@spt-aki/models/spt/config/IHealthConfig";
|
|
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
|
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
|
|
import { SaveServer } from "@spt-aki/servers/SaveServer";
|
|
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
|
|
import { TimeUtil } from "@spt-aki/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)
|
|
* @param deleteExistingEffects Should all prior effects be removed before apply new ones
|
|
*/
|
|
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: Record<string, { Time: number; }>): boolean
|
|
{
|
|
for (const key in map)
|
|
{
|
|
if (key in map)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|