IPVGO: Improve favor gain from wins to balance around the rep value of favor (#2131)

This commit is contained in:
Michael Ficocelli
2025-05-17 05:36:58 -04:00
committed by GitHub
parent 2b8c008be1
commit 4749acdd4f
19 changed files with 79 additions and 51 deletions

View File

@@ -13,7 +13,7 @@ type SimpleOpponentStats = {
losses: number;
winStreak: number;
highestWinStreak: number;
favor: number;
rep: number;
bonusPercent: number;
bonusDescription: string;
};

View File

@@ -525,11 +525,17 @@ export function initBitNodes() {
<li>Level 2: Permanently unlocks the go.cheat API</li>
<li>Level 3: 25% additive increased success rate for the go.cheat API</li>
</ul>
This Source-File also increases the maximum favor you can gain for each faction from IPvGO to:
This Source-File also increases the maximum favor from winstreaks you can gain for each faction to:
<ul>
<li>Level 1: 80</li>
<li>Level 2: 100</li>
<li>Level 3: 120</li>
<li>Level 1: 200k rep equivalent</li>
<li>Level 2: 300k rep equivalent</li>
<li>Level 3: 400k rep equivalent</li>
</ul>
and increases the reputation converted to favor for winning two games in a row to:
<ul>
<li>Level 1: 1000 rep to favor</li>
<li>Level 2: 1500 rep to favor</li>
<li>Level 3: 2000 rep to favor</li>
</ul>
</>
),

View File

@@ -1,7 +1,7 @@
import type { CompanyPosition } from "./CompanyPosition";
import { CompanyName, JobName, FactionName } from "@enums";
import { MaxFavor, calculateFavorAfterResetting } from "../Faction/formulas/favor";
import { MaxFavor, addRepToFavor } from "../Faction/formulas/favor";
import { clampNumber } from "../utils/helpers/clampNumber";
export interface CompanyCtorParams {
@@ -75,7 +75,7 @@ export class Company {
}
prestigeAugmentation(): void {
this.setFavor(calculateFavorAfterResetting(this.favor, this.playerReputation));
this.setFavor(addRepToFavor(this.favor, this.playerReputation));
this.playerReputation = 0;
}

View File

@@ -1,6 +1,6 @@
import { AugmentationName, FactionName, FactionDiscovery } from "@enums";
import { FactionInfo, FactionInfos } from "./FactionInfo";
import { MaxFavor, calculateFavorAfterResetting } from "./formulas/favor";
import { MaxFavor, addRepToFavor } from "./formulas/favor";
import { clampNumber } from "../utils/helpers/clampNumber";
export class Faction {
@@ -76,7 +76,7 @@ export class Faction {
prestigeAugmentation(): void {
// Gain favor
this.setFavor(calculateFavorAfterResetting(this.favor, this.playerReputation));
this.setFavor(addRepToFavor(this.favor, this.playerReputation));
// Reset reputation and flags
this.playerReputation = 0;
this.alreadyInvited = false;

View File

@@ -19,6 +19,6 @@ export function repToFavor(r: number): number {
return clampNumber(Math.log1p(r / 25000) / log1point02, 0, MaxFavor);
}
export function calculateFavorAfterResetting(favor: number, playerReputation: number) {
export function addRepToFavor(favor: number, playerReputation: number) {
return repToFavor(favorToRep(favor) + playerReputation);
}

View File

@@ -17,7 +17,7 @@ import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box";
import InfoIcon from "@mui/icons-material/Info";
import { useCycleRerender } from "../../ui/React/hooks";
import { calculateFavorAfterResetting } from "../formulas/favor";
import { addRepToFavor } from "../formulas/favor";
import { knowAboutBitverse } from "../../BitNode/BitNodeUtils";
interface IProps {
@@ -74,8 +74,7 @@ export function Info(props: IProps): React.ReactElement {
title={
<>
<Typography>
You will have{" "}
<Favor favor={calculateFavorAfterResetting(props.faction.favor, props.faction.playerReputation)} />{" "}
You will have <Favor favor={addRepToFavor(props.faction.favor, props.faction.playerReputation)} />{" "}
faction favor after installing an Augmentation.
</Typography>
<MathJax>{"\\(\\huge{r = \\text{total faction reputation}}\\)"}</MathJax>

View File

@@ -81,7 +81,7 @@ export function newOpponentStats(): OpponentStats {
winStreak: 0,
oldWinStreak: 0,
highestWinStreak: 0,
favor: 0,
rep: 0,
};
}

View File

@@ -152,10 +152,10 @@ function loadStats(stats: unknown): PartialRecord<GoOpponent, OpponentStats> | s
if (!getEnumHelper("GoOpponent").isMember(opponent)) return `Invalid opponent in Go.stats: ${opponent}`;
if (!opponentStats || typeof opponentStats !== "object") return "Non-object encountered for an opponent's stats";
assertLoadingType<OpponentStats>(opponentStats as object);
const { favor, highestWinStreak, losses, nodes, wins, oldWinStreak, winStreak, nodePower } =
const { rep, highestWinStreak, losses, nodes, wins, oldWinStreak, winStreak, nodePower } =
opponentStats as OpponentStats;
// Integers >= 0. Todo: make a better helper for this.
if (!isInteger(favor) || favor < 0) return "A favor entry in Go.stats was invalid";
if (!isInteger(rep) || rep < 0) return "A rep entry in Go.stats was invalid";
if (!isInteger(highestWinStreak) || highestWinStreak < 0) return "A highestWinStreak entry in Go.stats was invalid";
if (!isInteger(losses) || losses < 0) return "A losses entry in Go.stats was invalid";
if (!isInteger(nodes) || nodes < 0) return "A nodes entry in Go.stats was invalid";
@@ -167,7 +167,7 @@ function loadStats(stats: unknown): PartialRecord<GoOpponent, OpponentStats> | s
// Numbers >= 0
if (!isNumber(nodePower) || nodePower < 0) return "A nodePower entry in Go.stats was invalid";
finalStats[opponent] = { favor, highestWinStreak, losses, nodes, wins, oldWinStreak, winStreak, nodePower };
finalStats[opponent] = { rep, highestWinStreak, losses, nodes, wins, oldWinStreak, winStreak, nodePower };
}
return finalStats;
}

View File

@@ -98,7 +98,7 @@ export type OpponentStats = {
winStreak: number;
oldWinStreak: number;
highestWinStreak: number;
favor: number;
rep: number;
};
export type SimpleOpponentStats = {
@@ -106,7 +106,7 @@ export type SimpleOpponentStats = {
losses: number;
winStreak: number;
highestWinStreak: number;
favor: number;
rep: number;
bonusPercent: number;
bonusDescription: string;
};

View File

@@ -5,11 +5,12 @@ import { GoOpponent, GoColor } from "@enums";
import { newOpponentStats } from "../Constants";
import { getAllChains, getPlayerNeighbors } from "./boardAnalysis";
import { getKomi, resetAI } from "./goAI";
import { getDifficultyMultiplier, getMaxFavor, getWinstreakMultiplier } from "../effects/effect";
import { getDifficultyMultiplier, getMaxRep, getWinstreakMultiplier } from "../effects/effect";
import { isNotNullish } from "../boardState/boardState";
import { Factions } from "../../Faction/Factions";
import { getEnumHelper } from "../../utils/EnumHelper";
import { Go, GoEvents } from "../Go";
import { addRepToFavor } from "../../Faction/formulas/favor";
/**
* Returns the score of the current board.
@@ -49,7 +50,7 @@ export function endGoGame(boardState: BoardState) {
boardState.previousPlayer = null;
const statusToUpdate = getOpponentStats(boardState.ai);
statusToUpdate.favor = statusToUpdate.favor ?? 0;
statusToUpdate.rep = statusToUpdate.rep ?? 0;
const score = getScore(boardState);
if (score[GoColor.black].sum < score[GoColor.white].sum) {
@@ -68,10 +69,13 @@ export function endGoGame(boardState: BoardState) {
factionName &&
statusToUpdate.winStreak % 2 === 0 &&
Player.factions.includes(factionName) &&
statusToUpdate.favor < getMaxFavor()
statusToUpdate.rep < getMaxRep()
) {
Factions[factionName].setFavor(Factions[factionName].favor + 1);
statusToUpdate.favor++;
const currentFavor = Factions[factionName].favor;
const repToAdd = getMaxRep() / 200;
const newFavor = addRepToFavor(currentFavor, repToAdd);
Factions[factionName].setFavor(newFavor);
statusToUpdate.rep += repToAdd;
}
}

View File

@@ -23,22 +23,24 @@ export function CalculateEffect(nodes: number, faction: GoOpponent): number {
/**
* Get maximum favor that you can gain from IPvGO win streaks
* for factions you are a member of
* for factions you are a member of.
*
* This is added as converted rep to avoid making the equivalent value of the favor in rep very different as you approach 150 favor
*/
export function getMaxFavor() {
export function getMaxRep() {
const sourceFileLevel = Player.activeSourceFileLvl(14);
if (sourceFileLevel === 1) {
return 80;
return 200_000;
}
if (sourceFileLevel === 2) {
return 100;
return 300_000;
}
if (sourceFileLevel >= 3) {
return 120;
return 400_000;
}
return 40;
return 100_000;
}
/**

View File

@@ -378,7 +378,7 @@ export function getStats() {
losses: details.losses,
winStreak: details.winStreak,
highestWinStreak: details.highestWinStreak,
favor: details.favor,
rep: details.rep,
bonusPercent: effectPercent,
bonusDescription: effectDescription,
};

View File

@@ -7,7 +7,7 @@ import { getOpponentStats, getScore } from "../boardAnalysis/scoring";
import { GoGameboard } from "./GoGameboard";
import { boardStyles } from "../boardState/goStyles";
import { useRerender } from "../../ui/React/hooks";
import { getBonusText, getMaxFavor } from "../effects/effect";
import { getBonusText, getMaxRep } from "../effects/effect";
import { formatNumber } from "../../ui/formatNumber";
import { GoScoreSummaryTable } from "./GoScoreSummaryTable";
import { getNewBoardState } from "../boardState/boardState";
@@ -115,15 +115,18 @@ export const GoHistoryPage = (): React.ReactElement => {
<Tooltip
title={
<>
Two wins in a row against a faction will give you +1 favor to that faction <br />
(up to a max of {getMaxFavor()} favor), if you are a member of that faction <br />
Two wins in a row against an opponent will give you {getMaxRep() / 200} rep converted to favor
with that faction (up to a max of {getMaxRep()} favor), if you are a member of that faction.
<br />
The rep is immediately applied as favor, meaning it will increase reputation gain right away
without needing an install.
</>
}
>
<TableRow>
<TableCell className={classes.cellNone}>Favor from winstreaks:</TableCell>
<TableCell className={classes.cellNone}>
{data.favor ?? 0} {data.favor === getMaxFavor() ? "(max)" : ""}
{data.rep ?? 0} {data.rep === getMaxRep() ? "(max)" : ""}
</TableCell>
</TableRow>
</Tooltip>

View File

@@ -7,7 +7,7 @@ import { boardStateFromSimpleBoard } from "../boardAnalysis/boardAnalysis";
import { GoTutorialChallenge } from "./GoTutorialChallenge";
import { Router } from "../../ui/GameRoot";
import { Page } from "../../ui/Router";
import { getMaxFavor } from "../effects/effect";
import { getMaxRep } from "../effects/effect";
const captureChallenge = (
<GoTutorialChallenge
@@ -149,8 +149,11 @@ export const GoInstructionsPage = (): React.ReactElement => {
will increase the amount gained, but is not required.
<br />
<br />
Two wins in a row against a faction will give you +1 favor to that faction (up to a max of
{getMaxFavor()} favor), if you are a member of that faction.
Two wins in a row against an opponent will give you {getMaxRep() / 200} rep converted to favor with that
faction (up to a max of {getMaxRep()} favor), if you are a member of that faction.
<br />
The rep is immediately applied as favor, meaning it will increase reputation gain right away without
needing an install.
<br />
<br />
For experienced Go players: IPvGO uses the old traditional Go score rules, area scoring, rather than the

View File

@@ -6,7 +6,7 @@ import { Table, TableBody, TableCell, TableRow, Typography, Tooltip } from "@mui
import { Player } from "@player";
import { GoOpponent, GoColor } from "@enums";
import { Go } from "../Go";
import { getBonusText, getDifficultyMultiplier, getMaxFavor, getWinstreakMultiplier } from "../effects/effect";
import { getBonusText, getDifficultyMultiplier, getMaxRep, getWinstreakMultiplier } from "../effects/effect";
import { boardStyles } from "../boardState/goStyles";
import { formatNumber } from "../../ui/formatNumber";
import { getOpponentStats } from "../boardAnalysis/scoring";
@@ -99,14 +99,19 @@ export const GoScorePowerSummary = ({ finalScore, opponent }: Props) => {
<Tooltip
title={
<>
Two wins in a row against a faction will give you +1 favor to that faction <br />
(up to a max of {getMaxFavor()} favor), if you are a member of that faction
Two wins in a row against an opponent will give you {getMaxRep() / 200} rep converted to favor with that
faction (up to a max of {getMaxRep()} favor), if you are a member of that faction.
<br />
The rep is immediately applied as favor, meaning it will increase reputation gain right away without
needing an install.
</>
}
>
<Typography className={`${classes.inlineFlexBox} ${classes.keyText}`}>
<span>Winstreak Bonus: </span>
<span>+1 favor to {opponent}</span>
<span>
{getMaxRep() / 200} reputation converted to favor with {opponent}
</span>
</Typography>
</Tooltip>
) : (

View File

@@ -24,7 +24,7 @@ import { companyNameAsLocationName } from "../../Company/utils";
import { JobSummary } from "../../Company/ui/JobSummary";
import { StatsTable } from "../../ui/React/StatsTable";
import { JobListings } from "../../Company/ui/JobListings";
import { calculateFavorAfterResetting } from "../../Faction/formulas/favor";
import { addRepToFavor } from "../../Faction/formulas/favor";
interface IProps {
companyName: CompanyName;
@@ -101,8 +101,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
key="repLabel"
title={
<>
You will have{" "}
<Favor favor={calculateFavorAfterResetting(company.favor, company.playerReputation)} /> company
You will have <Favor favor={addRepToFavor(company.favor, company.playerReputation)} /> company
favor upon resetting after installing Augmentations
</>
}

View File

@@ -49,7 +49,7 @@ import { JobTracks } from "../Company/data/JobTracks";
import { ServerConstants } from "../Server/data/Constants";
import { blackOpsArray } from "../Bladeburner/data/BlackOperations";
import { calculateEffectiveRequiredReputation } from "../Company/utils";
import { calculateFavorAfterResetting } from "../Faction/formulas/favor";
import { addRepToFavor } from "../Faction/formulas/favor";
import { validBitNodes } from "../BitNode/BitNodeUtils";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { cat } from "../Terminal/commands/cat";
@@ -754,7 +754,7 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
helpers.checkSingularityAccess(ctx);
const companyName = getEnumHelper("CompanyName").nsGetMember(ctx, _companyName);
const company = Companies[companyName];
return calculateFavorAfterResetting(company.favor, company.playerReputation) - company.favor;
return addRepToFavor(company.favor, company.playerReputation) - company.favor;
},
getFactionInviteRequirements: (ctx) => (_facName) => {
helpers.checkSingularityAccess(ctx);
@@ -924,7 +924,7 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
helpers.checkSingularityAccess(ctx);
const facName = getEnumHelper("FactionName").nsGetMember(ctx, _facName);
const faction = Factions[facName];
return calculateFavorAfterResetting(faction.favor, faction.playerReputation) - faction.favor;
return addRepToFavor(faction.favor, faction.playerReputation) - faction.favor;
},
donateToFaction: (ctx) => (_facName, _amt) => {
helpers.checkSingularityAccess(ctx);

View File

@@ -4442,12 +4442,19 @@ type GoOpponent =
/** @public */
type SimpleOpponentStats = {
/** Number of wins since last reset */
wins: number;
/** Number of losses since last reset*/
losses: number;
/** Current winstreak */
winStreak: number;
/** Highest winstreak since last reset*/
highestWinStreak: number;
favor: number;
/** Favor gain from winstreaks, calculated as converted rep */
rep: number;
/** Stat boost*/
bonusPercent: number;
/** Description of stat boost */
bonusDescription: string;
};

View File

@@ -16,7 +16,7 @@ import { Money } from "./React/Money";
import { StatsRow } from "./React/StatsRow";
import { StatsTable } from "./React/StatsTable";
import { useCycleRerender } from "./React/hooks";
import { getMaxFavor } from "../Go/effects/effect";
import { getMaxRep } from "../Go/effects/effect";
import { canAccessBitNodeFeature, knowAboutBitverse } from "../BitNode/BitNodeUtils";
interface EmployersModalProps {
@@ -566,7 +566,7 @@ export function CharacterStats(): React.ReactElement {
},
{
mult: "IPvGO Max Favor",
value: getMaxFavor(),
value: getMaxRep(),
isNumber: true,
},
]}