From 84c77c1d2cd300528a4c56848615fa5139b87cb9 Mon Sep 17 00:00:00 2001 From: Martin Fournier Date: Fri, 17 Dec 2021 09:35:54 -0500 Subject: [PATCH] Fix #1889: Add skill progress to overview Adds a progress bar for each stat to show how close to level up you are. --- src/PersonObjects/IPlayer.ts | 2 + src/PersonObjects/Player/PlayerObject.ts | 3 + .../Player/PlayerObjectGeneralMethods.tsx | 7 ++- src/PersonObjects/formulas/skill.ts | 34 +++++++++++ src/ui/React/CharacterOverview.tsx | 50 +++++++++++++++- src/ui/React/StatsProgressBar.tsx | 57 +++++++++++++++++++ 6 files changed, 149 insertions(+), 4 deletions(-) create mode 100644 src/ui/React/StatsProgressBar.tsx diff --git a/src/PersonObjects/IPlayer.ts b/src/PersonObjects/IPlayer.ts index 346c65581..c5c3ef8c9 100644 --- a/src/PersonObjects/IPlayer.ts +++ b/src/PersonObjects/IPlayer.ts @@ -29,6 +29,7 @@ import { ICodingContractReward } from "../CodingContracts"; import { IRouter } from "../ui/Router"; import { WorkerScript } from "../Netscript/WorkerScript"; import { HacknetServer } from "../Hacknet/HacknetServer"; +import { ISkillProgress } from "./formulas/skill"; export interface IPlayer { // Class members @@ -259,6 +260,7 @@ export interface IPlayer { prestigeAugmentation(): void; prestigeSourceFile(): void; calculateSkill(exp: number, mult?: number): number; + calculateSkillProgress(exp: number, mult?: number): ISkillProgress; resetWorkStatus(generalType?: string, group?: string, workType?: string): void; getWorkHackExpGain(): number; getWorkStrExpGain(): number; diff --git a/src/PersonObjects/Player/PlayerObject.ts b/src/PersonObjects/Player/PlayerObject.ts index 74974ff0d..bce4ec3c6 100644 --- a/src/PersonObjects/Player/PlayerObject.ts +++ b/src/PersonObjects/Player/PlayerObject.ts @@ -34,6 +34,7 @@ import { CityName } from "../../Locations/data/CityNames"; import { MoneySourceTracker } from "../../utils/MoneySourceTracker"; import { Reviver, Generic_toJSON, Generic_fromJSON } from "../../utils/JSONReviver"; +import { ISkillProgress } from "../formulas/skill"; export class PlayerObject implements IPlayer { // Class members @@ -265,6 +266,7 @@ export class PlayerObject implements IPlayer { prestigeAugmentation: () => void; prestigeSourceFile: () => void; calculateSkill: (exp: number, mult?: number) => number; + calculateSkillProgress: (exp: number, mult?: number) => ISkillProgress; resetWorkStatus: (generalType?: string, group?: string, workType?: string) => void; getWorkHackExpGain: () => number; getWorkStrExpGain: () => number; @@ -470,6 +472,7 @@ export class PlayerObject implements IPlayer { this.prestigeSourceFile = generalMethods.prestigeSourceFile; this.receiveInvite = generalMethods.receiveInvite; this.calculateSkill = generalMethods.calculateSkill; + this.calculateSkillProgress = generalMethods.calculateSkillProgress; this.updateSkillLevels = generalMethods.updateSkillLevels; this.resetMultipliers = generalMethods.resetMultipliers; this.hasProgram = generalMethods.hasProgram; diff --git a/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx b/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx index c5ed59dbb..9847bc170 100644 --- a/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx +++ b/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx @@ -26,7 +26,7 @@ import { Locations } from "../../Locations/Locations"; import { CityName } from "../../Locations/data/CityNames"; import { LocationName } from "../../Locations/data/LocationNames"; import { Sleeve } from "../../PersonObjects/Sleeve/Sleeve"; -import { calculateSkill as calculateSkillF } from "../formulas/skill"; +import { calculateSkill as calculateSkillF, calculateSkillProgress as calculateSkillProgressF, getEmptySkillProgress, ISkillProgress } from "../formulas/skill"; import { calculateIntelligenceBonus } from "../formulas/intelligence"; import { getHackingWorkRepGain, @@ -226,6 +226,11 @@ export function calculateSkill(this: IPlayer, exp: number, mult = 1): number { return calculateSkillF(exp, mult); } +//Calculates skill level progress based on experience. The same formula will be used for every skill +export function calculateSkillProgress(this: IPlayer, exp: number, mult = 1): ISkillProgress { + return calculateSkillProgressF(exp, mult); +} + export function updateSkillLevels(this: IPlayer): void { this.hacking = Math.max( 1, diff --git a/src/PersonObjects/formulas/skill.ts b/src/PersonObjects/formulas/skill.ts index ac5701bd8..86f422ce4 100644 --- a/src/PersonObjects/formulas/skill.ts +++ b/src/PersonObjects/formulas/skill.ts @@ -5,3 +5,37 @@ export function calculateSkill(exp: number, mult = 1): number { export function calculateExp(skill: number, mult = 1): number { return Math.exp((skill / mult + 200) / 32) - 534.6; } + +export function calculateSkillProgress(exp: number, mult = 1): ISkillProgress { + const currentSkill = calculateSkill(exp, mult); + const nextSkill = currentSkill + 1; + let baseExperience = calculateExp(currentSkill, mult); + if (baseExperience < 0) baseExperience = 0; + const nextExperience = calculateExp(nextSkill, mult) + + return { + currentSkill, + nextSkill, + baseExperience, + experience: exp, + nextExperience, + progress: exp / nextExperience, + } +} + +export interface ISkillProgress { + currentSkill: number; + nextSkill: number; + baseExperience: number; + experience: number; + nextExperience: number; + progress: number; +} + +export function getEmptySkillProgress() { + return { + currentSkill: 0, nextSkill: 0, + baseExperience: 0, experience: 0, nextExperience: 0, + progress: 0, + }; +} diff --git a/src/ui/React/CharacterOverview.tsx b/src/ui/React/CharacterOverview.tsx index dfa01bd11..0264276be 100644 --- a/src/ui/React/CharacterOverview.tsx +++ b/src/ui/React/CharacterOverview.tsx @@ -1,7 +1,7 @@ // Root React Component for the Corporation UI import React, { useState, useEffect } from "react"; -import { Theme } from "@mui/material/styles"; +import { Theme, useTheme } from "@mui/material/styles"; import makeStyles from "@mui/styles/makeStyles"; import createStyles from "@mui/styles/createStyles"; import { numeralWrapper } from "../../ui/numeralFormat"; @@ -20,6 +20,8 @@ import ClearAllIcon from "@mui/icons-material/ClearAll"; import { Settings } from "../../Settings/Settings"; import { use } from "../Context"; +import { StatsProgressOverviewCell } from "./StatsProgressBar"; +import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers"; interface IProps { save: () => void; @@ -139,6 +141,8 @@ const useStyles = makeStyles((theme: Theme) => }), ); +export { useStyles as characterOverviewStyles }; + export function CharacterOverview({ save, killScripts }: IProps): React.ReactElement { const [killOpen, setKillOpen] = useState(false); const player = use.Player(); @@ -151,6 +155,21 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle }, []); const classes = useStyles(); + const theme = useTheme(); + + const hackingProgress = player.calculateSkillProgress( + player.hacking_exp, player.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier); + const strengthProgress = player.calculateSkillProgress( + player.strength_exp, player.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier); + const defenseProgress = player.calculateSkillProgress( + player.defense_exp, player.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier); + const dexterityProgress = player.calculateSkillProgress( + player.dexterity_exp, player.dexterity_mult * BitNodeMultipliers.DexterityLevelMultiplier); + const agilityProgress = player.calculateSkillProgress( + player.agility_exp, player.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier); + const charismaProgress = player.calculateSkillProgress( + player.charisma_exp, player.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier); + return ( <> @@ -186,12 +205,20 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle - + Hack  - + {numeralWrapper.formatSkill(player.hacking)} + + + + + + + + {/*Hook for player scripts*/} @@ -212,6 +239,9 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle + + + @@ -226,6 +256,9 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle + + + @@ -240,6 +273,10 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle + + + + Agi  @@ -253,6 +290,9 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle + + + @@ -267,6 +307,10 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle + + + + diff --git a/src/ui/React/StatsProgressBar.tsx b/src/ui/React/StatsProgressBar.tsx new file mode 100644 index 000000000..a86900a22 --- /dev/null +++ b/src/ui/React/StatsProgressBar.tsx @@ -0,0 +1,57 @@ +import * as React from 'react'; +import LinearProgress from '@mui/material/LinearProgress'; +import { TableCell, Tooltip } from '@mui/material'; +import { characterOverviewStyles } from './CharacterOverview'; +import { ISkillProgress } from 'src/PersonObjects/formulas/skill'; + +interface IProgressProps { + min: number; + max: number; + current: number; + color?: React.CSSProperties["color"]; +} + +interface IStatsOverviewCellProps { + progress: ISkillProgress; + color?: React.CSSProperties["color"]; +} + +export function StatsProgressBar({ min, max, current, color }: IProgressProps): React.ReactElement { + const normalise = (value: number): number => ((value - min) * 100) / (max - min); + const tooltipText = <> + Experience: {current.toFixed(2)}/{max.toFixed(2)} +
+ {normalise(current).toFixed(2)}% + ; + + return ( + + + + ); +} + +export function StatsProgressOverviewCell({progress, color}: IStatsOverviewCellProps): React.ReactElement { + const classes = characterOverviewStyles(); + return ( + + + + ) +}