bitburner-src/src/Corporation/ResearchTree.ts

305 lines
7.5 KiB
TypeScript
Raw Normal View History

// Defines a "Research Tree"
// Each Industry has a unique Research Tree
// Each Node in the Research Trees only holds the name(s) of Research,
// not an actual Research object. The name can be used to obtain a reference
// to the corresponding Research object using the ResearchMap
import { Research } from "./Research";
import { ResearchMap } from "./ResearchMap";
import { IMap } from "../types";
import { numeralWrapper } from "../ui/numeralFormat";
interface IConstructorParams {
2021-09-05 01:09:30 +02:00
children?: Node[];
cost: number;
text: string;
parent?: Node | null;
}
export class Node {
2021-09-05 01:09:30 +02:00
// All child Nodes in the tree
// The Research held in this Node is a prerequisite for all Research in
// child Nodes
children: Node[] = [];
// How much Scientific Research is needed for this
// Necessary to show it on the UI
cost = 0;
// Whether or not this Research has been unlocked
researched = false;
// Parent node in the tree
// The parent node defines the prerequisite Research (there can only be one)
// Set as null for no prerequisites
parent: Node | null = null;
// Name of the Research held in this Node
text = "";
constructor(p: IConstructorParams = { cost: 0, text: "" }) {
if (ResearchMap[p.text] == null) {
throw new Error(
`Invalid Research name used when constructing ResearchTree Node: ${p.text}`,
);
}
2021-09-05 01:09:30 +02:00
this.text = p.text;
this.cost = p.cost;
2021-09-05 01:09:30 +02:00
if (p.children && p.children.length > 0) {
this.children = p.children;
}
2021-09-05 01:09:30 +02:00
if (p.parent != null) {
this.parent = p.parent;
}
2021-09-05 01:09:30 +02:00
}
addChild(n: Node): void {
this.children.push(n);
n.parent = this;
}
// Return an object that describes a TreantJS-compatible markup/config for this Node
// See: http://fperucic.github.io/treant-js/
createTreantMarkup(): any {
const childrenArray = [];
for (let i = 0; i < this.children.length; ++i) {
childrenArray.push(this.children[i].createTreantMarkup());
}
2021-09-05 01:09:30 +02:00
// Determine what css class this Node should have in the diagram
let htmlClass = "";
if (this.researched) {
htmlClass = "researched";
} else if (this.parent && this.parent.researched === false) {
htmlClass = "locked";
} else {
htmlClass = "unlocked";
}
2021-09-05 01:09:30 +02:00
const research: Research | null = ResearchMap[this.text];
const sanitizedName: string = this.text.replace(/\s/g, "");
return {
children: childrenArray,
HTMLclass: htmlClass,
innerHTML:
`<div id="${sanitizedName}-corp-research-click-listener" class="tooltip">` +
`${this.text}<br>${numeralWrapper.format(
this.cost,
"0,0",
)} Scientific Research` +
`<span class="tooltiptext">` +
`${research.desc}` +
`</span>` +
`</div>`,
text: { name: this.text },
};
}
// Recursive function for finding a Node with the specified text
findNode(text: string): Node | null {
// Is this the Node?
if (this.text === text) {
return this;
}
2021-09-05 01:09:30 +02:00
// Recursively search chilren
let res = null;
for (let i = 0; i < this.children.length; ++i) {
res = this.children[i].findNode(text);
if (res != null) {
return res;
}
}
2021-09-05 01:09:30 +02:00
return null;
}
setParent(n: Node): void {
this.parent = n;
}
}
// A ResearchTree defines all available Research in an Industry
// The root node in a Research Tree must always be the "Hi-Tech R&D Laboratory"
export class ResearchTree {
2021-09-05 01:09:30 +02:00
// Object containing names of all acquired Research by name
researched: IMap<boolean> = {};
2021-09-05 01:09:30 +02:00
// Root Node
root: Node | null = null;
2021-09-05 01:09:30 +02:00
// Return an object that contains a Tree markup for TreantJS (using the JSON approach)
// See: http://fperucic.github.io/treant-js/
createTreantMarkup(): any {
if (this.root == null) {
return {};
}
2021-09-05 01:09:30 +02:00
const treeMarkup = this.root.createTreantMarkup();
2021-09-05 01:09:30 +02:00
return {
chart: {
container: "",
},
nodeStructure: treeMarkup,
};
}
2021-09-05 01:09:30 +02:00
// Gets an array with the 'text' values of ALL Nodes in the Research Tree
getAllNodes(): string[] {
const res: string[] = [];
const queue: Node[] = [];
2021-09-05 01:09:30 +02:00
if (this.root == null) {
return res;
}
2021-09-05 01:09:30 +02:00
queue.push(this.root);
while (queue.length !== 0) {
const node: Node | undefined = queue.shift();
if (node == null) {
continue;
}
res.push(node.text);
for (let i = 0; i < node.children.length; ++i) {
queue.push(node.children[i]);
}
}
2021-09-05 01:09:30 +02:00
return res;
}
2021-09-05 01:09:30 +02:00
// Get total multipliers from this Research Tree
getAdvertisingMultiplier(): number {
return this.getMultiplierHelper("advertisingMult");
}
2021-09-05 01:09:30 +02:00
getEmployeeChaMultiplier(): number {
return this.getMultiplierHelper("employeeChaMult");
}
2021-09-05 01:09:30 +02:00
getEmployeeCreMultiplier(): number {
return this.getMultiplierHelper("employeeCreMult");
}
2021-09-05 01:09:30 +02:00
getEmployeeEffMultiplier(): number {
return this.getMultiplierHelper("employeeEffMult");
}
2021-09-05 01:09:30 +02:00
getEmployeeIntMultiplier(): number {
return this.getMultiplierHelper("employeeIntMult");
}
2021-09-05 01:09:30 +02:00
getProductionMultiplier(): number {
return this.getMultiplierHelper("productionMult");
}
2021-09-05 01:09:30 +02:00
getProductProductionMultiplier(): number {
return this.getMultiplierHelper("productProductionMult");
}
2021-09-05 01:09:30 +02:00
getSalesMultiplier(): number {
return this.getMultiplierHelper("salesMult");
}
2021-09-05 01:09:30 +02:00
getScientificResearchMultiplier(): number {
return this.getMultiplierHelper("sciResearchMult");
}
2021-09-05 01:09:30 +02:00
getStorageMultiplier(): number {
return this.getMultiplierHelper("storageMult");
}
2021-09-05 01:09:30 +02:00
// Helper function for all the multiplier getter fns
getMultiplierHelper(propName: string): number {
let res = 1;
if (this.root == null) {
return res;
}
2021-09-05 01:09:30 +02:00
const queue: Node[] = [];
queue.push(this.root);
while (queue.length !== 0) {
const node: Node | undefined = queue.shift();
// If the Node has not been researched, there's no need to
// process it or its children
if (node == null || !node.researched) {
continue;
}
const research: Research | null = ResearchMap[node.text];
// Safety checks
if (research == null) {
console.warn(`Invalid Research name in node: ${node.text}`);
continue;
}
const mult: any = (research as any)[propName];
2021-09-05 01:09:30 +02:00
if (mult == null) {
console.warn(
`Invalid propName specified in ResearchTree.getMultiplierHelper: ${propName}`,
);
continue;
}
res *= mult;
for (let i = 0; i < node.children.length; ++i) {
queue.push(node.children[i]);
}
}
2021-09-05 01:09:30 +02:00
return res;
}
2021-09-05 01:09:30 +02:00
// Search for a Node with the given name ('text' property on the Node)
// Returns 'null' if it cannot be found
findNode(name: string): Node | null {
if (this.root == null) {
return null;
}
return this.root.findNode(name);
}
2021-09-05 01:09:30 +02:00
// Marks a Node as researched
research(name: string): void {
if (this.root == null) {
return;
}
2021-09-05 01:09:30 +02:00
const queue: Node[] = [];
queue.push(this.root);
while (queue.length !== 0) {
const node: Node | undefined = queue.shift();
if (node == null) {
continue;
}
if (node.text === name) {
node.researched = true;
this.researched[name] = true;
return;
}
for (let i = 0; i < node.children.length; ++i) {
queue.push(node.children[i]);
}
}
2021-09-05 01:09:30 +02:00
console.warn(
`ResearchTree.research() did not find the specified Research node for: ${name}`,
);
}
// Set the tree's Root Node
setRoot(root: Node): void {
this.root = root;
}
}