Merge pull request #1979 from MartinFournier/feature/skill-progress-bars

Fix #1889: Add skill progress to overview
This commit is contained in:
hydroflame 2021-12-18 14:44:18 -05:00 committed by GitHub
commit b739d60490
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 149 additions and 4 deletions

@ -29,6 +29,7 @@ import { ICodingContractReward } from "../CodingContracts";
import { IRouter } from "../ui/Router"; import { IRouter } from "../ui/Router";
import { WorkerScript } from "../Netscript/WorkerScript"; import { WorkerScript } from "../Netscript/WorkerScript";
import { HacknetServer } from "../Hacknet/HacknetServer"; import { HacknetServer } from "../Hacknet/HacknetServer";
import { ISkillProgress } from "./formulas/skill";
export interface IPlayer { export interface IPlayer {
// Class members // Class members
@ -259,6 +260,7 @@ export interface IPlayer {
prestigeAugmentation(): void; prestigeAugmentation(): void;
prestigeSourceFile(): void; prestigeSourceFile(): void;
calculateSkill(exp: number, mult?: number): number; calculateSkill(exp: number, mult?: number): number;
calculateSkillProgress(exp: number, mult?: number): ISkillProgress;
resetWorkStatus(generalType?: string, group?: string, workType?: string): void; resetWorkStatus(generalType?: string, group?: string, workType?: string): void;
getWorkHackExpGain(): number; getWorkHackExpGain(): number;
getWorkStrExpGain(): number; getWorkStrExpGain(): number;

@ -34,6 +34,7 @@ import { CityName } from "../../Locations/data/CityNames";
import { MoneySourceTracker } from "../../utils/MoneySourceTracker"; import { MoneySourceTracker } from "../../utils/MoneySourceTracker";
import { Reviver, Generic_toJSON, Generic_fromJSON } from "../../utils/JSONReviver"; import { Reviver, Generic_toJSON, Generic_fromJSON } from "../../utils/JSONReviver";
import { ISkillProgress } from "../formulas/skill";
export class PlayerObject implements IPlayer { export class PlayerObject implements IPlayer {
// Class members // Class members
@ -265,6 +266,7 @@ export class PlayerObject implements IPlayer {
prestigeAugmentation: () => void; prestigeAugmentation: () => void;
prestigeSourceFile: () => void; prestigeSourceFile: () => void;
calculateSkill: (exp: number, mult?: number) => number; calculateSkill: (exp: number, mult?: number) => number;
calculateSkillProgress: (exp: number, mult?: number) => ISkillProgress;
resetWorkStatus: (generalType?: string, group?: string, workType?: string) => void; resetWorkStatus: (generalType?: string, group?: string, workType?: string) => void;
getWorkHackExpGain: () => number; getWorkHackExpGain: () => number;
getWorkStrExpGain: () => number; getWorkStrExpGain: () => number;
@ -470,6 +472,7 @@ export class PlayerObject implements IPlayer {
this.prestigeSourceFile = generalMethods.prestigeSourceFile; this.prestigeSourceFile = generalMethods.prestigeSourceFile;
this.receiveInvite = generalMethods.receiveInvite; this.receiveInvite = generalMethods.receiveInvite;
this.calculateSkill = generalMethods.calculateSkill; this.calculateSkill = generalMethods.calculateSkill;
this.calculateSkillProgress = generalMethods.calculateSkillProgress;
this.updateSkillLevels = generalMethods.updateSkillLevels; this.updateSkillLevels = generalMethods.updateSkillLevels;
this.resetMultipliers = generalMethods.resetMultipliers; this.resetMultipliers = generalMethods.resetMultipliers;
this.hasProgram = generalMethods.hasProgram; this.hasProgram = generalMethods.hasProgram;

@ -26,7 +26,7 @@ import { Locations } from "../../Locations/Locations";
import { CityName } from "../../Locations/data/CityNames"; import { CityName } from "../../Locations/data/CityNames";
import { LocationName } from "../../Locations/data/LocationNames"; import { LocationName } from "../../Locations/data/LocationNames";
import { Sleeve } from "../../PersonObjects/Sleeve/Sleeve"; 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 { calculateIntelligenceBonus } from "../formulas/intelligence";
import { import {
getHackingWorkRepGain, getHackingWorkRepGain,
@ -226,6 +226,11 @@ export function calculateSkill(this: IPlayer, exp: number, mult = 1): number {
return calculateSkillF(exp, mult); 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 { export function updateSkillLevels(this: IPlayer): void {
this.hacking = Math.max( this.hacking = Math.max(
1, 1,

@ -5,3 +5,37 @@ export function calculateSkill(exp: number, mult = 1): number {
export function calculateExp(skill: number, mult = 1): number { export function calculateExp(skill: number, mult = 1): number {
return Math.exp((skill / mult + 200) / 32) - 534.6; 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,
};
}

@ -1,7 +1,7 @@
// Root React Component for the Corporation UI // Root React Component for the Corporation UI
import React, { useState, useEffect } from "react"; 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 makeStyles from "@mui/styles/makeStyles";
import createStyles from "@mui/styles/createStyles"; import createStyles from "@mui/styles/createStyles";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
@ -20,6 +20,8 @@ import ClearAllIcon from "@mui/icons-material/ClearAll";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { use } from "../Context"; import { use } from "../Context";
import { StatsProgressOverviewCell } from "./StatsProgressBar";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
interface IProps { interface IProps {
save: () => void; save: () => void;
@ -139,6 +141,8 @@ const useStyles = makeStyles((theme: Theme) =>
}), }),
); );
export { useStyles as characterOverviewStyles };
export function CharacterOverview({ save, killScripts }: IProps): React.ReactElement { export function CharacterOverview({ save, killScripts }: IProps): React.ReactElement {
const [killOpen, setKillOpen] = useState(false); const [killOpen, setKillOpen] = useState(false);
const player = use.Player(); const player = use.Player();
@ -151,6 +155,21 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
}, []); }, []);
const classes = useStyles(); 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 ( return (
<> <>
<Table sx={{ display: "block", m: 1 }}> <Table sx={{ display: "block", m: 1 }}>
@ -186,12 +205,20 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cell }}> <TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.hack }}>Hack&nbsp;</Typography> <Typography classes={{ root: classes.hack }}>Hack&nbsp;</Typography>
</TableCell> </TableCell>
<TableCell align="right" classes={{ root: classes.cell }}> <TableCell align="right" classes={{ root: classes.cellNone }}>
<Typography classes={{ root: classes.hack }}>{numeralWrapper.formatSkill(player.hacking)}</Typography> <Typography classes={{ root: classes.hack }}>{numeralWrapper.formatSkill(player.hacking)}</Typography>
</TableCell> </TableCell>
</TableRow>
<TableRow>
<StatsProgressOverviewCell progress={hackingProgress} color={theme.colors.hack} />
</TableRow>
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cell }}>
<Typography classes={{ root: classes.hack }}></Typography>
</TableCell>
<TableCell align="right" classes={{ root: classes.cell }}> <TableCell align="right" classes={{ root: classes.cell }}>
<Typography id="overview-hack-hook" classes={{ root: classes.hack }}> <Typography id="overview-hack-hook" classes={{ root: classes.hack }}>
{/*Hook for player scripts*/} {/*Hook for player scripts*/}
@ -212,6 +239,9 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
</Typography> </Typography>
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow>
<StatsProgressOverviewCell progress={strengthProgress} color={theme.colors.combat} />
</TableRow>
<TableRow> <TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}> <TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
@ -226,6 +256,9 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
</Typography> </Typography>
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow>
<StatsProgressOverviewCell progress={defenseProgress} color={theme.colors.combat} />
</TableRow>
<TableRow> <TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}> <TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
@ -240,6 +273,10 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
</Typography> </Typography>
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow>
<StatsProgressOverviewCell progress={dexterityProgress} color={theme.colors.combat} />
</TableRow>
<TableRow> <TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cell }}> <TableCell component="th" scope="row" classes={{ root: classes.cell }}>
<Typography classes={{ root: classes.combat }}>Agi&nbsp;</Typography> <Typography classes={{ root: classes.combat }}>Agi&nbsp;</Typography>
@ -253,6 +290,9 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
</Typography> </Typography>
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow>
<StatsProgressOverviewCell progress={agilityProgress} color={theme.colors.combat} />
</TableRow>
<TableRow> <TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cellNone }}> <TableCell component="th" scope="row" classes={{ root: classes.cellNone }}>
@ -267,6 +307,10 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
</Typography> </Typography>
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow>
<StatsProgressOverviewCell progress={charismaProgress} color={theme.colors.cha} />
</TableRow>
<Intelligence /> <Intelligence />
<TableRow> <TableRow>

@ -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)}
<br />
{normalise(current).toFixed(2)}%
</>;
return (
<Tooltip title={tooltipText}>
<LinearProgress
variant="determinate"
value={normalise(current)}
sx={{
backgroundColor: '#111111',
'& .MuiLinearProgress-bar1Determinate': {
backgroundColor: color,
},
}}
/>
</Tooltip>
);
}
export function StatsProgressOverviewCell({progress, color}: IStatsOverviewCellProps): React.ReactElement {
const classes = characterOverviewStyles();
return (
<TableCell component="th" scope="row" colSpan={2}
classes={{ root: classes.cellNone }}
style={{ paddingBottom: '2px', position: 'relative', top: '-3px' }}>
<StatsProgressBar
min={progress.baseExperience}
max={progress.nextExperience}
current={progress.experience}
color={color}
/>
</TableCell>
)
}