Fixed merge conflicts. Rebalanced new Hacknet Node mechanics. Adjusted Hacknet API so that it'll work with hacknet Servers. Fixed Corporation bug with Issuing new Shares

This commit is contained in:
danielyxie
2019-03-29 16:13:58 -07:00
137 changed files with 10196 additions and 3142 deletions
+3 -1
View File
@@ -12,6 +12,8 @@ import { IPlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugment
import { Company } from "../Company/Company";
import { CompanyPosition } from "../Company/CompanyPosition";
import { CityName } from "../Locations/data/CityNames";
import { HacknetNode } from "../Hacknet/HacknetNode";
import { HacknetServer } from "../Hacknet/HacknetServer";
import { LocationName } from "../Locations/data/LocationNames";
import { IPlayerOwnedSourceFile } from "../SourceFile/PlayerOwnedSourceFile";
import { MoneySourceTracker } from "../utils/MoneySourceTracker";
@@ -26,7 +28,7 @@ export interface IPlayer {
corporation: any;
currentServer: string;
factions: string[];
hacknetNodes: any[];
hacknetNodes: (HacknetNode | string)[]; // HacknetNode object or IP of Hacknet Server
hasWseAccount: boolean;
jobs: IMap<string>;
karma: number;
+1 -1
View File
@@ -105,7 +105,7 @@ export abstract class Person {
/**
* City that the person is in
*/
city: string = CityName.Sector12;
city: CityName = CityName.Sector12;
constructor() {}
+2 -2
View File
@@ -96,7 +96,7 @@ export function createResleevesPage(p: IPlayer) {
display: "inline-block",
innerText: "Sort By: "
});
UIElems.sortSelector = createElement("select") as HTMLSelectElement;
UIElems.sortSelector = createElement("select", { class: "dropdown" }) as HTMLSelectElement;
enum SortOption {
Cost = "Cost",
@@ -309,7 +309,7 @@ function createResleeveUi(resleeve: Resleeve): IResleeveUIElems {
elems.statsPanel.appendChild(elems.multipliersButton);
elems.augPanel = createElement("div", { class: "resleeve-panel", width: "50%" });
elems.augSelector = createElement("select", { class: "resleeve-aug-selector" }) as HTMLSelectElement;
elems.augSelector = createElement("select", { class: "resleeve-aug-selector dropdown" }) as HTMLSelectElement;
elems.augDescription = createElement("p");
for (let i = 0; i < resleeve.augmentations.length; ++i) {
elems.augSelector.add(createOptionElement(resleeve.augmentations[i].name));
+94 -13
View File
@@ -14,6 +14,8 @@ import { Person,
createTaskTracker } from "../Person";
import { Augmentation } from "../../Augmentation/Augmentation";
import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
@@ -112,13 +114,12 @@ export class Sleeve extends Person {
logs: string[] = [];
/**
* Clone retains memory% of exp upon prestige. If exp would be lower than previously
* kept exp, nothing happens
* Clone retains 'memory' synchronization (and maybe exp?) upon prestige/installing Augs
*/
memory: number = 0;
memory: number = 1;
/**
* Sleeve shock. Number between 1 and 100
* Sleeve shock. Number between 0 and 100
* Trauma/shock that comes with being in a sleeve. Experience earned
* is multipled by shock%. This gets applied before synchronization
*
@@ -337,6 +338,31 @@ export class Sleeve extends Person {
p.gainMoney(gain);
}
/**
* Returns the cost of upgrading this sleeve's memory by a certain amount
*/
getMemoryUpgradeCost(n: number): number {
const amt = Math.round(n);
if (amt < 0) {
return 0;
}
if (this.memory + amt > 100) {
return this.getMemoryUpgradeCost(100 - this.memory);
}
const mult = 1.02;
const baseCost = 1e12;
let currCost = 0;
let currMemory = this.memory-1;
for (let i = 0; i < n; ++i) {
currCost += (Math.pow(mult, currMemory));
++currMemory;
}
return currCost * baseCost;
}
/**
* Gets reputation gain for the current task
* Only applicable when working for company or faction
@@ -406,6 +432,20 @@ export class Sleeve extends Person {
}
}
/**
* Called on every sleeve for a Source File prestige
*/
prestige(p: IPlayer) {
this.resetTaskStatus();
this.earningsForSleeves = createTaskTracker();
this.earningsForPlayer = createTaskTracker();
this.logs = [];
this.shock = 1;
this.storedCycles = 0;
this.sync = Math.max(this.memory, 1);
this.shockRecovery(p);
}
/**
* Process loop
* Returns an object containing the amount of experience that should be
@@ -612,12 +652,7 @@ export class Sleeve extends Person {
/**
* Travel to another City. Costs money from player
*/
travel(p: IPlayer, newCity: string): boolean {
if (CityName[newCity] == null) {
console.error(`Invalid city ${newCity} passed into Sleeve.travel()`);
return false;
}
travel(p: IPlayer, newCity: CityName): boolean {
p.loseMoney(CONSTANTS.TravelCost);
this.city = newCity;
@@ -641,8 +676,8 @@ export class Sleeve extends Person {
const company: Company | null = Companies[companyName];
const companyPosition: CompanyPosition | null = CompanyPositions[p.jobs[companyName]];
if (company == null) { throw new Error(`Invalid company name specified in Sleeve.workForCompany(): ${companyName}`); }
if (companyPosition == null) { throw new Error(`Invalid CompanyPosition data in Sleeve.workForCompany(): ${companyName}`); }
if (company == null) { return false; }
if (companyPosition == null) { return false; }
this.gainRatesForTask.money = companyPosition.baseSalary *
company.salaryMultiplier *
this.work_money_mult *
@@ -684,8 +719,8 @@ export class Sleeve extends Person {
* Returns boolean indicating success
*/
workForFaction(p: IPlayer, factionName: string, workType: string): boolean {
if (factionName === "") { return false; }
if (!(Factions[factionName] instanceof Faction) || !p.factions.includes(factionName)) {
throw new Error(`Invalid Faction specified for Sleeve.workForFaction(): ${factionName}`);
return false;
}
@@ -807,6 +842,25 @@ export class Sleeve extends Person {
return true;
}
tryBuyAugmentation(p: IPlayer, aug: Augmentation): boolean {
if (!p.canAfford(aug.startingCost)) {
return false;
}
p.loseMoney(aug.startingCost);
this.installAugmentation(aug);
return true;
}
upgradeMemory(n: number): void {
if (n < 0) {
console.warn(`Sleeve.upgradeMemory() called with negative value: ${n}`);
return;
}
this.memory = Math.min(100, Math.round(this.memory + n));
}
/**
* Serialize the current object to a JSON save state.
*/
@@ -815,4 +869,31 @@ export class Sleeve extends Person {
}
}
export function findSleevePurchasableAugs(sleeve: Sleeve, p: IPlayer): Augmentation[] {
// You can only purchase Augmentations that are actually available from
// your factions. I.e. you must be in a faction that has the Augmentation
// and you must also have enough rep in that faction in order to purchase it.
const ownedAugNames: string[] = sleeve.augmentations.map((e) => {return e.name});
const availableAugs: Augmentation[] = [];
for (const facName of p.factions) {
if (facName === "Bladeburners") { continue; }
const fac: Faction | null = Factions[facName];
if (fac == null) { continue; }
for (const augName of fac.augmentations) {
if (augName === AugmentationNames.NeuroFluxGovernor) { continue; }
if (ownedAugNames.includes(augName)) { continue; }
const aug: Augmentation | null = Augmentations[augName];
if (fac.playerReputation > aug.baseRepRequirement && !availableAugs.includes(aug)) {
availableAugs.push(aug);
}
}
}
return availableAugs;
}
Reviver.constructors.Sleeve = Sleeve;
@@ -2,7 +2,7 @@
* Module for handling the UI for purchasing Sleeve Augmentations
* This UI is a popup, not a full page
*/
import { Sleeve } from "./Sleeve";
import { Sleeve, findSleevePurchasableAugs } from "./Sleeve";
import { IPlayer } from "../IPlayer";
@@ -29,23 +29,7 @@ export function createSleevePurchaseAugsPopup(sleeve: Sleeve, p: IPlayer) {
// You can only purchase Augmentations that are actually available from
// your factions. I.e. you must be in a faction that has the Augmentation
// and you must also have enough rep in that faction in order to purchase it.
const availableAugs: Augmentation[] = [];
for (const facName of p.factions) {
if (facName === "Bladeburners") { continue; }
const fac: Faction | null = Factions[facName];
if (fac == null) { continue; }
for (const augName of fac.augmentations) {
if (augName === AugmentationNames.NeuroFluxGovernor) { continue; }
if (ownedAugNames.includes(augName)) { continue; }
const aug: Augmentation | null = Augmentations[augName];
if (fac.playerReputation > aug.baseRepRequirement && !availableAugs.includes(aug)) {
availableAugs.push(aug);
}
}
}
const availableAugs = findSleevePurchasableAugs(sleeve, p);
// Create popup
const popupId = "purchase-sleeve-augs-popup";
@@ -105,15 +89,13 @@ export function createSleevePurchaseAugsPopup(sleeve: Sleeve, p: IPlayer) {
innerHTML:
[
`<h2>${aug.name}</h2><br>`,
`Cost: ${numeralWrapper.formatMoney(aug.baseCost)}<br><br>`,
`Cost: ${numeralWrapper.formatMoney(aug.startingCost)}<br><br>`,
`${aug.info}`
].join(" "),
padding: "2px",
clickListener: () => {
if (p.canAfford(aug.baseCost)) {
p.loseMoney(aug.baseCost);
sleeve.installAugmentation(aug);
dialogBoxCreate(`Installed ${aug.name} on Duplicate Sleeve!`, false)
if (sleeve.tryBuyAugmentation(p, aug)) {
dialogBoxCreate(`Installed ${aug.name} on Duplicate Sleeve!`, false);
removeElementById(popupId);
createSleevePurchaseAugsPopup(sleeve, p);
} else {
@@ -1,48 +1,18 @@
/**
* Implements the purchasing of extra Duplicate Sleeves from The Covenant
* Implements the purchasing of extra Duplicate Sleeves from The Covenant,
* as well as the purchasing of upgrades (memory)
*/
import { Sleeve } from "./Sleeve";
import { IPlayer } from "../IPlayer";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { yesNoBoxCreate,
yesNoBoxClose,
yesNoBoxGetYesButton,
yesNoBoxGetNoButton } from "../../../utils/YesNoBox";
import { CovenantPurchasesRoot } from "./ui/CovenantPurchasesRoot";
import { createPopup,
removePopup } from "../../ui/React/createPopup";
export const MaxSleevesFromCovenant: number = 5;
export const BaseCostPerSleeve: number = 10e12;
export const PopupId: string = "covenant-sleeve-purchases-popup";
export function createPurchaseSleevesFromCovenantPopup(p: IPlayer) {
if (p.sleevesFromCovenant >= MaxSleevesFromCovenant) { return; }
// First sleeve purchased costs the base amount. Then, the price of
// each successive one increases by the same amount
const baseCostPerExtraSleeve: number = 10e12;
const cost: number = (p.sleevesFromCovenant + 1) * baseCostPerExtraSleeve;
const yesBtn = yesNoBoxGetYesButton();
const noBtn = yesNoBoxGetNoButton();
yesBtn!.addEventListener("click", () => {
if (p.canAfford(cost)) {
p.loseMoney(cost);
p.sleevesFromCovenant += 1;
p.sleeves.push(new Sleeve(p));
yesNoBoxClose();
} else {
dialogBoxCreate("You cannot afford to purchase a Duplicate Sleeve", false);
}
});
noBtn!.addEventListener("click", () => {
yesNoBoxClose();
});
const txt = `Would you like to purchase an additional Duplicate Sleeve from The Covenant for ` +
`${numeralWrapper.formatMoney(cost)}?<br><br>` +
`These Duplicate Sleeves are permanent. You can purchase a total of 5 Duplicate ` +
`Sleeves from The Covenant`;
yesNoBoxCreate(txt);
export function createSleevePurchasesFromCovenantPopup(p: IPlayer) {
const removePopupFn = removePopup.bind(null, PopupId);
createPopup(PopupId, CovenantPurchasesRoot, { p: p, closeFn: removePopupFn });
}
+5 -4
View File
@@ -288,7 +288,7 @@ function createSleeveUi(sleeve: Sleeve, allSleeves: Sleeve[]): ISleeveUIElems {
}
elems.taskPanel = createElement("div", { class: "sleeve-panel", width: "40%" });
elems.taskSelector = createElement("select") as HTMLSelectElement;
elems.taskSelector = createElement("select", { class: "dropdown" }) as HTMLSelectElement;
elems.taskSelector.add(createOptionElement("------"));
elems.taskSelector.add(createOptionElement("Work for Company"));
elems.taskSelector.add(createOptionElement("Work for Faction"));
@@ -297,8 +297,8 @@ function createSleeveUi(sleeve: Sleeve, allSleeves: Sleeve[]): ISleeveUIElems {
elems.taskSelector.add(createOptionElement("Workout at Gym"));
elems.taskSelector.add(createOptionElement("Shock Recovery"));
elems.taskSelector.add(createOptionElement("Synchronize"));
elems.taskDetailsSelector = createElement("select") as HTMLSelectElement;
elems.taskDetailsSelector2 = createElement("select") as HTMLSelectElement;
elems.taskDetailsSelector = createElement("select", { class: "dropdown" }) as HTMLSelectElement;
elems.taskDetailsSelector2 = createElement("select", { class: "dropdown" }) as HTMLSelectElement;
elems.taskDescription = createElement("p");
elems.taskProgressBar = createElement("p");
elems.taskSelector.addEventListener("change", () => {
@@ -383,7 +383,8 @@ function updateSleeveUi(sleeve: Sleeve, elems: ISleeveUIElems) {
`HP: ${numeralWrapper.format(sleeve.hp, "0,0")} / ${numeralWrapper.format(sleeve.max_hp, "0,0")}`,
`City: ${sleeve.city}`,
`Shock: ${numeralWrapper.format(100 - sleeve.shock, "0,0.000")}`,
`Sync: ${numeralWrapper.format(sleeve.sync, "0,0.000")}`].join("<br>");
`Sync: ${numeralWrapper.format(sleeve.sync, "0,0.000")}`,
`Memory: ${numeralWrapper.format(sleeve.memory, "0")}`].join("<br>");
let repGainText: string = "";
if (sleeve.currentTask === SleeveTaskType.Company || sleeve.currentTask === SleeveTaskType.Faction) {
+9 -1
View File
@@ -45,5 +45,13 @@ export const SleeveFaq: string =
"are not available for sleeves.<br><br>",
"<strong><u>Do sleeves get reset when installing Augmentations or switching BitNodes?</u></strong><br>",
"Sleeves are reset when switching BitNodes, but not when installing Augmentations."
"Sleeves are reset when switching BitNodes, but not when installing Augmentations.<br><br>",
"<strong><u>What is Memory?</u></strong><br>",
"Sleeve memory dictates what a sleeve's synchronization will be",
"when its reset by switching BitNodes. For example, if a sleeve has a memory of 25,",
"then when you switch BitNodes its synchronization will initially be set to 25, rather than 1.<br><br>",
"Memory can only be increased by purchasing upgrades from The Covenant. It is a",
"persistent stat, meaning it never gets resets back to 1. The maximum possible",
"value for a sleeve's memory is 100."
].join(" ");
@@ -0,0 +1,112 @@
/**
* Root React component for the popup that lets player purchase Duplicate
* Sleeves and Sleeve-related upgrades from The Covenant
*/
import * as React from "react";
import { CovenantSleeveUpgrades } from "./CovenantSleeveUpgrades";
import { Sleeve } from "../Sleeve";
import { BaseCostPerSleeve,
MaxSleevesFromCovenant,
PopupId } from "../SleeveCovenantPurchases";
import { IPlayer } from "../../IPlayer";
import { numeralWrapper } from "../../../ui/numeralFormat";
import { PopupCloseButton } from "../../../ui/React/PopupCloseButton";
import { StdButton } from "../../../ui/React/StdButton";
import { dialogBoxCreate } from "../../../../utils/DialogBox";
interface IProps {
closeFn: () => void;
p: IPlayer;
rerender: () => void;
}
interface IState {
update: number;
}
export class CovenantPurchasesRoot extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
update: 0,
}
this.rerender = this.rerender.bind(this);
}
/**
* Get the cost to purchase a new Duplicate Sleeve
*/
purchaseCost(): number {
return (this.props.p.sleevesFromCovenant + 1) * BaseCostPerSleeve;
}
/**
* Force a rerender by just changing an arbitrary state value
*/
rerender() {
this.setState((state: IState) => ({
update: state.update + 1,
}));
}
render() {
// Purchasing a new Duplicate Sleeve
let purchaseDisabled = false;
if (!this.props.p.canAfford(this.purchaseCost())) {
purchaseDisabled = true;
}
if (this.props.p.sleevesFromCovenant >= MaxSleevesFromCovenant) {
purchaseDisabled = true;
}
const purchaseOnClick = () => {
if (this.props.p.sleevesFromCovenant >= MaxSleevesFromCovenant) { return; }
if (this.props.p.canAfford(this.purchaseCost())) {
this.props.p.loseMoney(this.purchaseCost());
this.props.p.sleevesFromCovenant += 1;
this.props.p.sleeves.push(new Sleeve(this.props.p));
this.rerender();
} else {
dialogBoxCreate(`You cannot afford to purchase a Duplicate Sleeve`, false);
}
}
// Purchasing Upgrades for Sleeves
const upgradePanels = [];
for (let i = 0; i < this.props.p.sleeves.length; ++i) {
const sleeve = this.props.p.sleeves[i];
upgradePanels.push(
<CovenantSleeveUpgrades {...this.props} sleeve={sleeve} index={i} rerender={this.rerender} key={i} />
)
}
return (
<div>
<PopupCloseButton popup={PopupId} text={"Close"} />
<p>
Would you like to purchase an additional Duplicate Sleeve from The Covenant
for {numeralWrapper.formatMoney(this.purchaseCost())}?
</p>
<br />
<p>
These Duplicate Sleeves are permanent (they persist through BitNodes). You can
purchase a total of {MaxSleevesFromCovenant} from The Covenant.
</p>
<StdButton disabled={purchaseDisabled} onClick={purchaseOnClick} text={"Purchase"} />
<br /><br />
<p>
Here, you can also purchase upgrades for your Duplicate Sleeves. These upgrades
are also permanent, meaning they persist across BitNodes.
</p>
{upgradePanels}
</div>
)
}
}
@@ -0,0 +1,97 @@
/**
* React component for a panel that lets you purchase upgrades for a Duplicate
* Sleeve's Memory (through The Covenant)
*/
import * as React from "react";
import { Sleeve } from "../Sleeve";
import { IPlayer } from "../../IPlayer";
import { numeralWrapper } from "../../../ui/numeralFormat";
import { StdButton } from "../../../ui/React/StdButton";
interface IProps {
index: number;
p: IPlayer;
rerender: () => void;
sleeve: Sleeve;
}
interface IState {
amt: number;
}
export class CovenantSleeveMemoryUpgrade extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
amt: 1,
}
this.changePurchaseAmount = this.changePurchaseAmount.bind(this);
this.purchaseMemory = this.purchaseMemory.bind(this);
}
changePurchaseAmount(e: React.ChangeEvent<HTMLInputElement>): void {
const n: number = parseInt(e.target.value);
this.setState({
amt: n,
});
}
getPurchaseCost(): number {
if (isNaN(this.state.amt)) { return Infinity; }
const maxMemory = 100 - this.props.sleeve.memory;
if (this.state.amt > maxMemory) { return Infinity; }
return this.props.sleeve.getMemoryUpgradeCost(this.state.amt);
}
purchaseMemory(): void {
const cost = this.getPurchaseCost();
if (this.props.p.canAfford(cost)) {
this.props.sleeve.upgradeMemory(this.state.amt);
this.props.p.loseMoney(cost);
this.props.rerender();
}
}
render() {
const inputId = `sleeve-${this.props.index}-memory-upgrade-input`;
// Memory cannot go above 100
const maxMemory = 100 - this.props.sleeve.memory;
// Purchase button props
const cost = this.getPurchaseCost();
const purchaseBtnDisabled = !this.props.p.canAfford(cost);
let purchaseBtnText;
if (this.state.amt > maxMemory) {
purchaseBtnText = `Memory cannot exceed 100`;
} else if (isNaN(this.state.amt)) {
purchaseBtnText = "Invalid value";
} else {
purchaseBtnText = `Purchase ${this.state.amt} memory - ${numeralWrapper.formatMoney(cost)}`;
}
return (
<div>
<h2><u>Upgrade Memory</u></h2>
<p>
Purchase a memory upgrade for your sleeve. Note that a sleeve's max memory
is 100 (current: {numeralWrapper.format(this.props.sleeve.memory, "0")})
</p>
<label htmlFor={inputId}>
Amount of memory to purchase (must be an integer):
</label>
<input id={inputId} onChange={this.changePurchaseAmount} type={"number"} value={this.state.amt} />
<br />
<StdButton disabled={purchaseBtnDisabled} onClick={this.purchaseMemory} text={purchaseBtnText} />
</div>
)
}
}
@@ -0,0 +1,28 @@
/**
* React Component for a panel that lets you purchase upgrades for a single
* Duplicate Sleeve through The Covenant
*/
import * as React from "react";
import { CovenantSleeveMemoryUpgrade } from "./CovenantSleeveMemoryUpgrade";
import { Sleeve } from "../Sleeve";
import { IPlayer } from "../../IPlayer";
interface IProps {
index: number;
p: IPlayer;
rerender: () => void;
sleeve: Sleeve;
}
export class CovenantSleeveUpgrades extends React.Component<IProps, any> {
render() {
return (
<div className={"bladeburner-action"}>
<h1>Duplicate Sleeve {this.props.index}</h1>
<CovenantSleeveMemoryUpgrade {...this.props} />
</div>
)
}
}