mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-05-08 08:37:48 +02:00
TYPESAFETY: Strict internal typing for AugmentationName (#608)
This commit is contained in:
@@ -1,14 +1,16 @@
|
||||
import { StaticAugmentations } from "../../Augmentation/StaticAugmentations";
|
||||
import { GraftableAugmentation } from "./GraftableAugmentation";
|
||||
import { Player } from "@player";
|
||||
import { AugmentationName, FactionName } from "@enums";
|
||||
import { Augmentations } from "../../Augmentation/Augmentations";
|
||||
import { calculateIntelligenceBonus } from "../formulas/intelligence";
|
||||
import { GraftableAugmentation } from "./GraftableAugmentation";
|
||||
import { getRecordEntries } from "../../Types/Record";
|
||||
|
||||
export const getGraftingAvailableAugs = (): string[] => {
|
||||
const augs: string[] = [];
|
||||
export const getGraftingAvailableAugs = (): AugmentationName[] => {
|
||||
const augs: AugmentationName[] = [];
|
||||
|
||||
for (const [augName, aug] of Object.entries(StaticAugmentations)) {
|
||||
if (Player.factions.includes("Bladeburners")) {
|
||||
if (aug.isSpecial && !aug.factions.includes("Bladeburners")) continue;
|
||||
for (const [augName, aug] of getRecordEntries(Augmentations)) {
|
||||
if (Player.factions.includes(FactionName.Bladeburners)) {
|
||||
if (aug.isSpecial && !aug.factions.includes(FactionName.Bladeburners)) continue;
|
||||
} else {
|
||||
if (aug.isSpecial) continue;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import type { Augmentation } from "../../../Augmentation/Augmentation";
|
||||
|
||||
import { Player } from "@player";
|
||||
import { AugmentationName, LocationName } from "@enums";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { CheckBox, CheckBoxOutlineBlank, Construction } from "@mui/icons-material";
|
||||
import { Box, Button, Container, List, ListItemButton, Paper, Typography } from "@mui/material";
|
||||
|
||||
import { Player } from "@player";
|
||||
import { AugmentationName, LocationName } from "@enums";
|
||||
import { GraftingWork } from "../../../Work/GraftingWork";
|
||||
import { StaticAugmentations } from "../../../Augmentation/StaticAugmentations";
|
||||
import { Augmentations } from "../../../Augmentation/Augmentations";
|
||||
import { CONSTANTS } from "../../../Constants";
|
||||
import { hasAugmentationPrereqs } from "../../../Faction/FactionHelpers";
|
||||
import { Locations } from "../../../Locations/Locations";
|
||||
@@ -25,7 +26,7 @@ import { useRerender } from "../../../ui/React/hooks";
|
||||
|
||||
export const GraftableAugmentations = (): Record<string, GraftableAugmentation> => {
|
||||
const gAugs: Record<string, GraftableAugmentation> = {};
|
||||
for (const aug of Object.values(StaticAugmentations)) {
|
||||
for (const aug of Object.values(Augmentations)) {
|
||||
const name = aug.name;
|
||||
const graftableAug = new GraftableAugmentation(aug);
|
||||
gAugs[name] = graftableAug;
|
||||
@@ -66,10 +67,10 @@ export const GraftingRoot = (): React.ReactElement => {
|
||||
|
||||
const [selectedAug, setSelectedAug] = useState(getGraftingAvailableAugs()[0]);
|
||||
const [graftOpen, setGraftOpen] = useState(false);
|
||||
const selectedAugmentation = StaticAugmentations[selectedAug];
|
||||
const selectedAugmentation = Augmentations[selectedAug];
|
||||
const rerender = useRerender(200);
|
||||
|
||||
const getAugsSorted = (): string[] => {
|
||||
const getAugsSorted = (): AugmentationName[] => {
|
||||
const augs = getGraftingAvailableAugs();
|
||||
switch (Settings.PurchaseAugmentationsOrder) {
|
||||
case PurchaseAugmentationsOrderSetting.Cost:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Person } from "./Person";
|
||||
import { calculateSkill } from "./formulas/skill";
|
||||
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
||||
import { currentNodeMults } from "../BitNode/BitNodeMultipliers";
|
||||
import { Player } from "@player";
|
||||
import { WorkStats } from "@nsdefs";
|
||||
|
||||
@@ -14,10 +14,7 @@ export function gainHackingExp(this: Person, exp: number): void {
|
||||
this.exp.hacking = 0;
|
||||
}
|
||||
|
||||
this.skills.hacking = calculateSkill(
|
||||
this.exp.hacking,
|
||||
this.mults.hacking * BitNodeMultipliers.HackingLevelMultiplier,
|
||||
);
|
||||
this.skills.hacking = calculateSkill(this.exp.hacking, this.mults.hacking * currentNodeMults.HackingLevelMultiplier);
|
||||
}
|
||||
|
||||
export function gainStrengthExp(this: Person, exp: number): void {
|
||||
@@ -32,7 +29,7 @@ export function gainStrengthExp(this: Person, exp: number): void {
|
||||
|
||||
this.skills.strength = calculateSkill(
|
||||
this.exp.strength,
|
||||
this.mults.strength * BitNodeMultipliers.StrengthLevelMultiplier,
|
||||
this.mults.strength * currentNodeMults.StrengthLevelMultiplier,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -46,10 +43,7 @@ export function gainDefenseExp(this: Person, exp: number): void {
|
||||
this.exp.defense = 0;
|
||||
}
|
||||
|
||||
this.skills.defense = calculateSkill(
|
||||
this.exp.defense,
|
||||
this.mults.defense * BitNodeMultipliers.DefenseLevelMultiplier,
|
||||
);
|
||||
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);
|
||||
@@ -67,7 +61,7 @@ export function gainDexterityExp(this: Person, exp: number): void {
|
||||
|
||||
this.skills.dexterity = calculateSkill(
|
||||
this.exp.dexterity,
|
||||
this.mults.dexterity * BitNodeMultipliers.DexterityLevelMultiplier,
|
||||
this.mults.dexterity * currentNodeMults.DexterityLevelMultiplier,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -81,10 +75,7 @@ export function gainAgilityExp(this: Person, exp: number): void {
|
||||
this.exp.agility = 0;
|
||||
}
|
||||
|
||||
this.skills.agility = calculateSkill(
|
||||
this.exp.agility,
|
||||
this.mults.agility * BitNodeMultipliers.AgilityLevelMultiplier,
|
||||
);
|
||||
this.skills.agility = calculateSkill(this.exp.agility, this.mults.agility * currentNodeMults.AgilityLevelMultiplier);
|
||||
}
|
||||
|
||||
export function gainCharismaExp(this: Person, exp: number): void {
|
||||
@@ -99,7 +90,7 @@ export function gainCharismaExp(this: Person, exp: number): void {
|
||||
|
||||
this.skills.charisma = calculateSkill(
|
||||
this.exp.charisma,
|
||||
this.mults.charisma * BitNodeMultipliers.CharismaLevelMultiplier,
|
||||
this.mults.charisma * currentNodeMults.CharismaLevelMultiplier,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -164,33 +155,29 @@ export function regenerateHp(this: Person, amt: number): void {
|
||||
export function updateSkillLevels(this: Person): void {
|
||||
this.skills.hacking = Math.max(
|
||||
1,
|
||||
Math.floor(this.calculateSkill(this.exp.hacking, this.mults.hacking * BitNodeMultipliers.HackingLevelMultiplier)),
|
||||
Math.floor(this.calculateSkill(this.exp.hacking, this.mults.hacking * currentNodeMults.HackingLevelMultiplier)),
|
||||
);
|
||||
this.skills.strength = Math.max(
|
||||
1,
|
||||
Math.floor(
|
||||
this.calculateSkill(this.exp.strength, this.mults.strength * BitNodeMultipliers.StrengthLevelMultiplier),
|
||||
),
|
||||
Math.floor(this.calculateSkill(this.exp.strength, this.mults.strength * currentNodeMults.StrengthLevelMultiplier)),
|
||||
);
|
||||
this.skills.defense = Math.max(
|
||||
1,
|
||||
Math.floor(this.calculateSkill(this.exp.defense, this.mults.defense * BitNodeMultipliers.DefenseLevelMultiplier)),
|
||||
Math.floor(this.calculateSkill(this.exp.defense, this.mults.defense * currentNodeMults.DefenseLevelMultiplier)),
|
||||
);
|
||||
this.skills.dexterity = Math.max(
|
||||
1,
|
||||
Math.floor(
|
||||
this.calculateSkill(this.exp.dexterity, this.mults.dexterity * BitNodeMultipliers.DexterityLevelMultiplier),
|
||||
this.calculateSkill(this.exp.dexterity, this.mults.dexterity * currentNodeMults.DexterityLevelMultiplier),
|
||||
),
|
||||
);
|
||||
this.skills.agility = Math.max(
|
||||
1,
|
||||
Math.floor(this.calculateSkill(this.exp.agility, this.mults.agility * BitNodeMultipliers.AgilityLevelMultiplier)),
|
||||
Math.floor(this.calculateSkill(this.exp.agility, this.mults.agility * currentNodeMults.AgilityLevelMultiplier)),
|
||||
);
|
||||
this.skills.charisma = Math.max(
|
||||
1,
|
||||
Math.floor(
|
||||
this.calculateSkill(this.exp.charisma, this.mults.charisma * BitNodeMultipliers.CharismaLevelMultiplier),
|
||||
),
|
||||
Math.floor(this.calculateSkill(this.exp.charisma, this.mults.charisma * currentNodeMults.CharismaLevelMultiplier)),
|
||||
);
|
||||
|
||||
const ratio: number = Math.min(this.hp.current / this.hp.max, 1);
|
||||
|
||||
@@ -26,6 +26,7 @@ import { cyrb53 } from "../../utils/StringHelperFunctions";
|
||||
import { getRandomInt } from "../../utils/helpers/getRandomInt";
|
||||
import { CONSTANTS } from "../../Constants";
|
||||
import { Person } from "../Person";
|
||||
import { getEnumHelper } from "../../utils/EnumHelper";
|
||||
|
||||
export class PlayerObject extends Person implements IPlayer {
|
||||
// Player-specific properties
|
||||
@@ -167,9 +168,19 @@ export class PlayerObject extends Person implements IPlayer {
|
||||
/** Initializes a PlayerObject object from a JSON save state. */
|
||||
static fromJSON(value: IReviverValue): PlayerObject {
|
||||
const player = Generic_fromJSON(PlayerObject, value.data);
|
||||
// Any statistics that could be infinite would be serialized as null (JSON.stringify(Infinity) is "null")
|
||||
player.hp = { current: player.hp?.current ?? 10, max: player.hp?.max ?? 10 };
|
||||
player.money ??= 0;
|
||||
// Just remove from the save file any augs that have invalid name
|
||||
player.augmentations = player.augmentations.filter((ownedAug) =>
|
||||
getEnumHelper("AugmentationName").isMember(ownedAug.name),
|
||||
);
|
||||
player.queuedAugmentations = player.queuedAugmentations.filter((ownedAug) =>
|
||||
getEnumHelper("AugmentationName").isMember(ownedAug.name),
|
||||
);
|
||||
player.updateSkillLevels();
|
||||
// Converstion code for Player.sourceFiles is here instead of normal save conversion area because it needs
|
||||
// to happen earlier for use in the savegame comparison tool.
|
||||
if (Array.isArray(player.sourceFiles)) {
|
||||
// Expect pre-2.3 sourcefile format here.
|
||||
type OldSourceFiles = { n: number; lvl: number }[];
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { AugmentationName, CityName, CompletedProgramName, FactionName, LocationName, ToastVariant } from "@enums";
|
||||
|
||||
import type { PlayerObject } from "./PlayerObject";
|
||||
import type { ProgramFilePath } from "../../Paths/ProgramFilePath";
|
||||
|
||||
import { applyAugmentation } from "../../Augmentation/AugmentationHelpers";
|
||||
import { PlayerOwnedAugmentation } from "../../Augmentation/PlayerOwnedAugmentation";
|
||||
import { AugmentationName, CityName, CompletedProgramName, FactionName, LocationName, ToastVariant } from "@enums";
|
||||
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
||||
import { currentNodeMults } from "../../BitNode/BitNodeMultipliers";
|
||||
import { CodingContractRewardType, ICodingContractReward } from "../../CodingContracts";
|
||||
import { Company } from "../../Company/Company";
|
||||
import { Companies } from "../../Company/Companies";
|
||||
@@ -548,22 +549,16 @@ export function reapplyAllAugmentations(this: PlayerObject, resetMultipliers = t
|
||||
this.resetMultipliers();
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.augmentations.length; ++i) {
|
||||
//Compatibility with new version
|
||||
if (this.augmentations[i].name === "HacknetNode NIC Architecture Neural-Upload") {
|
||||
this.augmentations[i].name = "Hacknet Node NIC Architecture Neural-Upload";
|
||||
}
|
||||
|
||||
const playerAug = this.augmentations[i];
|
||||
for (const playerAug of this.augmentations) {
|
||||
const augName = playerAug.name;
|
||||
|
||||
if (augName == AugmentationName.NeuroFluxGovernor) {
|
||||
for (let j = 0; j < playerAug.level; ++j) {
|
||||
applyAugmentation(this.augmentations[i], true);
|
||||
for (let i = 0; i < playerAug.level; ++i) {
|
||||
applyAugmentation(playerAug, true);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
applyAugmentation(this.augmentations[i], true);
|
||||
applyAugmentation(playerAug, true);
|
||||
}
|
||||
|
||||
this.updateSkillLevels();
|
||||
@@ -644,7 +639,7 @@ export function checkForFactionInvitations(this: PlayerObject): Faction[] {
|
||||
!daedalusFac.isBanned &&
|
||||
!daedalusFac.isMember &&
|
||||
!daedalusFac.alreadyInvited &&
|
||||
numAugmentations >= BitNodeMultipliers.DaedalusAugsRequirement &&
|
||||
numAugmentations >= currentNodeMults.DaedalusAugsRequirement &&
|
||||
this.money >= 100000000000 &&
|
||||
(this.skills.hacking >= 2500 ||
|
||||
(this.skills.strength >= 1500 &&
|
||||
@@ -1080,7 +1075,7 @@ export function setBitNodeNumber(this: PlayerObject, n: number): void {
|
||||
this.bitNodeN = n;
|
||||
}
|
||||
|
||||
export function queueAugmentation(this: PlayerObject, name: string): void {
|
||||
export function queueAugmentation(this: PlayerObject, name: AugmentationName): void {
|
||||
for (const aug of this.queuedAugmentations) {
|
||||
if (aug.name == name) {
|
||||
console.warn(`tried to queue ${name} twice, this may be a bug`);
|
||||
@@ -1152,7 +1147,7 @@ export function gainCodingContractReward(
|
||||
}
|
||||
case CodingContractRewardType.Money:
|
||||
default: {
|
||||
const moneyGain = CONSTANTS.CodingContractBaseMoneyGain * difficulty * BitNodeMultipliers.CodingContractMoney;
|
||||
const moneyGain = CONSTANTS.CodingContractBaseMoneyGain * difficulty * currentNodeMults.CodingContractMoney;
|
||||
this.gainMoney(moneyGain, "codingcontract");
|
||||
return `Gained ${formatMoney(moneyGain)}`;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Server and HacknetServer-related methods for the Player class (PlayerObject)
|
||||
import { CONSTANTS } from "../../Constants";
|
||||
|
||||
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
||||
import { currentNodeMults } from "../../BitNode/BitNodeMultipliers";
|
||||
import { Server } from "../../Server/Server";
|
||||
import { BaseServer } from "../../Server/BaseServer";
|
||||
import { HacknetServer } from "../../Hacknet/HacknetServer";
|
||||
@@ -35,7 +35,7 @@ export function getUpgradeHomeRamCost(this: PlayerObject): number {
|
||||
//Calculate cost
|
||||
//Have cost increase by some percentage each time RAM has been upgraded
|
||||
const mult = Math.pow(1.58, numUpgrades);
|
||||
const cost = currentRam * CONSTANTS.BaseCostFor1GBOfRamHome * mult * BitNodeMultipliers.HomeComputerRamCost;
|
||||
const cost = currentRam * CONSTANTS.BaseCostFor1GBOfRamHome * mult * currentNodeMults.HomeComputerRamCost;
|
||||
return cost;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,14 +7,16 @@
|
||||
* Sleeves are unlocked in BitNode-10.
|
||||
*/
|
||||
|
||||
import type { SleevePerson } from "@nsdefs";
|
||||
import type { Augmentation } from "../../Augmentation/Augmentation";
|
||||
import type { Company } from "../../Company/Company";
|
||||
import type { CompanyPosition } from "../../Company/CompanyPosition";
|
||||
import type { SleeveWork } from "./Work/Work";
|
||||
|
||||
import { Player } from "@player";
|
||||
import { Person } from "../Person";
|
||||
|
||||
import { Augmentation } from "../../Augmentation/Augmentation";
|
||||
|
||||
import { Companies } from "../../Company/Companies";
|
||||
import { Company } from "../../Company/Company";
|
||||
import { CompanyPosition } from "../../Company/CompanyPosition";
|
||||
import { CompanyPositions } from "../../Company/CompanyPositions";
|
||||
import { Contracts } from "../../Bladeburner/data/Contracts";
|
||||
import { CONSTANTS } from "../../Constants";
|
||||
@@ -24,7 +26,6 @@ import { Factions } from "../../Faction/Factions";
|
||||
|
||||
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../../utils/JSONReviver";
|
||||
import { formatPercent } from "../../ui/formatNumber";
|
||||
import { SleeveWork } from "./Work/Work";
|
||||
import { SleeveClassWork } from "./Work/SleeveClassWork";
|
||||
import { SleeveSynchroWork } from "./Work/SleeveSynchroWork";
|
||||
import { SleeveRecoveryWork } from "./Work/SleeveRecoveryWork";
|
||||
@@ -35,8 +36,8 @@ import { SleeveSupportWork } from "./Work/SleeveSupportWork";
|
||||
import { SleeveBladeburnerWork } from "./Work/SleeveBladeburnerWork";
|
||||
import { SleeveCrimeWork } from "./Work/SleeveCrimeWork";
|
||||
import * as sleeveMethods from "./SleeveMethods";
|
||||
import { SleevePerson } from "@nsdefs";
|
||||
import { calculateIntelligenceBonus } from "../formulas/intelligence";
|
||||
import { getEnumHelper } from "../../utils/EnumHelper";
|
||||
|
||||
export class Sleeve extends Person implements SleevePerson {
|
||||
currentWork: SleeveWork | null = null;
|
||||
@@ -475,8 +476,17 @@ export class Sleeve extends Person implements SleevePerson {
|
||||
|
||||
/** Initializes a Sleeve object from a JSON save state. */
|
||||
static fromJSON(value: IReviverValue): Sleeve {
|
||||
if (!value.data.hp?.current || !value.data.hp?.max) value.data.hp = { current: 10, max: 10 };
|
||||
return Generic_fromJSON(Sleeve, value.data);
|
||||
const sleeve = Generic_fromJSON(Sleeve, value.data);
|
||||
if (!sleeve.hp?.current || !sleeve.hp?.max) sleeve.hp = { current: 10, max: 10 };
|
||||
// Remove any invalid aug names on game load
|
||||
sleeve.augmentations = sleeve.augmentations.filter((ownedAug) =>
|
||||
getEnumHelper("AugmentationName").isMember(ownedAug.name),
|
||||
);
|
||||
sleeve.queuedAugmentations = sleeve.queuedAugmentations.filter((ownedAug) =>
|
||||
getEnumHelper("AugmentationName").isMember(ownedAug.name),
|
||||
);
|
||||
|
||||
return sleeve;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,11 @@ import { Player } from "@player";
|
||||
import { AugmentationName, FactionName } from "@enums";
|
||||
import { Sleeve } from "./Sleeve";
|
||||
import { Augmentation } from "../../Augmentation/Augmentation";
|
||||
import { StaticAugmentations } from "../../Augmentation/StaticAugmentations";
|
||||
import { Augmentations } from "../../Augmentation/Augmentations";
|
||||
import { Factions } from "../../Faction/Factions";
|
||||
import { mergeMultipliers, Multipliers } from "../Multipliers";
|
||||
import { getFactionAugmentationsFiltered } from "../../Faction/FactionHelpers";
|
||||
import { getAugCost } from "../../Augmentation/AugmentationHelpers";
|
||||
|
||||
/** Updates this object's multipliers for the given augmentation */
|
||||
export function applyAugmentation(this: Sleeve, aug: Augmentation): void {
|
||||
@@ -61,10 +62,10 @@ export function findPurchasableAugs(this: Sleeve): Augmentation[] {
|
||||
const gangAugs = getFactionAugmentationsFiltered(fac);
|
||||
|
||||
for (const augName of gangAugs) {
|
||||
const aug = StaticAugmentations[augName];
|
||||
const aug = Augmentations[augName];
|
||||
if (!isAvailableForSleeve(aug)) continue;
|
||||
|
||||
if (fac.playerReputation > aug.getCost().repCost) {
|
||||
if (fac.playerReputation > getAugCost(aug).repCost) {
|
||||
availableAugs.push(aug);
|
||||
}
|
||||
}
|
||||
@@ -77,10 +78,10 @@ export function findPurchasableAugs(this: Sleeve): Augmentation[] {
|
||||
if (!fac) continue;
|
||||
|
||||
for (const augName of fac.augmentations) {
|
||||
const aug = StaticAugmentations[augName];
|
||||
const aug = Augmentations[augName];
|
||||
if (!isAvailableForSleeve(aug)) continue;
|
||||
|
||||
if (fac.playerReputation > aug.getCost().repCost) {
|
||||
if (fac.playerReputation > getAugCost(aug).repCost) {
|
||||
availableAugs.push(aug);
|
||||
}
|
||||
}
|
||||
@@ -88,7 +89,7 @@ export function findPurchasableAugs(this: Sleeve): Augmentation[] {
|
||||
|
||||
// Add the stanek sleeve aug
|
||||
if (!ownedAugNames.includes(AugmentationName.ZOE) && Player.factions.includes(FactionName.ChurchOfTheMachineGod)) {
|
||||
const aug = StaticAugmentations[AugmentationName.ZOE];
|
||||
const aug = Augmentations[AugmentationName.ZOE];
|
||||
availableAugs.push(aug);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CONSTANTS } from "../../Constants";
|
||||
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
||||
import { currentNodeMults } from "../../BitNode/BitNodeMultipliers";
|
||||
import { CalculateShareMult } from "../../NetworkShare/Share";
|
||||
import { Person as IPerson } from "@nsdefs";
|
||||
import { calculateIntelligenceBonus } from "./intelligence";
|
||||
@@ -9,7 +9,7 @@ function mult(favor: number): number {
|
||||
if (isNaN(favorMult)) {
|
||||
favorMult = 1;
|
||||
}
|
||||
return favorMult * BitNodeMultipliers.FactionWorkRepGain;
|
||||
return favorMult * currentNodeMults.FactionWorkRepGain;
|
||||
}
|
||||
|
||||
export function getHackingWorkRepGain(p: IPerson, favor: number): number {
|
||||
|
||||
Reference in New Issue
Block a user