Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI: Share RAM to boost reputation gain #1862

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/Faction/ui/FactionRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { GangButton } from "./GangButton";
import { FactionWork } from "../../Work/FactionWork";
import { useCycleRerender } from "../../ui/React/hooks";
import { repNeededToDonate } from "../formulas/donation";
import { ShareOption } from "./ShareOption";

type FactionRootProps = {
faction: Faction;
Expand All @@ -45,7 +46,7 @@ const securityWorkInfo =
"You will gain exp for all combat stats and hacking.";
const augmentationsInfo =
"As your reputation with this faction rises, you will " +
"unlock Augmentations, which you can purchase to enhance " +
"unlock augmentations, which you can purchase to enhance " +
"your abilities.";
const sleevePurchasesInfo = "Purchase Duplicate Sleeves and upgrades. These are permanent!";

Expand Down Expand Up @@ -131,6 +132,7 @@ function MainPage({ faction, rerender, onAugmentations }: IMainProps): React.Rea
{!isPlayersGang && factionInfo.offersWork() && (
<DonateOption faction={faction} rerender={rerender} favorToDonate={favorToDonate} disabled={!canDonate} />
)}
{!isPlayersGang && factionInfo.offersWork() && <ShareOption rerender={rerender} />}
<Option buttonText={"Purchase Augmentations"} infoText={augmentationsInfo} onClick={onAugmentations} />
{canPurchaseSleeves && (
<>
Expand Down
1 change: 0 additions & 1 deletion src/Faction/ui/Info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ interface IProps {
const useStyles = makeStyles()({
noformat: {
whiteSpace: "pre-wrap",
lineHeight: "1em",
},
});

Expand Down
87 changes: 87 additions & 0 deletions src/Faction/ui/ShareOption.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React, { useState } from "react";

import { TextField } from "@mui/material";
import Button from "@mui/material/Button";
import Paper from "@mui/material/Paper";
import Typography from "@mui/material/Typography";

import { Player } from "@player";
import {
calculateCurrentShareBonus,
calculateShareBonusWithAdditionalThreads,
pendingUIShareJobIds,
ShareBonusTime,
startSharing,
} from "../../NetworkShare/Share";
import { formatRam } from "../../ui/formatNumber";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { useCycleRerender } from "../../ui/React/hooks";
import { roundToTwo } from "../../utils/helpers/roundToTwo";

export function ShareOption({ rerender }: { rerender: () => void }): React.ReactElement {
const [ram, setRam] = useState<number>(0);
useCycleRerender();

const home = Player.getHomeComputer();
const threads = Math.floor(ram / 4);

function onShare(): void {
if (threads === 0) {
return;
}
if (!Number.isFinite(threads) || threads < 0) {
dialogBoxCreate("Invalid RAM amount.");
return;
}
const freeRAM = home.maxRam - home.ramUsed;
const ramUsage = roundToTwo(4 * threads);
if (ramUsage > freeRAM + 0.001) {
dialogBoxCreate("Not enough RAM.");
return;
}

home.updateRamUsed(roundToTwo(home.ramUsed + ramUsage));
const end = startSharing(threads, home.cpuCores);
const jobId = window.setTimeout(() => {
end();
if (pendingUIShareJobIds.includes(jobId)) {
home.updateRamUsed(roundToTwo(home.ramUsed - ramUsage));
}
rerender();
}, ShareBonusTime);
pendingUIShareJobIds.push(jobId);
}

return (
<Paper sx={{ my: 1, p: 1 }}>
<Typography>
You can share free RAM of your home computer with your faction to get a bonus multiplier for reputation gain.
Each time you share your free RAM, you get a boost for {ShareBonusTime / 1000} seconds. You can share free RAM
of other servers that you have admin rights by using ns.share() API.
<br />
Free RAM on home computer: {formatRam(home.maxRam - home.ramUsed)}.
<br />
Current bonus: {calculateCurrentShareBonus()}. Bonus with {formatRam(ram)}:{" "}
{calculateShareBonusWithAdditionalThreads(threads, home.cpuCores)}
</Typography>

<TextField
value={ram}
onChange={(event) => {
if (event.target.value === "") {
setRam(0);
return;
}
const value = Number.parseFloat(event.target.value);
if (!Number.isFinite(value) || value < 0) {
return;
}
setRam(value);
}}
InputProps={{
endAdornment: <Button onClick={onShare}>Share</Button>,
}}
/>
</Paper>
);
}
20 changes: 8 additions & 12 deletions src/NetscriptFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import {
numCycleForGrowthCorrected,
processSingleServerGrowth,
safelyCreateUniqueServer,
getCoreBonus,
getWeakenEffect,
} from "./Server/ServerHelpers";
import {
Expand Down Expand Up @@ -89,8 +88,7 @@ import { SnackbarEvents } from "./ui/React/Snackbar";
import { matchScriptPathExact } from "./utils/helpers/scriptKey";

import { Flags } from "./NetscriptFunctions/Flags";
import { calculateIntelligenceBonus } from "./PersonObjects/formulas/intelligence";
import { CalculateShareMult, StartSharing } from "./NetworkShare/Share";
import { calculateCurrentShareBonus, ShareBonusTime, startSharing } from "./NetworkShare/Share";
import { recentScripts } from "./Netscript/RecentScripts";
import { InternalAPI, setRemovedFunctions, NSProxy } from "./Netscript/APIWrapper";
import { INetscriptExtra } from "./NetscriptFunctions/Extra";
Expand Down Expand Up @@ -418,19 +416,17 @@ export const ns: InternalAPI<NSFull> = {
return getWeakenEffect(threads, cores);
},
share: (ctx) => () => {
const cores = helpers.getServer(ctx, ctx.workerScript.hostname).cpuCores;
const coreBonus = getCoreBonus(cores);
helpers.log(ctx, () => "Sharing this computer.");
const end = StartSharing(
ctx.workerScript.scriptRef.threads * calculateIntelligenceBonus(Player.skills.intelligence, 2) * coreBonus,
);
return helpers.netscriptDelay(ctx, 10000).finally(function () {
helpers.log(ctx, () => "Finished sharing this computer.");
const threads = ctx.workerScript.scriptRef.threads;
const hostname = ctx.workerScript.hostname;
helpers.log(ctx, () => `Sharing ${threads} threads on ${hostname}.`);
const end = startSharing(threads, helpers.getServer(ctx, hostname).cpuCores);
return helpers.netscriptDelay(ctx, ShareBonusTime).finally(function () {
helpers.log(ctx, () => `Finished sharing ${threads} threads on ${hostname}.`);
end();
});
},
getSharePower: () => () => {
return CalculateShareMult();
return calculateCurrentShareBonus();
},
print:
(ctx) =>
Expand Down
57 changes: 57 additions & 0 deletions src/NetworkShare/Share.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Player } from "@player";
import { calculateIntelligenceBonus } from "../PersonObjects/formulas/intelligence";
import { getCoreBonus } from "../Server/ServerHelpers";
import { clampNumber } from "../utils/helpers/clampNumber";

let sharePower = 1;

export const ShareBonusTime = 10000;

/**
* When the player shares free RAM via UI, it's a "pending job". After that job finishes, we restore the free RAM by
* decreasing server.ramUsed by calling server.updateRamUsed(). However, if the player prestiges before that, all
* servers are reset and ramUsed is reset to 0. This means that when a job finishes, we may modify ramUsed of a new
* server.
*
* To solve this problem, we use an array to save job IDs. When the player prestiges, we clear this array. When a job
* finishes, we check if that job ID is still in this array. If it is not, it means that the player performed a
* prestige, and we do not need to decrease ramUsed.
*/
export const pendingUIShareJobIds: number[] = [];

export function calculateEffectiveSharedThreads(threads: number, cpuCores: number): number {
const coreBonus = getCoreBonus(cpuCores);
return threads * calculateIntelligenceBonus(Player.skills.intelligence, 2) * coreBonus;
}

export function startSharing(threads: number, cpuCores: number): () => void {
const effectiveThreads = calculateEffectiveSharedThreads(threads, cpuCores);
sharePower += effectiveThreads;
return () => {
/**
* Due to floating point inaccuracy, sharePower may be slightly higher or lower than 1 after many times the player
* shares their RAM. We need to make sure that it's not smaller than 1.
*/
sharePower = clampNumber(sharePower - effectiveThreads, 1);
// sharePower may be slightly higher than 1. Reset sharePower if it's smaller than a threshold.
if (sharePower < 1.00001) {
sharePower = 1;
}
};
}

function calculateShareBonus(sharePower: number): number {
const bonus = 1 + Math.log(sharePower) / 25;
if (!Number.isFinite(bonus)) {
return 1;
}
return bonus;
}

export function calculateShareBonusWithAdditionalThreads(threads: number, cpuCores: number): number {
return calculateShareBonus(sharePower + calculateEffectiveSharedThreads(threads, cpuCores));
}

export function calculateCurrentShareBonus(): number {
return calculateShareBonus(sharePower);
}
12 changes: 0 additions & 12 deletions src/NetworkShare/Share.tsx

This file was deleted.

5 changes: 0 additions & 5 deletions src/NetworkShare/formulas/share.tsx

This file was deleted.

8 changes: 4 additions & 4 deletions src/PersonObjects/formulas/reputation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CONSTANTS } from "../../Constants";
import { currentNodeMults } from "../../BitNode/BitNodeMultipliers";
import { CalculateShareMult } from "../../NetworkShare/Share";
import { calculateCurrentShareBonus } from "../../NetworkShare/Share";
import { Person as IPerson } from "@nsdefs";
import { calculateIntelligenceBonus } from "./intelligence";

Expand All @@ -18,7 +18,7 @@ export function getHackingWorkRepGain(p: IPerson, favor: number): number {
p.mults.faction_rep *
calculateIntelligenceBonus(p.skills.intelligence, 1) *
mult(favor) *
CalculateShareMult()
calculateCurrentShareBonus()
);
}

Expand All @@ -29,7 +29,7 @@ export function getFactionSecurityWorkRepGain(p: IPerson, favor: number): number
p.skills.defense +
p.skills.dexterity +
p.skills.agility +
(p.skills.hacking + p.skills.intelligence) * CalculateShareMult())) /
(p.skills.hacking + p.skills.intelligence) * calculateCurrentShareBonus())) /
CONSTANTS.MaxSkillLevel /
4.5;
return t * p.mults.faction_rep * mult(favor) * calculateIntelligenceBonus(p.skills.intelligence, 1);
Expand All @@ -43,7 +43,7 @@ export function getFactionFieldWorkRepGain(p: IPerson, favor: number): number {
p.skills.dexterity +
p.skills.agility +
p.skills.charisma +
(p.skills.hacking + p.skills.intelligence) * CalculateShareMult())) /
(p.skills.hacking + p.skills.intelligence) * calculateCurrentShareBonus())) /
CONSTANTS.MaxSkillLevel /
5.5;
return t * p.mults.faction_rep * mult(favor) * calculateIntelligenceBonus(p.skills.intelligence, 1);
Expand Down
8 changes: 8 additions & 0 deletions src/Prestige.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { Go } from "./Go/Go";
import { calculateExp } from "./PersonObjects/formulas/skill";
import { currentNodeMults } from "./BitNode/BitNodeMultipliers";
import { canAccessBitNodeFeature } from "./BitNode/BitNodeUtils";
import { pendingUIShareJobIds } from "./NetworkShare/Share";

const BitNode8StartingMoney = 250e6;
function delayedDialog(message: string) {
Expand Down Expand Up @@ -75,6 +76,9 @@ export function prestigeAugmentation(): void {
AddToAllServers(homeComp);
prestigeHomeComputer(homeComp);

// Clear all pending share jobs created via UI
pendingUIShareJobIds.length = 0;

// Receive starting money and programs from installed augmentations
for (const ownedAug of Player.augmentations) {
const aug = Augmentations[ownedAug.name];
Expand Down Expand Up @@ -211,6 +215,10 @@ export function prestigeSourceFile(isFlume: boolean): void {
// Reset home computer (only the programs) and add to AllServers
AddToAllServers(homeComp);
prestigeHomeComputer(homeComp);

// Clear all pending share jobs created via UI
pendingUIShareJobIds.length = 0;

// Ram usage needs to be cleared for bitnode-level resets, due to possible change in singularity cost.
for (const script of homeComp.scripts.values()) script.ramUsage = null;

Expand Down
3 changes: 2 additions & 1 deletion src/Server/BaseServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { Settings } from "../Settings/Settings";

import type { ScriptKey } from "../utils/helpers/scriptKey";
import { objectAssert } from "../utils/helpers/typeAssertion";
import { clampNumber } from "../utils/helpers/clampNumber";

interface IConstructorParams {
adminRights?: boolean;
Expand Down Expand Up @@ -232,7 +233,7 @@ export abstract class BaseServer implements IServer {
}

updateRamUsed(ram: number): void {
this.ramUsed = ram;
this.ramUsed = clampNumber(ram, 0, this.maxRam);
}

pushProgram(program: ProgramFilePath | CompletedProgramName): void {
Expand Down
Loading