mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-17 23:08:36 +02:00
510 lines
21 KiB
JavaScript
510 lines
21 KiB
JavaScript
// React Component for displaying an Industry's warehouse information
|
|
// (right-side panel in the Industry UI)
|
|
import React from "react";
|
|
import { BaseReactComponent } from "./BaseReactComponent";
|
|
|
|
import { Material } from "../Material";
|
|
import { Product } from "../Product";
|
|
|
|
import { Warehouse,
|
|
WarehouseInitialCost,
|
|
WarehouseUpgradeBaseCost,
|
|
ProductProductionCostRatio } from "../Corporation";
|
|
|
|
import { numeralWrapper } from "../../ui/numeralFormat";
|
|
|
|
import { isString } from "../../../utils/helpers/isString";
|
|
|
|
// Creates the UI for a single Product type
|
|
function ProductComponent(props) {
|
|
const corp = props.corp;
|
|
const division = props.division;
|
|
const warehouse = props.warehouse;
|
|
const city = props.city;
|
|
const product = props.product;
|
|
const eventHandler = props.eventHandler;
|
|
|
|
// Numeraljs formatters
|
|
const nf = "0.000";
|
|
const nfB = "0.000a"; // For numbers that might be big
|
|
|
|
const hasUpgradeDashboard = division.hasResearch("uPgrade: Dashboard");
|
|
|
|
// Total product gain = production - sale
|
|
const totalGain = product.data[city][1] - product.data[city][2];
|
|
|
|
// Sell button
|
|
let sellButtonText;
|
|
if (product.sllman[city][0]) {
|
|
if (isString(product.sllman[city][1])) {
|
|
sellButtonText = `Sell (${numeralWrapper.format(product.data[city][2], nfB)}/${product.sllman[city][1]})`;
|
|
} else {
|
|
sellButtonText = `Sell (${numeralWrapper.format(product.data[city][2], nfB)}/${numeralWrapper.format(product.sllman[city][1], nfB)})`;
|
|
}
|
|
} else {
|
|
sellButtonText = "Sell (0.000/0.000)";
|
|
}
|
|
|
|
if (product.sCost) {
|
|
if (isString(product.sCost)) {
|
|
sellButtonText += (" @ " + product.sCost);
|
|
} else {
|
|
sellButtonText += (" @ " + numeralWrapper.format(product.sCost, "$0.000a"));
|
|
}
|
|
}
|
|
const sellButtonOnClick = eventHandler.createSellProductPopup.bind(eventHandler, product, city);
|
|
|
|
// Limit Production button
|
|
const limitProductionButtonText = "Limit Production";
|
|
if (product.prdman[city][0]) {
|
|
limitProductionButtonText += " (" + numeralWrapper.format(product.prdman[city][1], nf) + ")";
|
|
}
|
|
const limitProductionButtonOnClick = eventHandler.createLimitProductProdutionPopup.bind(eventHandler, product, city);
|
|
|
|
// Discontinue Button
|
|
const discontinueButtonOnClick = eventHandler.createDiscontinueProductPopup.bind(eventHandler, product);
|
|
|
|
// Unfinished Product
|
|
if (!product.fin) {
|
|
if (hasUpgradeDashboard) {
|
|
return (
|
|
<div className={"cmpy-mgmt-warehouse-product-div"} key={product.name}>
|
|
<p>Designing {product.name}...</p><br />
|
|
<p>{numeralWrapper.format(product.prog, "0.00")}% complete</p>
|
|
<br />
|
|
|
|
<div>
|
|
<button className={"std-button"} onClick={sellButtonOnClick}>
|
|
{sellButtonText}
|
|
</button><br />
|
|
<button className={"std-button"} onClick={limitProductionButtonOnClick}>
|
|
{limitProductionButtonText}
|
|
</button>
|
|
<button className={"std-button"} onClick={discontinueButtonOnClick}>
|
|
Discontinue
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
} else {
|
|
return (
|
|
<div className={"cmpy-mgmt-warehouse-product-div"} key={product.name}>
|
|
<p>Designing {product.name}...</p><br />
|
|
<p>{numeralWrapper.format(product.prog, "0.00")}% complete</p>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className={"cmpy-mgmt-warehouse-product-div"} key={props.key}>
|
|
<p className={"tooltip"}>
|
|
{product.name}: {numeralWrapper.format(product.data[city][0], nfB)} ({numeralWrapper.format(totalGain, nfB)}/s)
|
|
<span className={"tooltiptext"}>
|
|
Prod: {numeralWrapper.format(product.data[city][1], nfB)}/s
|
|
<br />
|
|
Sell: {numeralWrapper.format(product.data[city][2], nfB)} /s
|
|
</span>
|
|
</p><br />
|
|
<p className={"tooltip"}>
|
|
Rating: {numeralWrapper.format(product.rat, nf)}
|
|
<span className={"tooltiptext"}>
|
|
Quality: {numeralWrapper.format(product.qlt, nf)} <br />
|
|
Performance: {numeralWrapper.format(product.per, nf)} <br />
|
|
Durability: {numeralWrapper.format(product.dur, nf)} <br />
|
|
Reliability: {numeralWrapper.format(product.rel, nf)} <br />
|
|
Aesthetics: {numeralWrapper.format(product.aes, nf)} <br />
|
|
Features: {numeralWrapper.format(product.fea, nf)}
|
|
{
|
|
corp.unlockUpgrades[2] === 1 && <br />
|
|
}
|
|
{
|
|
corp.unlockUpgrades[2] === 1 &&
|
|
"Demand: " + numeralWrapper.format(product.dmd, nf)
|
|
}
|
|
{
|
|
corp.unlockUpgrades[3] === 1 && <br />
|
|
}
|
|
{
|
|
corp.unlockUpgrades[3] === 1 &&
|
|
"Competition: " + numeralWrapper.format(product.cmp, nf)
|
|
}
|
|
|
|
</span>
|
|
</p><br />
|
|
<p className={"tooltip"}>
|
|
Est. Production Cost: {numeralWrapper.formatMoney(product.pCost / ProductProductionCostRatio)}
|
|
<span className={"tooltiptext"}>
|
|
An estimate of the material cost it takes to create this Product.
|
|
</span>
|
|
</p><br />
|
|
<p className={"tooltip"}>
|
|
Est. Market Price: {numeralWrapper.formatMoney(product.pCost + product.rat / product.mku)}
|
|
<span className={"tooltiptext"}>
|
|
An estimate of how much consumers are willing to pay for this product.
|
|
Setting the sale price above this may result in less sales. Setting the sale price below this may result
|
|
in more sales.
|
|
</span>
|
|
</p>
|
|
|
|
<div>
|
|
<button className={"std-button"} onClick={sellButtonOnClick}>
|
|
{sellButtonText}
|
|
</button><br />
|
|
<button className={"std-button"} onClick={limitProductionButtonOnClick}>
|
|
{limitProductionButtonText}
|
|
</button>
|
|
<button className={"std-button"} onClick={discontinueButtonOnClick}>
|
|
Discontinue
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// Creates the UI for a single Material type
|
|
function MaterialComponent(props) {
|
|
const corp = props.corp;
|
|
const division = props.division;
|
|
const warehouse = props.warehouse;
|
|
const mat = props.mat;
|
|
const eventHandler = props.eventHandler;
|
|
const markupLimit = mat.getMarkupLimit();
|
|
|
|
// Numeraljs formatter
|
|
const nf = "0.000";
|
|
const nfB = "0.000a"; // For numbers that might be biger
|
|
|
|
// Total gain or loss of this material (per second)
|
|
const totalGain = mat.buy + mat.prd + mat.imp - mat.sll - mat.totalExp;
|
|
|
|
// Competition and demand info, if they're unlocked
|
|
let cmpAndDmdText = "";
|
|
if (corp.unlockUpgrades[2] === 1) {
|
|
cmpAndDmdText += "<br>Demand: " + numeralWrapper.format(mat.dmd, nf);
|
|
}
|
|
if (corp.unlockUpgrades[3] === 1) {
|
|
cmpAndDmdText += "<br>Competition: " + numeralWrapper.format(mat.cmp, nf);
|
|
}
|
|
|
|
// Flag that determines whether this industry is "new" and the current material should be
|
|
// marked with flashing-red lights
|
|
const tutorial = division.newInd && Object.keys(division.reqMats).includes(mat.name) &&
|
|
mat.buy === 0 && mat.imp === 0;
|
|
|
|
// Purchase material button
|
|
const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nf)})`;
|
|
const purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button";
|
|
const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division);
|
|
|
|
// Export material button
|
|
const exportButtonOnClick = eventHandler.createExportMaterialPopup.bind(eventHandler, mat);
|
|
|
|
// Sell material button
|
|
let sellButtonText;
|
|
if (mat.sllman[0]) {
|
|
if (isString(mat.sllman[1])) {
|
|
sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nf)}/${mat.sllman[1]})`
|
|
} else {
|
|
sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nf)}/${numeralWrapper.format(mat.sllman[1], nf)})`;
|
|
}
|
|
|
|
if (mat.sCost) {
|
|
if (mat.marketTa1) {
|
|
sellButtonText += " @ " + numeralWrapper.formatMoney(mat.bCost + markupLimit);
|
|
} else if (isString(mat.sCost)) {
|
|
var sCost = mat.sCost.replace(/MP/g, mat.bCost);
|
|
sellButtonText += " @ " + numeralWrapper.formatMoney(eval(sCost));
|
|
} else {
|
|
sellButtonText += " @ " + numeralWrapper.formatMoney(mat.sCost);
|
|
}
|
|
}
|
|
} else {
|
|
sellButtonText = "Sell (0.000/0.000)";
|
|
}
|
|
const sellButtonOnClick = eventHandler.createSellMaterialPopup.bind(eventHandler, mat);
|
|
|
|
// Market TA button
|
|
const marketTaButtonOnClick = eventHandler.createMarketTaPopup.bind(eventHandler, mat, division);
|
|
|
|
return (
|
|
<div className={"cmpy-mgmt-warehouse-material-div"} key={props.key}>
|
|
<div style={{display: "inline-block"}}>
|
|
<p className={"tooltip"}>
|
|
{mat.name}: {numeralWrapper.format(mat.qty, nfB)} ({numeralWrapper.format(totalGain, nfB)}/s)
|
|
<span className={"tooltiptext"}>
|
|
Buy: {numeralWrapper.format(mat.buy, nfB)} <br />
|
|
Prod: {numeralWrapper.format(mat.prd, nfB)} <br />
|
|
Sell: {numeralWrapper.format(mat.sll, nfB)} <br />
|
|
Export: {numeralWrapper.format(mat.totalExp, nfB)} <br />
|
|
Import: {numeralWrapper.format(mat.imp, nfB)}
|
|
{
|
|
corp.unlockUpgrades[2] === 1 && <br />
|
|
}
|
|
{
|
|
corp.unlockUpgrades[2] === 1 &&
|
|
"Demand: " + numeralWrapper.format(mat.dmd, nf)
|
|
}
|
|
{
|
|
corp.unlockUpgrades[3] === 1 && <br />
|
|
}
|
|
{
|
|
corp.unlockUpgrades[3] === 1 &&
|
|
"Competition: " + numeralWrapper.format(mat.cmp, nf)
|
|
}
|
|
</span>
|
|
</p><br />
|
|
<p className={"tooltip"}>
|
|
MP: {numeralWrapper.formatMoney(mat.bCost)}
|
|
<span className={"tooltiptext"}>
|
|
Market Price: The price you would pay if you were to buy this material on the market
|
|
</span>
|
|
</p> <br />
|
|
<p className={"tooltip"}>
|
|
Quality: {numeralWrapper.format(mat.qlt, "0.00a")}
|
|
<span className={"tooltiptext"}>
|
|
The quality of your material. Higher quality will lead to more sales
|
|
</span>
|
|
</p>
|
|
</div>
|
|
|
|
<div style={{display: "inline-block"}}>
|
|
<button className={purchaseButtonClass} onClick={purchaseButtonOnClick}>
|
|
{purchaseButtonText}
|
|
{
|
|
tutorial &&
|
|
<span className={"tooltiptext"}>
|
|
Purchase your required materials to get production started!
|
|
</span>
|
|
}
|
|
</button>
|
|
|
|
{
|
|
corp.unlockUpgrades[0] === 1 &&
|
|
<button className={"std-button"} onClick={exportButtonOnClick}>
|
|
Export
|
|
</button>
|
|
}
|
|
<br />
|
|
|
|
<button className={"std-button"} onClick={sellButtonOnClick}>
|
|
{sellButtonText}
|
|
</button>
|
|
|
|
{
|
|
division.hasResearch("Market-TA.I") &&
|
|
<button className={"std-button"} onClick={marketTaButtonOnClick}>
|
|
Market-TA
|
|
</button>
|
|
}
|
|
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export class IndustryWarehouse extends BaseReactComponent {
|
|
renderWarehouseUI() {
|
|
const corp = this.corp();
|
|
const division = this.routing().currentDivision; // Validated in render()
|
|
const warehouse = division.warehouses[this.props.currentCity]; // Validated in render()
|
|
|
|
// General Storage information at the top
|
|
const sizeUsageStyle = {
|
|
color: warehouse.sizeUsed >= warehouse.size ? "red" : "white",
|
|
margin: "5px",
|
|
}
|
|
|
|
// Upgrade Warehouse size button
|
|
const sizeUpgradeCost = WarehouseUpgradeBaseCost * Math.pow(1.07, warehouse.level + 1);
|
|
const canAffordUpgrade = (corp.funds.gt(sizeUpgradeCost));
|
|
const upgradeWarehouseClass = canAffordUpgrade ? "std-button" : "a-link-button-inactive";
|
|
const upgradeWarehouseOnClick = () => {
|
|
++warehouse.level;
|
|
warehouse.updateSize(corp, division);
|
|
corp.funds = corp.funds.minus(sizeUpgradeCost);
|
|
corp.rerender();
|
|
return;
|
|
}
|
|
|
|
// Industry material Requirements
|
|
let generalReqsText = "This Industry uses [" + Object.keys(division.reqMats).join(", ") +
|
|
"] in order to ";
|
|
if (division.prodMats.length > 0) {
|
|
generalReqsText += "produce [" + division.prodMats.join(", ") + "] ";
|
|
if (division.makesProducts) {
|
|
generalReqsText += " and " + division.getProductDescriptionText();
|
|
}
|
|
} else if (division.makesProducts) {
|
|
generalReqsText += (division.getProductDescriptionText() + ".");
|
|
}
|
|
|
|
const ratioLines = [];
|
|
for (const matName in division.reqMats) {
|
|
if (division.reqMats.hasOwnProperty(matName)) {
|
|
const text = [" *", division.reqMats[matName], matName].join(" ");
|
|
ratioLines.push((
|
|
<div key={matName}>
|
|
<p>{text}</p>
|
|
</div>
|
|
));
|
|
}
|
|
}
|
|
|
|
let createdItemsText = "in order to create ";
|
|
if (division.prodMats.length > 0) {
|
|
createdItemsText += "one of each produced Material (" + division.prodMats.join(", ") + ") ";
|
|
if (division.makesProducts) {
|
|
createdItemsText += "or to create one of its Products";
|
|
}
|
|
} else if (division.makesProducts) {
|
|
createdItemsText += "one of its Products";
|
|
}
|
|
|
|
// Current State:
|
|
let stateText;
|
|
switch(division.state) {
|
|
case "START":
|
|
stateText = "Current state: Preparing...";
|
|
break;
|
|
case "PURCHASE":
|
|
stateText = "Current state: Purchasing materials...";
|
|
break;
|
|
case "PRODUCTION":
|
|
stateText = "Current state: Producing materials and/or products...";
|
|
break;
|
|
case "SALE":
|
|
stateText = "Current state: Selling materials and/or products...";
|
|
break;
|
|
case "EXPORT":
|
|
stateText = "Current state: Exporting materials and/or products...";
|
|
break;
|
|
default:
|
|
console.error(`Invalid state: ${division.state}`);
|
|
break;
|
|
}
|
|
|
|
// Smart Supply Checkbox
|
|
const smartSupplyCheckboxId = "cmpy-mgmt-smart-supply-checkbox";
|
|
const smartSupplyOnChange = (e) => {
|
|
warehouse.smartSupplyEnabled = e.target.checked;
|
|
corp.rerender();
|
|
}
|
|
|
|
// Materials that affect Production multiplier
|
|
const prodMultiplierMats = ["Hardware", "Robots", "AICores", "RealEstate"];
|
|
|
|
// Returns a boolean indicating whether the given material is relevant for the
|
|
// current industry.
|
|
function isRelevantMaterial(matName) {
|
|
if (Object.keys(division.reqMats).includes(matName)) { return true; }
|
|
if (division.prodMats.includes(matName)) { return true; }
|
|
if (prodMultiplierMats.includes(matName)) { return true; }
|
|
|
|
return false;
|
|
}
|
|
|
|
// Create React components for materials
|
|
const mats = [];
|
|
for (const matName in warehouse.materials) {
|
|
if (warehouse.materials[matName] instanceof Material) {
|
|
// Only create UI for materials that are relevant for the industry
|
|
if (isRelevantMaterial(matName)) {
|
|
mats.push(MaterialComponent({
|
|
corp: corp,
|
|
division: division,
|
|
eventHandler: this.eventHandler(),
|
|
key: matName,
|
|
mat: warehouse.materials[matName],
|
|
warehouse: warehouse,
|
|
}));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create React components for products
|
|
const products = [];
|
|
if (division.makesProducts && Object.keys(division.products).length > 0) {
|
|
for (const productName in division.products) {
|
|
if (division.products[productName] instanceof Product) {
|
|
products.push(ProductComponent({
|
|
city: this.props.currentCity,
|
|
corp: corp,
|
|
division: division,
|
|
eventHandler: this.eventHandler(),
|
|
key: productName,
|
|
product: division.products[productName],
|
|
warehouse: warehouse,
|
|
}));
|
|
}
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className={"cmpy-mgmt-warehouse-panel"}>
|
|
<p className={"tooltip"} style={sizeUsageStyle}>
|
|
Storage: {numeralWrapper.format(warehouse.sizeUsed, "0.000")} / {numeralWrapper.format(warehouse.size, "0.000")}
|
|
<span className={"tooltiptext"} dangerouslySetInnerHTML={{__html: warehouse.breakdown}}></span>
|
|
</p>
|
|
|
|
<button className={upgradeWarehouseClass} onClick={upgradeWarehouseOnClick}>
|
|
Upgrade Warehouse Size - {numeralWrapper.formatMoney(sizeUpgradeCost)}
|
|
</button>
|
|
|
|
<p>{generalReqsText}. The exact requirements for production are:</p><br />
|
|
{ratioLines}<br />
|
|
<p>{createdItemsText}</p>
|
|
<p>
|
|
To get started with production, purchase your required materials
|
|
or import them from another of your company's divisions.
|
|
</p><br />
|
|
|
|
<p>{stateText}</p>
|
|
|
|
{
|
|
corp.unlockUpgrades[1] &&
|
|
<div>
|
|
<label style={{color: "white"}} htmlFor={smartSupplyCheckboxId}>
|
|
Enable Smart Supply
|
|
</label>
|
|
<input type={"checkbox"}
|
|
id={smartSupplyCheckboxId}
|
|
onChange={smartSupplyOnChange}
|
|
style={{margin: "3px"}}
|
|
checked={warehouse.smartSupplyEnabled}
|
|
/>
|
|
</div>
|
|
}
|
|
|
|
{mats}
|
|
|
|
{products}
|
|
|
|
</div>
|
|
)
|
|
}
|
|
|
|
render() {
|
|
const corp = this.corp();
|
|
const division = this.routing().currentDivision;
|
|
if (division == null) {
|
|
throw new Error(`Routing does not hold reference to the current Industry`);
|
|
}
|
|
const warehouse = division.warehouses[this.props.currentCity];
|
|
|
|
const newWarehouseOnClick = this.eventHandler().purchaseWarehouse.bind(this.eventHandler(), division, this.props.currentCity);
|
|
|
|
if (warehouse instanceof Warehouse) {
|
|
return this.renderWarehouseUI();
|
|
} else {
|
|
return (
|
|
<div className={"cmpy-mgmt-warehouse-panel"}>
|
|
<button className={"std-button"} onClick={newWarehouseOnClick}>
|
|
Purchase Warehouse ({numeralWrapper.formatMoney(WarehouseInitialCost)})
|
|
</button>
|
|
</div>
|
|
)
|
|
}
|
|
}
|
|
}
|