Files
bitburner-src/src/PersonObjects/Person.ts
T
2026-04-19 12:06:54 -07:00

266 lines
8.1 KiB
TypeScript

import type { Person as IPerson, WorkStats } from "@nsdefs";
import type { PlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
import type { IReviverValue } from "../utils/JSONReviver";
import type { MoneySource } from "../utils/MoneySourceTracker";
import type { HP } from "./HP";
import type { Skills } from "./Skills";
import { CityName } from "@enums";
import { currentNodeMults } from "../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../Constants";
import { Player } from "../Player";
import { defaultMultipliers } from "./Multipliers";
import { calculateExp, calculateSkill } from "./formulas/skill";
// Base class representing a person-like object
export abstract class Person implements IPerson {
hp: HP = { current: 10, max: 10 };
skills: Skills = {
hacking: 1,
strength: 1,
defense: 1,
dexterity: 1,
agility: 1,
charisma: 1,
intelligence: 0,
};
exp: Skills = {
hacking: 0,
strength: 0,
defense: 0,
dexterity: 0,
agility: 0,
charisma: 0,
intelligence: 0,
};
persistentIntelligenceData = {
exp: 0,
};
mults = defaultMultipliers();
/** Augmentations */
augmentations: PlayerOwnedAugmentation[] = [];
queuedAugmentations: PlayerOwnedAugmentation[] = [];
/** City that the person is in */
city: CityName = CityName.Sector12;
gainHackingExp(exp: number): void {
if (isNaN(exp)) {
console.error("ERR: NaN passed into Player.gainHackingExp()");
return;
}
this.exp.hacking += exp;
if (this.exp.hacking < 0) {
this.exp.hacking = 0;
}
this.skills.hacking = calculateSkill(
this.exp.hacking,
this.mults.hacking * currentNodeMults.HackingLevelMultiplier,
);
}
gainStrengthExp(exp: number): void {
if (isNaN(exp)) {
console.error("ERR: NaN passed into Player.gainStrengthExp()");
return;
}
this.exp.strength += exp;
if (this.exp.strength < 0) {
this.exp.strength = 0;
}
this.skills.strength = calculateSkill(
this.exp.strength,
this.mults.strength * currentNodeMults.StrengthLevelMultiplier,
);
}
gainDefenseExp(exp: number): void {
if (isNaN(exp)) {
console.error("ERR: NaN passed into player.gainDefenseExp()");
return;
}
this.exp.defense += exp;
if (this.exp.defense < 0) {
this.exp.defense = 0;
}
this.skills.defense = calculateSkill(
this.exp.defense,
this.mults.defense * currentNodeMults.DefenseLevelMultiplier,
);
const ratio = this.hp.current / this.hp.max;
this.hp.max = Math.floor(10 + this.skills.defense / 10);
this.hp.current = Math.round(this.hp.max * ratio);
}
gainDexterityExp(exp: number): void {
if (isNaN(exp)) {
console.error("ERR: NaN passed into Player.gainDexterityExp()");
return;
}
this.exp.dexterity += exp;
if (this.exp.dexterity < 0) {
this.exp.dexterity = 0;
}
this.skills.dexterity = calculateSkill(
this.exp.dexterity,
this.mults.dexterity * currentNodeMults.DexterityLevelMultiplier,
);
}
gainAgilityExp(exp: number): void {
if (isNaN(exp)) {
console.error("ERR: NaN passed into Player.gainAgilityExp()");
return;
}
this.exp.agility += exp;
if (this.exp.agility < 0) {
this.exp.agility = 0;
}
this.skills.agility = calculateSkill(
this.exp.agility,
this.mults.agility * currentNodeMults.AgilityLevelMultiplier,
);
}
gainCharismaExp(exp: number): void {
if (isNaN(exp)) {
console.error("ERR: NaN passed into Player.gainCharismaExp()");
return;
}
this.exp.charisma += exp;
if (this.exp.charisma < 0) {
this.exp.charisma = 0;
}
this.skills.charisma = calculateSkill(
this.exp.charisma,
this.mults.charisma * currentNodeMults.CharismaLevelMultiplier,
);
}
overrideIntelligence(): void {
// Reset intelligence data if the player has not unlocked Intelligence.
// Note that this check cannot reset intelligence data in some edge cases (e.g., bitflume from non-BN5 to BN5). This
// is an accepted limitation.
// For more information, please check https://github.com/bitburner-official/bitburner-src/pull/2666
if (Player.sourceFileLvl(5) === 0 && Player.bitNodeN !== 5) {
this.skills.intelligence = 0;
this.exp.intelligence = 0;
this.persistentIntelligenceData.exp = 0;
return;
}
const persistentIntelligenceSkill = this.calculateSkill(this.persistentIntelligenceData.exp, 1);
// Reset exp and skill to the persistent values if there is no limit (intelligenceOverride) or the limit is greater
// than or equal to the persistent skill.
if (
Player.bitNodeOptions.intelligenceOverride === undefined ||
Player.bitNodeOptions.intelligenceOverride >= persistentIntelligenceSkill
) {
this.exp.intelligence = this.persistentIntelligenceData.exp;
this.skills.intelligence = persistentIntelligenceSkill;
return;
}
// Limit exp and skill based on intelligenceOverride only if it's smaller than the persistent skill.
this.exp.intelligence = calculateExp(Player.bitNodeOptions.intelligenceOverride, 1);
this.skills.intelligence = Player.bitNodeOptions.intelligenceOverride;
}
gainIntelligenceExp(exp: number): void {
if (isNaN(exp)) {
console.error("ERROR: NaN passed into Player.gainIntelligenceExp()");
return;
}
/**
* Don't change sourceFileLvl to activeSourceFileLvl. When the player has int level, the ability to gain more int is
* a permanent benefit.
*/
if (Player.sourceFileLvl(5) > 0 || Player.bitNodeN === 5) {
this.exp.intelligence += exp;
this.skills.intelligence = Math.floor(this.calculateSkill(this.exp.intelligence, 1));
this.persistentIntelligenceData.exp += exp;
}
}
gainStats(retValue: WorkStats): void {
this.gainHackingExp(retValue.hackExp * this.mults.hacking_exp);
this.gainStrengthExp(retValue.strExp * this.mults.strength_exp);
this.gainDefenseExp(retValue.defExp * this.mults.defense_exp);
this.gainDexterityExp(retValue.dexExp * this.mults.dexterity_exp);
this.gainAgilityExp(retValue.agiExp * this.mults.agility_exp);
this.gainCharismaExp(retValue.chaExp * this.mults.charisma_exp);
this.gainIntelligenceExp(retValue.intExp);
}
regenerateHp(amt: number): void {
if (typeof amt !== "number") {
console.warn(`Player.regenerateHp() called without a numeric argument: ${amt}`);
return;
}
this.hp.current += amt;
if (this.hp.current > this.hp.max) {
this.hp.current = this.hp.max;
}
}
updateSkillLevels(this: Person): void {
for (const [skill, bnMult] of [
["hacking", "HackingLevelMultiplier"],
["strength", "StrengthLevelMultiplier"],
["defense", "DefenseLevelMultiplier"],
["dexterity", "DexterityLevelMultiplier"],
["agility", "AgilityLevelMultiplier"],
["charisma", "CharismaLevelMultiplier"],
] as const) {
this.skills[skill] = Math.max(
1,
Math.floor(this.calculateSkill(this.exp[skill], this.mults[skill] * currentNodeMults[bnMult])),
);
}
const ratio: number = Math.min(this.hp.current / this.hp.max, 1);
this.hp.max = Math.floor(10 + this.skills.defense / 10);
this.hp.current = Math.round(this.hp.max * ratio);
}
hasAugmentation(augName: string, ignoreQueued = false) {
if (this.augmentations.some((a) => a.name === augName)) {
return true;
}
if (!ignoreQueued && this.queuedAugmentations.some((a) => a.name === augName)) {
return true;
}
return false;
}
travel(cityName: CityName): boolean {
if (!Player.canAfford(CONSTANTS.TravelCost)) {
return false;
}
Player.loseMoney(CONSTANTS.TravelCost, this.travelCostMoneySource());
this.city = cityName;
return true;
}
calculateSkill = calculateSkill; //Class version is equal to imported version
/** Reset all multipliers to 1 */
resetMultipliers() {
this.mults = defaultMultipliers();
}
abstract travelCostMoneySource(): MoneySource;
abstract takeDamage(amt: number): boolean;
abstract whoAmI(): string;
abstract toJSON(): IReviverValue;
}