Fixed more bugs with new Corporation UI. Minor rebalancing on Corp UI. Changed the Market TA researches to allow you to automatically set price

This commit is contained in:
danielyxie 2019-03-17 17:58:06 -07:00
parent a28fe7ab9f
commit e6c5ff7ab7
14 changed files with 460 additions and 146 deletions

@ -10,7 +10,8 @@
#cmpy-mgmt-container p,
#cmpy-mgmt-container a,
#cmpy-mgmt-container div {
#cmpy-mgmt-container div,
#cmpy-mgmt-container br {
font-size: $defaultFontSize * 0.8125;
}

@ -297,9 +297,11 @@ export let CONSTANTS: IMap<any> = {
** Significantly changed the effects of the different employee positions. See updated descriptions
** Reduced the amount of money you gain from private investors
** Training employees is now 3x more effective
** Bug Fix: An industry's products are now properly separated between different cities
* Rebalanced BitNode-3 to make it slightly harder
* Bug Fix: Bladeburner's Hyperbolic Regeneration Chamber should no longer instantly refill all stamina
* Bug Fix: The cost of purchasing Augmentations for Duplicate Sleeves no longer scales with how many Augs you've purchased for yourself
`
}

@ -19,6 +19,7 @@ import { CONSTANTS } from "../Constants";
import { Factions } from "../Faction/Factions";
import { showLiterature } from "../Literature";
import { Locations } from "../Locations";
import { createCityMap } from "../Locations/Cities";
import { Player } from "../Player";
import { numeralWrapper } from "../ui/numeralFormat";
@ -590,7 +591,7 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
//At the start of the export state, set the imports of everything to 0
if (this.state === "EXPORT") {
for (var i = 0; i < Cities.length; ++i) {
for (let i = 0; i < Cities.length; ++i) {
var city = Cities[i], office = this.offices[city];
if (!(this.warehouses[city] instanceof Warehouse)) {
continue;
@ -605,7 +606,7 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
}
}
for (var i = 0; i < Cities.length; ++i) {
for (let i = 0; i < Cities.length; ++i) {
var city = Cities[i], office = this.offices[city];
if (this.warehouses[city] instanceof Warehouse) {
@ -665,19 +666,17 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
prod = maxProd;
}
prod *= (SecsPerMarketCycle * marketCycles); //Convert production from per second to per market cycle
//Calculate net change in warehouse storage making
//the produced materials will cost
// Calculate net change in warehouse storage making the produced materials will cost
var totalMatSize = 0;
for (var tmp = 0; tmp < this.prodMats.length; ++tmp) {
for (let tmp = 0; tmp < this.prodMats.length; ++tmp) {
totalMatSize += (MaterialSizes[this.prodMats[tmp]]);
}
for (var reqMatName in this.reqMats) {
if (this.reqMats.hasOwnProperty(reqMatName)) {
var normQty = this.reqMats[reqMatName];
totalMatSize -= (MaterialSizes[reqMatName] * normQty);
}
for (const reqMatName in this.reqMats) {
var normQty = this.reqMats[reqMatName];
totalMatSize -= (MaterialSizes[reqMatName] * normQty);
}
//If not enough space in warehouse, limit the amount of produced materials
// If not enough space in warehouse, limit the amount of produced materials
if (totalMatSize > 0) {
var maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / totalMatSize);
prod = Math.min(maxAmt, prod);
@ -685,10 +684,10 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
if (prod < 0) {prod = 0;}
//Keep track of production for smart supply (/s)
// Keep track of production for smart supply (/s)
warehouse.smartSupplyStore += (prod / (SecsPerMarketCycle * marketCycles));
//Make sure we have enough resource to make our materials
// Make sure we have enough resource to make our materials
var producableFrac = 1;
for (var reqMatName in this.reqMats) {
if (this.reqMats.hasOwnProperty(reqMatName)) {
@ -700,17 +699,15 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
}
if (producableFrac <= 0) {producableFrac = 0; prod = 0;}
//Make our materials if they are producable
// Make our materials if they are producable
if (producableFrac > 0 && prod > 0) {
for (var reqMatName in this.reqMats) {
if (this.reqMats.hasOwnProperty(reqMatName)) {
var reqMatQtyNeeded = (this.reqMats[reqMatName] * prod * producableFrac);
warehouse.materials[reqMatName].qty -= reqMatQtyNeeded;
warehouse.materials[reqMatName].prd = 0;
warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (SecsPerMarketCycle * marketCycles);
}
for (const reqMatName in this.reqMats) {
var reqMatQtyNeeded = (this.reqMats[reqMatName] * prod * producableFrac);
warehouse.materials[reqMatName].qty -= reqMatQtyNeeded;
warehouse.materials[reqMatName].prd = 0;
warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (SecsPerMarketCycle * marketCycles);
}
for (var j = 0; j < this.prodMats.length; ++j) {
for (let j = 0; j < this.prodMats.length; ++j) {
warehouse.materials[this.prodMats[j]].qty += (prod * producableFrac);
warehouse.materials[this.prodMats[j]].qlt =
(office.employeeProd[EmployeePositions.Engineer] / 90 +
@ -718,7 +715,7 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
Math.pow(warehouse.materials["AICores"].qty, this.aiFac) / 10e3);
}
} else {
for (var reqMatName in this.reqMats) {
for (const reqMatName in this.reqMats) {
if (this.reqMats.hasOwnProperty(reqMatName)) {
warehouse.materials[reqMatName].prd = 0;
}
@ -726,18 +723,16 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
}
//Per second
var fooProd = prod * producableFrac / (SecsPerMarketCycle * marketCycles);
for (var fooI = 0; fooI < this.prodMats.length; ++fooI) {
const fooProd = prod * producableFrac / (SecsPerMarketCycle * marketCycles);
for (let fooI = 0; fooI < this.prodMats.length; ++fooI) {
warehouse.materials[this.prodMats[fooI]].prd = fooProd;
}
} else {
//If this doesn't produce any materials, then it only creates
//Products. Creating products will consume materials. The
//Production of all consumed materials must be set to 0
for (var reqMatName in this.reqMats) {
if (this.reqMats.hasOwnProperty(reqMatName)) {
warehouse.materials[reqMatName].prd = 0;
}
for (const reqMatName in this.reqMats) {
warehouse.materials[reqMatName].prd = 0;
}
}
break;
@ -751,12 +746,39 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
mat.sll = 0;
continue;
}
var mat = warehouse.materials[matName];
// Calculate sale cost
// Sale multipliers
const businessFactor = this.getBusinessFactor(office); //Business employee productivity
const advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity
const marketFactor = this.getMarketFactor(mat); //Competition + demand
// Determine the cost that the material will be sold at
const markupLimit = mat.getMarkupLimit();
var sCost;
if (mat.marketTa1) {
if (mat.marketTa2) {
const prod = mat.prd;
// Reverse engineer the 'maxSell' formula
// 1. Set 'maxSell' = prod
// 2. Substitute formula for 'markup'
// 3. Solve for 'sCost'
const numerator = markupLimit;
const sqrtNumerator = prod;
const sqrtDenominator = ((mat.qlt + .001)
* marketFactor
* businessFactor
* company.getSalesMultiplier()
* advertisingFactor
* this.getSalesMultiplier());
const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator);
const optimalPrice = (numerator / denominator) + mat.bCost;
// We'll store this "Optimal Price" in a property so that we don't have
// to re-calculate it for the UI
mat.marketTa2Price = optimalPrice;
sCost = optimalPrice;
} else if (mat.marketTa1) {
sCost = mat.bCost + markupLimit;
} else if (isString(mat.sCost)) {
sCost = mat.sCost.replace(/MP/g, mat.bCost);
@ -780,9 +802,7 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
markup = mat.bCost / sCost;
}
}
var businessFactor = this.getBusinessFactor(office); //Business employee productivity
var advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity
var marketFactor = this.getMarketFactor(mat); //Competition + demand
var maxSell = (mat.qlt + .001)
* marketFactor
* markup
@ -905,8 +925,8 @@ Industry.prototype.processMaterials = function(marketCycles=1, company) {
//Produce Scientific Research based on R&D employees
//Scientific Research can be produced without a warehouse
if (office instanceof OfficeSpace) {
this.sciResearch.qty += (.005
* Math.pow(office.employeeProd[EmployeePositions.RandD], 0.55)
this.sciResearch.qty += (.004
* Math.pow(office.employeeProd[EmployeePositions.RandD], 0.5)
* company.getScientificResearchMultiplier()
* this.getScientificResearchMultiplier());
}
@ -962,9 +982,9 @@ Industry.prototype.processProducts = function(marketCycles=1, corporation) {
//Processes FINISHED products
Industry.prototype.processProduct = function(marketCycles=1, product, corporation) {
var totalProfit = 0;
for (var i = 0; i < Cities.length; ++i) {
var city = Cities[i], office = this.offices[city], warehouse = this.warehouses[city];
let totalProfit = 0;
for (let i = 0; i < Cities.length; ++i) {
let city = Cities[i], office = this.offices[city], warehouse = this.warehouses[city];
if (warehouse instanceof Warehouse) {
switch(this.state) {
@ -1040,27 +1060,60 @@ Industry.prototype.processProduct = function(marketCycles=1, product, corporatio
}
}
//Since its a product, its production cost is increased for labor
// Since its a product, its production cost is increased for labor
product.pCost *= ProductProductionCostRatio;
//Calculate Sale Cost (sCost), which could be dynamically evaluated
// Sale multipliers
const businessFactor = this.getBusinessFactor(office); //Business employee productivity
const advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity
const marketFactor = this.getMarketFactor(product); //Competition + demand
// Calculate Sale Cost (sCost), which could be dynamically evaluated
const markupLimit = product.rat / product.mku;
var sCost;
if (isString(product.sCost)) {
if (product.marketTa2) {
const prod = product.data[city][1];
// Reverse engineer the 'maxSell' formula
// 1. Set 'maxSell' = prod
// 2. Substitute formula for 'markup'
// 3. Solve for 'sCost'roduct.pCost = sCost
const numerator = markupLimit;
const sqrtNumerator = prod;
const sqrtDenominator = (0.5
* Math.pow(product.rat, 0.65)
* marketFactor
* corporation.getSalesMultiplier()
* businessFactor
* advertisingFactor
* this.getSalesMultiplier());
const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator);
let optimalPrice;
if (sqrtDenominator === 0 || denominator === 0) {
optimalPrice = 0;
} else {
optimalPrice = (numerator / denominator) + product.pCost;
}
// Store this "optimal Price" in a property so we don't have to re-calculate for UI
product.marketTa2Price[city] = optimalPrice;
sCost = optimalPrice;
} else if (product.marketTa1) {
sCost = product.pCost + markupLimit;
} else if (isString(product.sCost)) {
sCost = product.sCost.replace(/MP/g, product.pCost + product.rat / product.mku);
sCost = eval(sCost);
} else {
sCost = product.sCost;
}
var markup = 1, markupLimit = product.rat / product.mku;
var markup = 1;
if (sCost > product.pCost) {
if ((sCost - product.pCost) > markupLimit) {
markup = markupLimit / (sCost - product.pCost);
}
}
var businessFactor = this.getBusinessFactor(office); //Business employee productivity
var advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity
var marketFactor = this.getMarketFactor(product); //Competition + demand
var maxSell = 0.5
* Math.pow(product.rat, 0.65)
* marketFactor
@ -1085,8 +1138,9 @@ Industry.prototype.processProduct = function(marketCycles=1, product, corporatio
} else if (product.sllman[city][0] && product.sllman[city][1] > 0) {
//Sell amount is manually limited
sellAmt = Math.min(maxSell, product.sllman[city][1]);
} else if (product.sllman[city][0] === false){
sellAmt = 0;
} else {
//Backwards compatibility, -1 = 0
sellAmt = maxSell;
}
if (sellAmt < 0) { sellAmt = 0; }
@ -1114,8 +1168,7 @@ Industry.prototype.processProduct = function(marketCycles=1, product, corporatio
return totalProfit;
}
Industry.prototype.discontinueProduct = function(product, parentRefs) {
var company = parentRefs.company, industry = parentRefs.industry;
Industry.prototype.discontinueProduct = function(product) {
for (var productName in this.products) {
if (this.products.hasOwnProperty(productName)) {
if (product === this.products[productName]) {
@ -1182,7 +1235,7 @@ Industry.prototype.getOfficeProductivity = function(office, params) {
// Returns a multiplier based on the office' 'Business' employees that affects sales
Industry.prototype.getBusinessFactor = function(office) {
const businessProd = 1 + office.employeeProd[EmployeePositions.Business];
return calculateEffectWithFactors(businessProd, 0.26, 10e3);
}

@ -66,6 +66,7 @@ export class Material {
// Flags that signal whether automatic sale pricing through Market TA is enabled
marketTa1: boolean = false;
marketTa2: boolean = false;
marketTa2Price: number = 0;
constructor(params: IConstructorParams = {}) {
if (params.name) { this.name = params.name; }

@ -2,15 +2,17 @@ import { IMap } from "../types";
// Map of material (by name) to their sizes (how much space it takes in warehouse)
export const MaterialSizes: IMap<number> = {
Water: 0.05,
Energy: 0.01,
Food: 0.03,
Plants: 0.05,
Metal: 0.1,
Hardware: 0.06,
Chemicals: 0.05,
Drugs: 0.02,
Robots: 0.5,
AICores: 0.1,
RealEstate: 0,
Water: 0.05,
Energy: 0.01,
Food: 0.03,
Plants: 0.05,
Metal: 0.1,
Hardware: 0.06,
Chemicals: 0.05,
Drugs: 0.02,
Robots: 0.5,
AICores: 0.1,
RealEstate: 0,
"Real Estate": 0,
"AI Cores": 0,
}

@ -4,8 +4,10 @@ import { ProductRatingWeights,
IProductRatingWeight } from "./ProductRatingWeights";
import { Cities } from "../Locations/Cities";
import { createCityMap } from "../Locations/createCityMap";
import { IMap } from "../types";
import { Generic_fromJSON,
Generic_toJSON,
Reviver } from "../../utils/JSONReviver";
@ -89,14 +91,7 @@ export class Product {
// Data refers to the production, sale, and quantity of the products
// These values are specific to a city
// For each city, the data is [qty, prod, sell]
data: IMap<number[]> = {
[Cities.Aevum]: [0, 0, 0],
[Cities.Chongqing]: [0, 0, 0],
[Cities.Sector12]: [0, 0, 0],
[Cities.NewTokyo]: [0, 0, 0],
[Cities.Ishima]: [0, 0, 0],
[Cities.Volhaven]: [0, 0, 0],
}
data: IMap<number[]> = createCityMap<number[]>([0, 0, 0]);
// Location of this Product
// Only applies for location-based products like restaurants/hospitals
@ -113,23 +108,13 @@ export class Product {
// Data to keep track of whether production/sale of this Product is
// manually limited. These values are specific to a city
// [Whether production/sale is limited, limit amount]
prdman: IMap<any[]> = {
[Cities.Aevum]: [false, 0],
[Cities.Chongqing]: [false, 0],
[Cities.Sector12]: [false, 0],
[Cities.NewTokyo]: [false, 0],
[Cities.Ishima]: [false, 0],
[Cities.Volhaven]: [false, 0],
}
prdman: IMap<any[]> = createCityMap<any[]>([false, 0]);
sllman: IMap<any[]> = createCityMap<any[]>([false, 0]);
sllman: IMap<any[]> = {
[Cities.Aevum]: [false, 0],
[Cities.Chongqing]: [false, 0],
[Cities.Sector12]: [false, 0],
[Cities.NewTokyo]: [false, 0],
[Cities.Ishima]: [false, 0],
[Cities.Volhaven]: [false, 0],
}
// Flags that signal whether automatic sale pricing through Market TA is enabled
marketTa1: boolean = false;
marketTa2: boolean = false;
marketTa2Price: IMap<number> = createCityMap<number>(0);
constructor(params: IConstructorParams={}) {
this.name = params.name ? params.name : "";

@ -46,6 +46,10 @@ export class Warehouse {
// Whether Smart Supply is enabled for this Industry (the Industry that this Warehouse is for)
smartSupplyEnabled: boolean = false;
// Flag that indicates whether Smart Supply accounts for imports when calculating
// the amount fo purchase
smartSupplyConsiderExports: boolean = false;
// Stores the amount of product to be produced. Used for Smart Supply unlock.
// The production tracked by smart supply is always based on the previous cycle,
// so it will always trail the "true" production by 1 cycle

@ -94,11 +94,13 @@ export const researchMetadata: IConstructorParams[] = [
},
{
name: "Market-TA.II",
cost: 40e3,
cost: 50e3,
desc: "Develop double-advanced AI software that uses technical analysis to " +
"help you understand and exploit the market. This research " +
"allows you to know how many sales of a Material/Product you lose or gain " +
"from having too high or too low or a sale price.",
"from having too high or too low or a sale price. It also lets you automatically " +
"set the sale price of your Materials/Products at the optimal price such that " +
"the amount sold matches the amount produced.",
},
{
name: "Overclock",

@ -17,10 +17,14 @@ import { Industries,
IndustryDescriptions,
IndustryResearchTrees } from "../IndustryData";
import { MaterialSizes } from "../MaterialSizes";
import { Product } from "../Product";
import { Player } from "../../Player";
import { Cities } from "../../Locations/Cities";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
@ -81,7 +85,7 @@ export class CorporationEventHandler {
var totalAmount = Number(money) + (stockShares * stockPrice);
var repGain = totalAmount / BribeToRepRatio;
repGainText.innerText = "You will gain " + numeralWrapper.formatNumber(repGain, "0,0") +
repGainText.innerText = "You will gain " + numeralWrapper.format(repGain, "0,0") +
" reputation with " +
factionSelector.options[factionSelector.selectedIndex].value +
" with this bribe";
@ -104,7 +108,7 @@ export class CorporationEventHandler {
var totalAmount = money + (stockShares * stockPrice);
var repGain = totalAmount / BribeToRepRatio;
console.log("repGain: " + repGain);
repGainText.innerText = "You will gain " + numeralWrapper.formatNumber(repGain, "0,0") +
repGainText.innerText = "You will gain " + numeralWrapper.format(repGain, "0,0") +
" reputation with " +
factionSelector.options[factionSelector.selectedIndex].value +
" with this bribe";
@ -131,7 +135,7 @@ export class CorporationEventHandler {
} else {
var totalAmount = money + (stockShares * stockPrice);
var repGain = totalAmount / BribeToRepRatio;
dialogBoxCreate("You gained " + formatNumber(repGain, 0) +
dialogBoxCreate("You gained " + numeralWrapper.format(repGain, "0,0") +
" reputation with " + fac.name + " by bribing them.");
fac.playerReputation += repGain;
this.corp.funds = this.corp.funds.minus(money);
@ -170,7 +174,6 @@ export class CorporationEventHandler {
type:"number", placeholder:"Shares to buyback", margin:"5px",
inputListener: ()=> {
var numShares = Math.round(input.value);
//TODO add conditional for if player doesn't have enough money
if (isNaN(numShares) || numShares <= 0) {
costIndicator.innerText = "ERROR: Invalid value entered for number of shares to buyback"
} else if (numShares > this.corp.issuedShares) {
@ -228,7 +231,7 @@ export class CorporationEventHandler {
}
// Create a popup that lets the player discontinue a product
createDiscontinueProductPopup(product) {
createDiscontinueProductPopup(product, industry) {
const popupId = "cmpy-mgmt-discontinue-product-popup";
const txt = createElement("p", {
innerText:"Are you sure you want to do this? Discontinuing a product " +
@ -237,9 +240,9 @@ export class CorporationEventHandler {
"removed and left unsold",
});
const confirmBtn = createElement("button", {
class:"a-link-button",innerText:"Discontinue",
class:"popup-box-button",innerText:"Discontinue",
clickListener: () => {
industry.discontinueProduct(product, parentRefs);
industry.discontinueProduct(product);
removeElementById(popupId);
this.rerender();
return false;
@ -247,7 +250,7 @@ export class CorporationEventHandler {
});
const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" });
createPopup(popupId, [txt, confirmBtn, cancelBtn]);
createPopup(popupId, [txt, cancelBtn, confirmBtn]);
}
// Create a popup that lets the player manage exports
@ -669,8 +672,8 @@ export class CorporationEventHandler {
productNameInput.focus();
}
// Create a popup that lets the player use the Market TA research
createMarketTaPopup(mat, industry) {
// Create a popup that lets the player use the Market TA research for Materials
createMaterialMarketTaPopup(mat, industry) {
const corp = this.corp;
const popupId = "cmpy-mgmt-marketta-popup";
@ -694,19 +697,21 @@ export class CorporationEventHandler {
"be sold at the price identified by Market-TA.I (i.e. the price shown above)"
})
const useTa1AutoSaleCheckbox = createElement("input", {
checked: mat.marketTa1,
id: useTa1AutoSaleId,
margin: "3px",
type: "checkbox",
value: mat.marketTa1,
changeListener: (e) => {
mat.marketTa1 = e.target.value;
mat.marketTa1 = e.target.checked;
}
});
useTa1AutoSaleDiv.appendChild(useTa1AutoSaleCheckbox);
useTa1AutoSaleDiv.appendChild(useTa1AutoSaleLabel);
useTa1AutoSaleDiv.appendChild(useTa1AutoSaleCheckbox);
const closeBtn = createPopupCloseButton(popupId, {
class: "std-button",
display: "block",
innerText: "Close",
});
if (industry.hasResearch("Market-TA.II")) {
@ -741,11 +746,36 @@ export class CorporationEventHandler {
}
ta2Text.innerHTML = `<br><u><strong>Market-TA.II</strong></u><br>` +
`If you sell at ${numeralWrapper.formatMoney(sCost)}, ` +
`then you will sell ${formatNumber(markup, 5)}x as much compared ` +
`then you will sell ${numeralWrapper.format(markup, "0.00000")}x as much compared ` +
`to if you sold at market price.`;
}
updateTa2Text();
createPopup(popupId, [ta1, ta2Text, ta2Input, closeBtn]);
// Enable using Market-TA2 for automatically setting sale price
const useTa2AutoSaleId = "cmpy-mgmt-marketa2-checkbox";
const useTa2AutoSaleDiv = createElement("div", { display: "block" });
const useTa2AutoSaleLabel = createElement("label", {
color: "white",
for: useTa2AutoSaleId,
innerText: "Use Market-TA.II for Auto-Sale Price",
tooltip: "If this is enabled, then this Material will automatically " +
"be sold at the optimal price such that the amount sold matches the " +
"amount produced. (i.e. the highest possible price, while still ensuring " +
" that all produced materials will be sold)"
})
const useTa2AutoSaleCheckbox = createElement("input", {
checked: mat.marketTa2,
id: useTa2AutoSaleId,
margin: "3px",
type: "checkbox",
changeListener: (e) => {
mat.marketTa2 = e.target.checked;
}
});
useTa2AutoSaleDiv.appendChild(useTa2AutoSaleLabel);
useTa2AutoSaleDiv.appendChild(useTa2AutoSaleCheckbox);
createPopup(popupId, [ta1, useTa1AutoSaleDiv, ta2Text, ta2Input, useTa2AutoSaleDiv, closeBtn]);
} else {
// Market-TA.I only
createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]);
@ -775,6 +805,7 @@ export class CorporationEventHandler {
display:"inline-block",
innerText: "Confirm",
clickListener: () => {
if (citySelector.length <= 0) { return false; }
let city = citySelector.options[citySelector.selectedIndex].value;
if (this.corp.funds.lt(OfficeInitialCost)) {
dialogBoxCreate("You don't have enough company funds to open a new office!");
@ -921,8 +952,110 @@ export class CorporationEventHandler {
return false;
}
// Create a popup that lets the player use the Market TA research for Products
createProductMarketTaPopup(product, industry) {
const corp = this.corp;
const popupId = "cmpy-mgmt-marketta-popup";
const markupLimit = product.rat / product.mku;
const ta1 = createElement("p", {
innerHTML: "<u><strong>Market-TA.I</strong></u><br>" +
"The maximum sale price you can mark this up to is " +
numeralWrapper.formatMoney(product.pCost + markupLimit) +
". This means that if you set the sale price higher than this, " +
"you will begin to experience a loss in number of sales",
});
// Enable using Market-TA1 for automatically setting sale price
const useTa1AutoSaleId = "cmpy-mgmt-marketa1-checkbox";
const useTa1AutoSaleDiv = createElement("div", { display: "block" });
const useTa1AutoSaleLabel = createElement("label", {
color: "white",
for: useTa1AutoSaleId,
innerText: "Use Market-TA.I for Auto-Sale Price",
tooltip: "If this is enabled, then this Product will automatically " +
"be sold at the price identified by Market-TA.I (i.e. the price shown above)"
})
const useTa1AutoSaleCheckbox = createElement("input", {
checked: product.marketTa1,
id: useTa1AutoSaleId,
margin: "3px",
type: "checkbox",
changeListener: (e) => {
product.marketTa1 = e.target.checked;
}
});
useTa1AutoSaleDiv.appendChild(useTa1AutoSaleLabel);
useTa1AutoSaleDiv.appendChild(useTa1AutoSaleCheckbox);
const closeBtn = createPopupCloseButton(popupId, {
class: "std-button",
display: "block",
innerText: "Close",
});
if (industry.hasResearch("Market-TA.II")) {
let updateTa2Text;
const ta2Text = createElement("p");
const ta2Input = createElement("input", {
marginTop: "4px",
onkeyup: (e) => {
e.preventDefault();
updateTa2Text();
},
type: "number",
value: product.pCost,
});
// Function that updates the text in ta2Text element
updateTa2Text = function() {
const sCost = parseFloat(ta2Input.value);
let markup = 1;
if (sCost > product.pCost) {
if ((sCost - product.pCost) > markupLimit) {
markup = markupLimit / (sCost - product.pCost);
}
}
ta2Text.innerHTML = `<br><u><strong>Market-TA.II</strong></u><br>` +
`If you sell at ${numeralWrapper.formatMoney(sCost)}, ` +
`then you will sell ${numeralWrapper.format(markup, "0.00000")}x as much compared ` +
`to if you sold at market price.`;
}
updateTa2Text();
// Enable using Market-TA2 for automatically setting sale price
const useTa2AutoSaleId = "cmpy-mgmt-marketa2-checkbox";
const useTa2AutoSaleDiv = createElement("div", { display: "block" });
const useTa2AutoSaleLabel = createElement("label", {
color: "white",
for: useTa2AutoSaleId,
innerText: "Use Market-TA.II for Auto-Sale Price",
tooltip: "If this is enabled, then this Product will automatically " +
"be sold at the optimal price such that the amount sold matches the " +
"amount produced. (i.e. the highest possible price, while still ensuring " +
" that all produced materials will be sold)"
})
const useTa2AutoSaleCheckbox = createElement("input", {
checked: product.marketTa2,
id: useTa2AutoSaleId,
margin: "3px",
type: "checkbox",
changeListener: (e) => {
product.marketTa2 = e.target.checked;
}
});
useTa2AutoSaleDiv.appendChild(useTa2AutoSaleLabel);
useTa2AutoSaleDiv.appendChild(useTa2AutoSaleCheckbox);
createPopup(popupId, [ta1, useTa1AutoSaleDiv, ta2Text, ta2Input, useTa2AutoSaleDiv, closeBtn]);
} else {
// Market-TA.I only
createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]);
}
}
// Create a popup that lets the player purchase a Material
createPurchaseMaterialPopup(mat, industry) {
createPurchaseMaterialPopup(mat, industry, warehouse) {
const corp = this.corp;
const purchasePopupId = "cmpy-mgmt-material-purchase-popup";
@ -980,15 +1113,20 @@ export class CorporationEventHandler {
let bulkPurchaseCostTxt = createElement("p");
function updateBulkPurchaseText(amount) {
const cost = parseFloat(amount) * mat.bCost;
if (isNaN(cost)) {
dialogBoxCreate(`Bulk Purchase Cost calculated to be NaN. This is either due to ` +
`invalid input, or it is a bug (in which case you should report to dev)`);
return;
}
const parsedAmt = parseFloat(amount);
const cost = parsedAmt * mat.bCost;
bulkPurchaseCostTxt.innerText = `Purchasing ${numeralWrapper.format(amt, "0,0.00")} of ` +
`${mat.name} will cost ${numeralWrapper.formatMoney(cost)}`;
const matSize = MaterialSizes[mat.name];
const maxAmount = ((warehouse.size - warehouse.sizeUsed) / matSize);
if (parsedAmt * matSize > maxAmount) {
bulkPurchaseCostTxt.innerText = "Not enough warehouse space to purchase this amount";
} else if (isNaN(cost)) {
bulkPurchaseCostTxt.innerText = "Invalid put for Bulk Purchase amount";
} else {
bulkPurchaseCostTxt.innerText = `Purchasing ${numeralWrapper.format(parsedAmt, "0,0.00")} of ` +
`${mat.name} will cost ${numeralWrapper.formatMoney(cost)}`;
}
}
let bulkPurchaseConfirmBtn;
@ -998,7 +1136,7 @@ export class CorporationEventHandler {
type: "number",
onkeyup: (e) => {
e.preventDefault();
updateBulkPurchaseText();
updateBulkPurchaseText(e.target.value);
if (e.keyCode === KEY.ENTER) {bulkPurchaseConfirmBtn.click();}
}
});
@ -1007,7 +1145,15 @@ export class CorporationEventHandler {
class: "std-button",
innerText: "Confirm Bulk Purchase",
clickListener: () => {
const amount = parseFloat(input.value);
const amount = parseFloat(bulkPurchaseInput.value);
const matSize = MaterialSizes[mat.name];
const maxAmount = ((warehouse.size - warehouse.sizeUsed) / matSize);
if (amount * matSize > maxAmount) {
dialogBoxCreate(`You do not have enough warehouse size to fit this purchase`);
return false;
}
if (isNaN(amount)) {
dialogBoxCreate("Invalid input amount");
} else {
@ -1065,9 +1211,18 @@ export class CorporationEventHandler {
if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
}
});
let inputButtonInitValue = mat.sCost ? mat.sCost : null;
if (mat.marketTa2) {
inputButtonInitValue += " (Market-TA.II)";
} else if (mat.marketTa1) {
inputButtonInitValue += " (Market-TA.I)";
}
const inputPx = createElement("input", {
type: "text", marginTop: "4px",
value: mat.sCost ? mat.sCost : null, placeholder: "Sell price",
value: inputButtonInitValue,
placeholder: "Sell price",
onkeyup: (e) => {
e.preventDefault();
if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
@ -1179,16 +1334,41 @@ export class CorporationEventHandler {
if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
}
});
let inputButtonInitValue = product.sCost ? product.sCost : null;
if (product.marketTa2) {
inputButtonInitValue += " (Market-TA.II)";
} else if (product.marketTa1) {
inputButtonInitValue += " (Market-TA.I)";
}
const inputPx = createElement("input", {
margin: "5px 0px 5px 0px",
placeholder: "Sell price",
type: "text",
value: product.sCost ? product.sCost : null,
value: inputButtonInitValue,
onkeyup: (e) => {
e.preventDefault();
if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
}
});
const checkboxDiv = createElement("div", {
border: "1px solid white",
display: "inline-block",
})
const checkboxLabel = createElement("label", {
for: popupId + "-checkbox",
innerText: "Use same 'Sell Amount' for all cities",
});
const checkbox = createElement("input", {
checked: true,
id: popupId + "-checkbox",
margin: "2px",
type: "checkbox",
});
checkboxDiv.appendChild(checkboxLabel);
checkboxDiv.appendChild(checkbox);
confirmBtn = createElement("button", {
class: "std-button",
innerText: "Confirm",
@ -1220,7 +1400,10 @@ export class CorporationEventHandler {
product.sCost = cost;
}
//Parse quantity
// Array of all cities. Used later
const cities = Object.values(Cities);
// Parse quantity
if (inputQty.value.includes("MAX") || inputQty.value.includes("PROD")) {
//Dynamically evaluated quantity. First test to make sure its valid
var qty = inputQty.value.replace(/\s+/g, '');
@ -1238,8 +1421,16 @@ export class CorporationEventHandler {
dialogBoxCreate("Invalid value or expression for sell price field");
return false;
}
product.sllman[city][0] = true;
product.sllman[city][1] = qty; //Use sanitized input
if (checkbox.checked) {
for (let i = 0; i < cities.length; ++i) {
const tempCity = cities[i];
product.sllman[tempCity][0] = true;
product.sllman[tempCity][1] = qty; //Use sanitized input
}
} else {
product.sllman[city][0] = true;
product.sllman[city][1] = qty; //Use sanitized input
}
} else if (isNaN(inputQty.value)) {
dialogBoxCreate("Invalid value for sell quantity field! Must be numeric");
return false;
@ -1247,10 +1438,25 @@ export class CorporationEventHandler {
var qty = parseFloat(inputQty.value);
if (isNaN(qty)) {qty = 0;}
if (qty === 0) {
product.sllman[city][0] = false;
if (checkbox.checked) {
for (let i = 0; i < cities.length; ++i) {
const tempCity = cities[i];
product.sllman[tempCity][0] = false;
}
} else {
product.sllman[city][0] = false;
}
} else {
product.sllman[city][0] = true;
product.sllman[city][1] = qty;
if (checkbox.checked) {
for (let i = 0; i < cities.length; ++i) {
const tempCity = cities[i];
product.sllman[tempCity][0] = true;
product.sllman[tempCity][1] = qty;
}
} else {
product.sllman[city][0] = true;
product.sllman[city][1] = qty;
}
}
}
@ -1259,9 +1465,12 @@ export class CorporationEventHandler {
return false;
}
});
const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" });
const cancelBtn = createPopupCloseButton(popupId, { class: "std-button" });
createPopup(popupId, [txt, inputQty, inputPx, confirmBtn, cancelBtn]);
const linebreak1 = createElement("br");
createPopup(popupId, [txt, inputQty, inputPx, confirmBtn, cancelBtn, linebreak1,
checkboxDiv]);
inputQty.focus();
}

@ -305,7 +305,7 @@ export class IndustryOffice extends BaseReactComponent {
<p>Total Employee Salary: {numeralWrapper.formatMoney(totalSalary)}</p>
{
vechain &&
<p className={"tooltip"} style={{display: "block"}}>
<p className={"tooltip"} style={{display: "inline-block"}}>
Material Production: {numeralWrapper.format(division.getOfficeProductivity(office), "0.000")}
<span className={"tooltiptext"}>
The base amount of material this office can produce. Does not include
@ -314,9 +314,12 @@ export class IndustryOffice extends BaseReactComponent {
</span>
</p>
}
{
vechain && <br />
}
{
vechain &&
<p className={"tooltip"} style={{display: "block"}}>
<p className={"tooltip"} style={{display: "inline-block"}}>
Product Production: {numeralWrapper.format(division.getOfficeProductivity(office, {forProduct:true}), "0.000")}
<span className={"tooltiptext"}>
The base amount of any given Product this office can produce. Does not include
@ -325,15 +328,21 @@ export class IndustryOffice extends BaseReactComponent {
</span>
</p>
}
{
vechain && <br />
}
{
vechain &&
<p className={"tooltip"} style={{display: "block"}}>
<p className={"tooltip"} style={{display: "inline-block"}}>
Business Multiplier: x{numeralWrapper.format(division.getBusinessFactor(office), "0.000")}
<span className={"tooltiptext"}>
The effect this office's 'Business' employees has on boosting sales
</span>
</p>
}
{
vechain && <br />
}
<h2 className={"tooltip"} style={positionHeaderStyle}>
{EmployeePositions.Operations} ({this.state.numOperations})

@ -3,13 +3,13 @@
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
import { Material } from "../Material";
import { Product } from "../Product";
import { Warehouse,
import { OfficeSpace,
WarehouseInitialCost,
WarehouseUpgradeBaseCost,
ProductProductionCostRatio } from "../Corporation";
import { Material } from "../Material";
import { Product } from "../Product";
import { Warehouse } from "../Warehouse";
import { numeralWrapper } from "../../ui/numeralFormat";
@ -45,7 +45,12 @@ function ProductComponent(props) {
sellButtonText = "Sell (0.000/0.000)";
}
if (product.sCost) {
if (product.marketTa2) {
sellButtonText += (" @ " + numeralWrapper.formatMoney(product.marketTa2Price[city]));
} else if (product.marketTa1) {
const markupLimit = product.rat / product.mku;
sellButtonText += (" @ " + numeralWrapper.formatMoney(product.pCost + markupLimit));
} else if (product.sCost) {
if (isString(product.sCost)) {
sellButtonText += (" @ " + product.sCost);
} else {
@ -55,14 +60,17 @@ function ProductComponent(props) {
const sellButtonOnClick = eventHandler.createSellProductPopup.bind(eventHandler, product, city);
// Limit Production button
const limitProductionButtonText = "Limit Production";
let 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);
const discontinueButtonOnClick = eventHandler.createDiscontinueProductPopup.bind(eventHandler, product, division);
// Market TA button
const marketTaButtonOnClick = eventHandler.createProductMarketTaPopup.bind(eventHandler, product, division);
// Unfinished Product
if (!product.fin) {
@ -83,6 +91,12 @@ function ProductComponent(props) {
<button className={"std-button"} onClick={discontinueButtonOnClick}>
Discontinue
</button>
{
division.hasResearch("Market-TA.I") &&
<button className={"std-button"} onClick={marketTaButtonOnClick}>
Market-TA
</button>
}
</div>
</div>
)
@ -139,7 +153,7 @@ function ProductComponent(props) {
</span>
</p><br />
<p className={"tooltip"}>
Est. Market Price: {numeralWrapper.formatMoney(product.pCost + product.rat / product.mku)}
Est. Market Price: {numeralWrapper.formatMoney(product.pCost)}
<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
@ -157,6 +171,12 @@ function ProductComponent(props) {
<button className={"std-button"} onClick={discontinueButtonOnClick}>
Discontinue
</button>
{
division.hasResearch("Market-TA.I") &&
<button className={"std-button"} onClick={marketTaButtonOnClick}>
Market-TA
</button>
}
</div>
</div>
)
@ -167,9 +187,14 @@ function MaterialComponent(props) {
const corp = props.corp;
const division = props.division;
const warehouse = props.warehouse;
const city = props.city;
const mat = props.mat;
const eventHandler = props.eventHandler;
const markupLimit = mat.getMarkupLimit();
const office = division.offices[city];
if (!(office instanceof OfficeSpace)) {
throw new Error(`Could not get OfficeSpace object for this city (${city})`);
}
// Numeraljs formatter
const nf = "0.000";
@ -195,7 +220,7 @@ function MaterialComponent(props) {
// 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);
const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division, warehouse);
// Export material button
const exportButtonOnClick = eventHandler.createExportMaterialPopup.bind(eventHandler, mat);
@ -209,10 +234,12 @@ function MaterialComponent(props) {
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)) {
if (mat.marketTa2) {
sellButtonText += " @ " + numeralWrapper.formatMoney(mat.marketTa2Price);
} else if (mat.marketTa1) {
sellButtonText += " @ " + numeralWrapper.formatMoney(mat.bCost + markupLimit);
} else if (mat.sCost) {
if (isString(mat.sCost)) {
var sCost = mat.sCost.replace(/MP/g, mat.bCost);
sellButtonText += " @ " + numeralWrapper.formatMoney(eval(sCost));
} else {
@ -225,7 +252,7 @@ function MaterialComponent(props) {
const sellButtonOnClick = eventHandler.createSellMaterialPopup.bind(eventHandler, mat);
// Market TA button
const marketTaButtonOnClick = eventHandler.createMarketTaPopup.bind(eventHandler, mat, division);
const marketTaButtonOnClick = eventHandler.createMaterialMarketTaPopup.bind(eventHandler, mat, division);
return (
<div className={"cmpy-mgmt-warehouse-material-div"} key={props.key}>
@ -411,6 +438,7 @@ export class IndustryWarehouse extends BaseReactComponent {
// Only create UI for materials that are relevant for the industry
if (isRelevantMaterial(matName)) {
mats.push(MaterialComponent({
city: this.props.currentCity,
corp: corp,
division: division,
eventHandler: this.eventHandler(),

@ -0,0 +1,18 @@
/**
* Utility function that creates a "city map", which is an object where
* each city is a key (property).
*
* This map uses the official name of the city, NOT its key in the 'Cities' object
*/
import { Cities } from "./Cities";
import { IMap } from "../types";
export function createCityMap<T>(initValue: T): IMap<T> {
const map: IMap<any> = {};
const cities = Object.values(Cities);
for (let i = 0; i < cities.length; ++i) {
map[cities[i]] = initValue;
}
return map;
}

@ -105,13 +105,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);
if (p.canAfford(aug.startingCost)) {
p.loseMoney(aug.startingCost);
sleeve.installAugmentation(aug);
dialogBoxCreate(`Installed ${aug.name} on Duplicate Sleeve!`, false)
removeElementById(popupId);

@ -2,7 +2,7 @@
"compilerOptions": {
"baseUrl" : ".",
"jsx": "react",
"lib" : ["es2016", "dom"],
"lib" : ["es2016", "dom", "es2017.object"],
"module": "commonjs",
"target": "es6",
"sourceMap": true,