mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-17 13:13:49 +01:00
CORP: Robotics industry NaN fix + better exports validation (#578)
This commit is contained in:
parent
4c4c4a0335
commit
cbff2a420b
@ -4,7 +4,7 @@
|
||||
|
||||
## BasicHGWOptions.additionalMsec property
|
||||
|
||||
Number of additional milliseconds that will be spent waiting between the start of the function and when it completes. Experimental in 2.2.2, may be removed in 2.3.
|
||||
Number of additional milliseconds that will be spent waiting between the start of the function and when it completes.
|
||||
|
||||
**Signature:**
|
||||
|
||||
|
@ -16,7 +16,7 @@ interface BasicHGWOptions
|
||||
|
||||
| Property | Modifiers | Type | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [additionalMsec?](./bitburner.basichgwoptions.additionalmsec.md) | | number | _(Optional)_ Number of additional milliseconds that will be spent waiting between the start of the function and when it completes. Experimental in 2.2.2, may be removed in 2.3. |
|
||||
| [additionalMsec?](./bitburner.basichgwoptions.additionalmsec.md) | | number | _(Optional)_ Number of additional milliseconds that will be spent waiting between the start of the function and when it completes. |
|
||||
| [stock?](./bitburner.basichgwoptions.stock.md) | | boolean | _(Optional)_ Set to true this action will affect the stock market. |
|
||||
| [threads?](./bitburner.basichgwoptions.threads.md) | | number | _(Optional)_ Number of threads to use for this function. Must be less than or equal to the number of threads the script is running with. |
|
||||
|
||||
|
@ -15,7 +15,6 @@ cancelExportMaterial(
|
||||
targetDivision: string,
|
||||
targetCity: CityName | `${CityName}`,
|
||||
materialName: string,
|
||||
amt: number,
|
||||
): void;
|
||||
```
|
||||
|
||||
@ -28,7 +27,6 @@ cancelExportMaterial(
|
||||
| targetDivision | string | Target division |
|
||||
| targetCity | [CityName](./bitburner.cityname.md) \| \`${[CityName](./bitburner.cityname.md)<!-- -->}\` | Target city |
|
||||
| materialName | string | Name of the material |
|
||||
| amt | number | Amount of material to export. |
|
||||
|
||||
**Returns:**
|
||||
|
||||
|
@ -22,7 +22,7 @@ Requires the Warehouse API upgrade from your corporation.
|
||||
| --- | --- |
|
||||
| [bulkPurchase(divisionName, city, materialName, amt)](./bitburner.warehouseapi.bulkpurchase.md) | Set material to bulk buy |
|
||||
| [buyMaterial(divisionName, city, materialName, amt)](./bitburner.warehouseapi.buymaterial.md) | Set material buy data |
|
||||
| [cancelExportMaterial(sourceDivision, sourceCity, targetDivision, targetCity, materialName, amt)](./bitburner.warehouseapi.cancelexportmaterial.md) | Cancel material export |
|
||||
| [cancelExportMaterial(sourceDivision, sourceCity, targetDivision, targetCity, materialName)](./bitburner.warehouseapi.cancelexportmaterial.md) | Cancel material export |
|
||||
| [discontinueProduct(divisionName, productName)](./bitburner.warehouseapi.discontinueproduct.md) | Discontinue a product. |
|
||||
| [exportMaterial(sourceDivision, sourceCity, targetDivision, targetCity, materialName, amt)](./bitburner.warehouseapi.exportmaterial.md) | Set material export data |
|
||||
| [getMaterial(divisionName, city, materialName)](./bitburner.warehouseapi.getmaterial.md) | Get material data |
|
||||
|
@ -48,6 +48,17 @@ export function NewDivision(corporation: Corporation, industry: IndustryType, na
|
||||
export function removeDivision(corporation: Corporation, name: string) {
|
||||
if (!corporation.divisions.has(name)) throw new Error("There is no division called " + name);
|
||||
corporation.divisions.delete(name);
|
||||
// We also need to remove any exports that were pointing to the old division
|
||||
for (const otherDivision of corporation.divisions.values()) {
|
||||
for (const warehouse of getRecordValues(otherDivision.warehouses)) {
|
||||
for (const material of getRecordValues(warehouse.materials)) {
|
||||
// Work backwards through exports array so splicing doesn't affect the loop
|
||||
for (let i = material.exports.length - 1; i >= 0; i--) {
|
||||
if (material.exports[i].division === name) material.exports.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function purchaseOffice(corporation: Corporation, division: Division, city: CityName): void {
|
||||
@ -443,52 +454,62 @@ export function Research(researchingDivision: Division, researchName: CorpResear
|
||||
}
|
||||
}
|
||||
|
||||
/** Set a new export for a material. Throw on any invalid input. */
|
||||
export function ExportMaterial(
|
||||
divisionName: string,
|
||||
cityName: CityName,
|
||||
targetDivision: Division,
|
||||
targetCity: CityName,
|
||||
material: Material,
|
||||
amt: string,
|
||||
division?: Division,
|
||||
amount: string,
|
||||
): void {
|
||||
// Sanitize amt
|
||||
let sanitizedAmt = amt.replace(/\s+/g, "").toUpperCase();
|
||||
if (!isRelevantMaterial(material.name, targetDivision)) {
|
||||
throw new Error(`You cannot export material: ${material.name} to division: ${targetDivision.name}!`);
|
||||
}
|
||||
if (!targetDivision.warehouses[targetCity]) {
|
||||
throw new Error(`Cannot export to ${targetCity} in division ${targetDivision.name} because there is no warehouse.`);
|
||||
}
|
||||
if (material === targetDivision.warehouses[targetCity]?.materials[material.name]) {
|
||||
throw new Error(`Source and target division/city cannot be the same.`);
|
||||
}
|
||||
for (const existingExport of material.exports) {
|
||||
if (existingExport.division === targetDivision.name && existingExport.city === targetCity) {
|
||||
throw new Error(`Tried to initialize an export to a duplicate warehouse.
|
||||
Target warehouse (division / city): ${existingExport.division} / ${existingExport.city}
|
||||
Existing export amount: ${existingExport.amount}
|
||||
Attempted export amount: ${amount}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Perform sanitization and tests
|
||||
let sanitizedAmt = amount.replace(/\s+/g, "").toUpperCase();
|
||||
sanitizedAmt = sanitizedAmt.replace(/[^-()\d/*+.MAXEPRODINV]/g, "");
|
||||
let temp = sanitizedAmt.replace(/MAX/g, "1");
|
||||
temp = temp.replace(/IPROD/g, "1");
|
||||
temp = temp.replace(/EPROD/g, "1");
|
||||
temp = temp.replace(/IINV/g, "1");
|
||||
temp = temp.replace(/EINV/g, "1");
|
||||
try {
|
||||
temp = eval(temp);
|
||||
} catch (e) {
|
||||
throw new Error("Invalid expression entered for export amount: " + e);
|
||||
for (const testReplacement of ["(1.23)", "(-1.23)"]) {
|
||||
const replaced = sanitizedAmt.replace(/(IPROD|EPROD|IINV|EINV)/g, testReplacement);
|
||||
let evaluated, error;
|
||||
try {
|
||||
evaluated = eval(replaced);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
if (!error && isNaN(evaluated)) error = "evaluated value is NaN";
|
||||
if (error) {
|
||||
throw new Error(`Error while trying to set the exported amount of ${material.name}.
|
||||
Error occurred while testing keyword replacement with ${testReplacement}.
|
||||
Your input: ${amount}
|
||||
Sanitized input: ${sanitizedAmt}
|
||||
Input after replacement: ${replaced}
|
||||
Evaluated value: ${evaluated}
|
||||
Error encountered: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
const n = parseFloat(temp);
|
||||
|
||||
if (n == null || isNaN(n)) {
|
||||
throw new Error("Invalid amount entered for export");
|
||||
}
|
||||
|
||||
if (!division || !isRelevantMaterial(material.name, division)) {
|
||||
throw new Error(`You cannot export material: ${material.name} to division: ${divisionName}!`);
|
||||
}
|
||||
|
||||
const exportObj = { division: divisionName, city: cityName, amount: sanitizedAmt };
|
||||
const exportObj = { division: targetDivision.name, city: targetCity, amount: sanitizedAmt };
|
||||
material.exports.push(exportObj);
|
||||
}
|
||||
|
||||
export function CancelExportMaterial(divisionName: string, cityName: string, material: Material, amt: string): void {
|
||||
for (let i = 0; i < material.exports.length; ++i) {
|
||||
if (
|
||||
material.exports[i].division !== divisionName ||
|
||||
material.exports[i].city !== cityName ||
|
||||
material.exports[i].amount !== amt
|
||||
)
|
||||
continue;
|
||||
material.exports.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
export function CancelExportMaterial(divisionName: string, cityName: CityName, material: Material): void {
|
||||
const index = material.exports.findIndex((exp) => exp.division === divisionName && exp.city === cityName);
|
||||
if (index === -1) return;
|
||||
material.exports.splice(index, 1);
|
||||
}
|
||||
|
||||
export function LimitProductProduction(product: Product, cityName: CityName, quantity: number): void {
|
||||
|
@ -434,7 +434,12 @@ export class Division {
|
||||
const divider = requiredMatsEntries.length;
|
||||
for (const [reqMatName, reqMat] of requiredMatsEntries) {
|
||||
const reqMatQtyNeeded = reqMat * prod * producableFrac;
|
||||
warehouse.materials[reqMatName].stored -= reqMatQtyNeeded;
|
||||
// producableFrac already takes into account that we have enough stored
|
||||
// Math.max is used here to avoid stored becoming negative (which can lead to NaNs)
|
||||
warehouse.materials[reqMatName].stored = Math.max(
|
||||
0,
|
||||
warehouse.materials[reqMatName].stored - reqMatQtyNeeded,
|
||||
);
|
||||
warehouse.materials[reqMatName].productionAmount = 0;
|
||||
warehouse.materials[reqMatName].productionAmount -=
|
||||
reqMatQtyNeeded / (corpConstants.secondsPerMarketCycle * marketCycles);
|
||||
@ -446,7 +451,7 @@ export class Division {
|
||||
let tempQlt =
|
||||
office.employeeProductionByJob[CorpEmployeeJob.Engineer] / 90 +
|
||||
Math.pow(this.researchPoints, this.researchFactor) +
|
||||
Math.pow(warehouse.materials["AI Cores"].stored, this.aiCoreFactor) / 10e3;
|
||||
Math.pow(Math.max(0, warehouse.materials["AI Cores"].stored), this.aiCoreFactor) / 10e3;
|
||||
const logQlt = Math.max(Math.pow(tempQlt, 0.5), 1);
|
||||
tempQlt = Math.min(tempQlt, avgQlt * logQlt);
|
||||
warehouse.materials[this.producedMaterials[j]].quality = Math.max(
|
||||
|
@ -54,7 +54,7 @@ export function ExportModal(props: IProps): React.ReactElement {
|
||||
|
||||
function exportMaterial(): void {
|
||||
try {
|
||||
ExportMaterial(targetDivision.name, targetCity, props.mat, exportAmount, targetDivision);
|
||||
ExportMaterial(targetDivision, targetCity, props.mat, exportAmount);
|
||||
} catch (err) {
|
||||
dialogBoxCreate(err + "");
|
||||
}
|
||||
|
@ -465,29 +465,22 @@ export function NetscriptCorporation(): InternalAPI<NSCorporation> {
|
||||
checkAccess(ctx, CorpUnlockName.WarehouseAPI);
|
||||
const sourceDivision = helpers.string(ctx, "sourceDivision", _sourceDivision);
|
||||
assertMember(ctx, CityName, "City", "sourceCity", sourceCity);
|
||||
const targetDivision = helpers.string(ctx, "targetDivision", _targetDivision);
|
||||
const targetDivision = getDivision(helpers.string(ctx, "targetDivision", _targetDivision));
|
||||
assertMember(ctx, CityName, "City", "targetCity", targetCity);
|
||||
assertMember(ctx, corpConstants.materialNames, "Material Name", "materialName", materialName);
|
||||
const amt = helpers.string(ctx, "amt", _amt);
|
||||
ExportMaterial(
|
||||
targetDivision,
|
||||
targetCity,
|
||||
getMaterial(sourceDivision, sourceCity, materialName),
|
||||
amt + "",
|
||||
getDivision(targetDivision),
|
||||
);
|
||||
ExportMaterial(targetDivision, targetCity, getMaterial(sourceDivision, sourceCity, materialName), amt);
|
||||
},
|
||||
cancelExportMaterial:
|
||||
(ctx) =>
|
||||
(_sourceDivision, sourceCity, _targetDivision, targetCity, materialName, _amt): void => {
|
||||
(_sourceDivision, sourceCity, _targetDivision, targetCity, materialName): void => {
|
||||
checkAccess(ctx, CorpUnlockName.WarehouseAPI);
|
||||
const sourceDivision = helpers.string(ctx, "sourceDivision", _sourceDivision);
|
||||
assertMember(ctx, CityName, "City Name", "sourceCity", sourceCity);
|
||||
const targetDivision = helpers.string(ctx, "targetDivision", _targetDivision);
|
||||
assertMember(ctx, CityName, "City Name", "targetCity", targetCity);
|
||||
assertMember(ctx, corpConstants.materialNames, "Material Name", "materialName", materialName);
|
||||
const amt = helpers.string(ctx, "amt", _amt);
|
||||
CancelExportMaterial(targetDivision, targetCity, getMaterial(sourceDivision, sourceCity, materialName), amt);
|
||||
CancelExportMaterial(targetDivision, targetCity, getMaterial(sourceDivision, sourceCity, materialName));
|
||||
},
|
||||
limitMaterialProduction: (ctx) => (_divisionName, cityName, materialName, _qty) => {
|
||||
checkAccess(ctx, CorpUnlockName.WarehouseAPI);
|
||||
|
@ -37,6 +37,8 @@ import { SpecialServers } from "./Server/data/SpecialServers";
|
||||
import { v2APIBreak } from "./utils/v2APIBreak";
|
||||
import { Corporation } from "./Corporation/Corporation";
|
||||
import { Terminal } from "./Terminal";
|
||||
import { getRecordValues } from "./Types/Record";
|
||||
import { ExportMaterial } from "./Corporation/Actions";
|
||||
|
||||
/* SaveObject.js
|
||||
* Defines the object used to save/load games
|
||||
@ -688,6 +690,38 @@ function evaluateVersionCompatibility(ver: string | number): void {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Sanitize corporation exports
|
||||
let anyExportsFailed = false;
|
||||
if (Player.corporation) {
|
||||
for (const division of Player.corporation.divisions.values()) {
|
||||
for (const warehouse of getRecordValues(division.warehouses)) {
|
||||
for (const material of getRecordValues(warehouse.materials)) {
|
||||
const originalExports = material.exports;
|
||||
// Clear all exports for the material
|
||||
material.exports = [];
|
||||
for (const originalExport of originalExports) {
|
||||
// Throw if there was a failure re-establishing an export
|
||||
try {
|
||||
const targetDivision = Player.corporation.divisions.get(originalExport.division);
|
||||
if (!targetDivision) throw new Error(`Target division ${originalExport.division} did not exist`);
|
||||
// Set the export again. ExportMaterial throws on failure
|
||||
ExportMaterial(targetDivision, originalExport.city, material, originalExport.amount);
|
||||
} catch (e) {
|
||||
anyExportsFailed = true;
|
||||
// We just need the text error, not a full stack trace
|
||||
console.error(`Failed to load export of material ${material.name} (${division.name} ${warehouse.city})
|
||||
Original export details: ${JSON.stringify(originalExport)}
|
||||
Error: ${e}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (anyExportsFailed)
|
||||
Terminal.error(
|
||||
"Some material exports failed to validate while loading and have been removed. See console for more info.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
2
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
2
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
@ -7093,7 +7093,6 @@ export interface WarehouseAPI {
|
||||
* @param targetDivision - Target division
|
||||
* @param targetCity - Target city
|
||||
* @param materialName - Name of the material
|
||||
* @param amt - Amount of material to export.
|
||||
*/
|
||||
cancelExportMaterial(
|
||||
sourceDivision: string,
|
||||
@ -7101,7 +7100,6 @@ export interface WarehouseAPI {
|
||||
targetDivision: string,
|
||||
targetCity: CityName | `${CityName}`,
|
||||
materialName: string,
|
||||
amt: number,
|
||||
): void;
|
||||
/**
|
||||
* Purchase warehouse for a new city
|
||||
|
Loading…
Reference in New Issue
Block a user