diff --git a/devTools/types/FC/mods.d.ts b/devTools/types/FC/mods.d.ts index 093b1317a64add79242937b2b36aca1a32c3f595..9d477f878a3efc8e0cd097fa40cdb66c5a56c932 100644 --- a/devTools/types/FC/mods.d.ts +++ b/devTools/types/FC/mods.d.ts @@ -1,46 +1,48 @@ -declare namespace FC { - namespace Mods { - /** The base mod interface, from which all mod interfaces inherit. */ - interface Base { - /** Whether the mod is enabled. */ - enabled: boolean; - } - - /** Properties relating to the food system mod. */ - interface Food extends Base { - /** How much food the arcology has, in kg. */ - amount: number; - /** The base price of 1kg of food. */ - cost: number; - /** How much food the arcology had at the end of last week, in kg. */ - lastWeek: number; - /** Whether the food market has been established. */ - market: boolean; - /** The amount of food produced this week, in kg. */ - produced: number; - /** The amount of food each class consumes in a week. */ - rate: { - /** The amount of food a slave consumes in a week, in kg. */ - slave: 8, - /** The amount of food the lower class consumes in a week, in kg. */ - lower: 14.5, - /** The amount of food the middle class consumes in a week, in kg. */ - middle: 16, - /** The amount of food the upper class consumes in a week, in kg. */ - upper: 17.5, - /** The amount of food the elite class consumes in a week, in kg. */ - top: 19, - }, - /** - * How much food you are providing your citizens weekly. - * - * Not in kg. - */ - rations: 0 | 1 | 2 | 3 | 4 | 5; - /** How much food the arcology has produced in total, in kg. */ - total: number; - /** Whether the player has received a warning about a lack of food. */ - warned: boolean; - } - } -} +declare namespace FC { + namespace Mods { + /** The base mod interface, from which all mod interfaces inherit. */ + interface Base { + /** Whether the mod is enabled. */ + enabled: boolean; + } + + /** Properties relating to the food system mod. */ + interface Food extends Base { + /** How much food the arcology has, in kg. */ + amount: number; + /** The base price of 1kg of food. */ + cost: number; + /** How much food the arcology had at the end of last week, in kg. */ + lastWeek: number; + /** Whether the food market has been established. */ + market: boolean; + /** The amount of food that needed to be bought this week to to stop people from starving */ + deficit: number, + /** The amount of food that had to be sold this week because there was not enough storage space */ + overstocked: number, + /** The amount of food each class consumes in a week. */ + rate: { + /** The amount of food a slave consumes in a week, in kg. */ + slave: 8, + /** The amount of food the lower class consumes in a week, in kg. */ + lower: 14.5, + /** The amount of food the middle class consumes in a week, in kg. */ + middle: 16, + /** The amount of food the upper class consumes in a week, in kg. */ + upper: 17.5, + /** The amount of food the elite class consumes in a week, in kg. */ + top: 19, + }, + /** + * How much food you are providing your citizens weekly. + * + * Not in kg. + */ + rations: 0 | 1 | 2 | 3 | 4 | 5; + /** How much food the arcology has produced in total, in kg. */ + total: number; + /** Whether the player has received a warning about a lack of food. */ + warned: boolean; + } + } +} diff --git a/js/003-data/gameVariableData.js b/js/003-data/gameVariableData.js index 85898008382ce7d78e0ec447fa50d20183f7d4de..fc390fb52f12b4640bdc172a143a4a927d6b5f15 100644 --- a/js/003-data/gameVariableData.js +++ b/js/003-data/gameVariableData.js @@ -364,9 +364,10 @@ App.Data.defaultGameStateVariables = { amount: 125000, cost: 25, - lastWeek: 0, + lastWeek: 125000, market: false, - produced: 0, + deficit: 0, + overstocked: 0, rate: { slave: 8, lower: 14.5, @@ -812,6 +813,8 @@ App.Data.resetOnNGPlus = { hydroponics: 0, machinery: 0, seeds: 0, + /** available food storage in tons */ + foodStorage: 150, }, farmyardCrops: 0, farmyardStables: 0, diff --git a/src/arcologyBuilding/ManageArcology.js b/src/arcologyBuilding/ManageArcology.js index a55cbfe253345fb67623040bed9784e092baf0d1..42843ed1a28b3cc6f983982107e2ed5da4aa772f 100644 --- a/src/arcologyBuilding/ManageArcology.js +++ b/src/arcologyBuilding/ManageArcology.js @@ -611,7 +611,7 @@ App.UI.manageArcology = function() { if (V.mods.food.enabled && V.mods.food.market) { App.UI.DOM.appendNewElement("h2", div, "Food Management"); - div.append(App.UI.foodMarket()); + div.append(App.UI.foodMarket.main()); } else if (V.eventResults.foodCrisis && !V.mods.food.rations) { const price = V.PC.skill.trading >= 50 && ["capitalist", "entrepreneur", "business kid"].includes(V.PC.career) || V.PC.skill.trading >= 100 ? 112_500 : 150_000; diff --git a/src/data/backwardsCompatibility/farmyardBC.js b/src/data/backwardsCompatibility/farmyardBC.js index ff48d37a05d4a4e438d152ee89c8a2e815adac13..cf4bdf66ed5542466aa4adc4876cc95bde6add93 100644 --- a/src/data/backwardsCompatibility/farmyardBC.js +++ b/src/data/backwardsCompatibility/farmyardBC.js @@ -6,6 +6,10 @@ App.Facilities.Farmyard.BC = function() { }; } + V.farmyardUpgrades.foodStorage = V.farmyardUpgrades.foodStorage ?? (V.mods.food.amount > 0) + ? Math.trunc(V.mods.food.amount / 1000) + 50 // old saves get enough storage to hold all their food plus a buffer + : 150; + if (App.Data.Animals.size === 0) { App.Facilities.Farmyard.animals.init(); } @@ -16,6 +20,9 @@ App.Facilities.Farmyard.BC = function() { delete V.foodStored; } + V.mods.food.deficit = V.mods.food.deficit ?? 0; + V.mods.food.overstocked = V.mods.food.overstocked ?? 0; + if (V.canine) { V.animals.canine = Array.from(V.canine); diff --git a/src/data/backwardsCompatibility/modsBC.js b/src/data/backwardsCompatibility/modsBC.js index d536a0f680db20dcb3148e9e6c3b0708a76185bf..2f8ce80a13ca051626c586f145565a903c9897e0 100644 --- a/src/data/backwardsCompatibility/modsBC.js +++ b/src/data/backwardsCompatibility/modsBC.js @@ -9,7 +9,6 @@ App.Update.mods = function(node) { amount: "food", lastWeek: "foodLastWeek", market: "foodMarket", - produced: "foodProduced", rate: "foodRate", rations: "foodRations", total: "foodTotal", diff --git a/src/data/newGamePlus.js b/src/data/newGamePlus.js index 2a677ac110f5ad0948b17db06e2d0aff678abe15..ea1cb7a996569a93fdc3ad5ce096af3c424f2a1f 100644 --- a/src/data/newGamePlus.js +++ b/src/data/newGamePlus.js @@ -232,15 +232,12 @@ App.Data.NewGamePlus = (function() { } function updateMods() { - if (V.mods.food.enabled) { - V.mods.food.amount = 0; - V.mods.food.lastWeek = 0; - V.mods.food.market = false; - V.mods.food.produced = 0; - V.mods.food.rations = 0; - V.mods.food.total = 0; - V.mods.food.warned = false; - } + V.mods.food.amount = 0; + V.mods.food.lastWeek = 0; + V.mods.food.market = false; + V.mods.food.rations = 0; + V.mods.food.total = 0; + V.mods.food.warned = false; } function doNGPSetup() { diff --git a/src/endWeek/economics/arcmgmt.js b/src/endWeek/economics/arcmgmt.js index fef8be08a884c1d9d7c7d8dba98ab4d6b22c5314..f57fb35e284a248ae60ee31065662f404f3c8d6b 100644 --- a/src/endWeek/economics/arcmgmt.js +++ b/src/endWeek/economics/arcmgmt.js @@ -722,12 +722,7 @@ App.EndWeek.arcManagement = function() { r = []; } - const food = document.createElement("span"); - food.id = "food"; - if (V.mods.food.enabled && V.mods.food.market) { - food.append(App.UI.foodReport()); - } - el.append(food); + App.Events.addParagraph(el, [App.UI.foodReport()]); App.Events.addNode(el, r); return el; diff --git a/src/endWeek/economics/economics.js b/src/endWeek/economics/economics.js index 6a8a3fffcc4481b0169e21004c66d2f1cdb957a6..4245c2cca3cf9d5a9141b14719a8b453bc35d878 100644 --- a/src/endWeek/economics/economics.js +++ b/src/endWeek/economics/economics.js @@ -5,9 +5,13 @@ App.EndWeek.economics = function() { if (V.cash > -10000) { V.debtWarned = 0; } - if (V.mods.food.enabled && V.mods.food.market && - (V.mods.food.amount > App.Facilities.Farmyard.foodConsumption() || - V.cash > App.Facilities.Farmyard.foodConsumption() * V.mods.food.cost)) { + if ( + V.mods.food.enabled && V.mods.food.market && + ( + App.Facilities.Farmyard.foodAvailable() > App.Facilities.Farmyard.foodConsumption() || + V.cash > App.Facilities.Farmyard.foodBuyCost(App.Facilities.Farmyard.foodConsumption()) + ) + ) { V.mods.food.warned = false; } diff --git a/src/endWeek/economics/persBusiness.js b/src/endWeek/economics/persBusiness.js index 66d0f8dbdf48ef0ef137f60f832300bf2a972a40..615b6ce3c341947081d07a3d57e663b914ef9a76 100644 --- a/src/endWeek/economics/persBusiness.js +++ b/src/endWeek/economics/persBusiness.js @@ -49,9 +49,9 @@ App.EndWeek.personalBusiness = function() { } } if (V.mods.food.enabled && V.mods.food.market) { - if (V.mods.food.amount < App.Facilities.Farmyard.foodConsumption() && V.cash < (App.Facilities.Farmyard.foodConsumption() * V.mods.food.cost)) { + if (App.Facilities.Farmyard.foodAvailable() < App.Facilities.Farmyard.foodConsumption() && V.cash < App.Facilities.Farmyard.foodBuyCost(App.Facilities.Farmyard.foodConsumption())) { r.push(`<span class="red">WARNING: your arcology will starve in the coming week unless action is taken.</span>`); - if (V.mods.food.warned) { + if (V.mods.food.warned === true) { V.gameover = "starving citizens"; V.nextLink = "Gameover"; } else { @@ -256,12 +256,16 @@ App.EndWeek.personalBusiness = function() { } } if (V.mods.food.enabled && V.mods.food.market) { - if (V.mods.food.amount < App.Facilities.Farmyard.foodConsumption()) { - const foodCost = (App.Facilities.Farmyard.foodConsumption() - V.mods.food.amount) * V.mods.food.cost; - r.push(`You also spent ${cashFormat(Math.trunc(foodCost))} this week buying enough food to keep your citizens fed, as you promised.`); - cashX(forceNeg(foodCost), "food"); + if (V.mods.food.deficit > 0) { + const foodCost = App.Facilities.Farmyard.foodBuyCost(V.mods.food.deficit); + r.push(`You also spent <span class="red">${cashFormat(Math.trunc(foodCost))}</span> this week buying enough food to keep your citizens fed, as you promised.`); } - V.mods.food.amount -= App.Facilities.Farmyard.foodConsumption(); + // actual food purchasing and consumption happens in endWeek().food() + } + if (V.mods.food.overstocked > 0) { + const foodvalue = App.Facilities.Farmyard.foodSellValue(V.mods.food.overstocked); + r.push(`You did not have enough storage for all of the food you produced this week, so ${massFormat(V.mods.food.overstocked)} were sold for <span class="yellowgreen">${cashFormat(Math.trunc(foodvalue))}</span>`); + // The actual selling happens in endWeek().food() } App.Events.addParagraph(el, r); r = []; diff --git a/src/endWeek/endWeek.js b/src/endWeek/endWeek.js index 22f8e463847ec69e720a1378bf8c5e60f9914a5c..d2beca55eb40be52277ba4c508caedc14e5b722d 100644 --- a/src/endWeek/endWeek.js +++ b/src/endWeek/endWeek.js @@ -92,7 +92,7 @@ globalThis.endWeek = (function() { function saveWeekTotals() { V.cashLastWeek = V.cash; V.repLastWeek = V.rep; - V.mods.food.lastWeek = V.mods.food.amount; + V.mods.food.lastWeek = App.Facilities.Farmyard.foodAvailable(); } function weather() { @@ -175,11 +175,25 @@ globalThis.endWeek = (function() { } function food() { - if (V.mods.food.enabled && V.mods.food.market) { - const foodAmount = App.Facilities.Farmyard.foodProduction(); - V.mods.food.amount += foodAmount; - V.mods.food.produced += foodAmount; - V.mods.food.total += foodAmount; + // produce food + App.Facilities.Farmyard.foodAdd(App.Facilities.Farmyard.foodProduction()); + if (V.mods.food.enabled && V.eventResults.foodCrisis) { + // buy food if there is a deficit + const deficit = App.Facilities.Farmyard.foodConsumption() - App.Facilities.Farmyard.foodAvailable(); + V.mods.food.deficit = Math.max(0, deficit); + if (deficit > 0 && V.cash > App.Facilities.Farmyard.foodBuyCost(V.mods.food.deficit)) { + App.Facilities.Farmyard.foodBuy(deficit); + } + // consume food + App.Facilities.Farmyard.foodRemove(App.Facilities.Farmyard.foodConsumption()); + } else { + V.mods.food.deficit = 0; + } + // sell excess food if there is not enough storage + const overstocked = -App.Facilities.Farmyard.foodStorageAvailable(); + V.mods.food.overstocked = Math.max(0, overstocked); + if (overstocked > 0) { + App.Facilities.Farmyard.foodSell(overstocked); } } diff --git a/src/endWeek/nextWeek/nextWeek.js b/src/endWeek/nextWeek/nextWeek.js index 8803e85fa2420b72418a79fcee82ab7a01a9ea12..3c314e87883fb4223b2d8e485a522d3c0c35b634 100644 --- a/src/endWeek/nextWeek/nextWeek.js +++ b/src/endWeek/nextWeek/nextWeek.js @@ -147,16 +147,13 @@ App.EndWeek.nextWeek = function() { V.localEcon = 20; } - if (V.mods.food.enabled && V.mods.food.market) { - if (V.localEcon > 100) { - V.mods.food.cost = Math.max(5 / (1 + (Math.trunc(1000 - 100000 / V.localEcon) / 10) / 100), 3.125); - } else if (V.localEcon === 100) { - V.mods.food.cost = 5; - } else { - V.mods.food.cost = Math.min(5 * (1 + 1.5 * Math.sqrt(Math.trunc(100000 / V.localEcon - 1000) / 10) / 100), 6.5); - } + if (V.localEcon > 100) { + V.mods.food.cost = Math.max(5 / (1 + (Math.trunc(1000 - 100000 / V.localEcon) / 10) / 100), 3.125); + } else if (V.localEcon === 100) { + V.mods.food.cost = 5; + } else { + V.mods.food.cost = Math.min(5 * (1 + 1.5 * Math.sqrt(Math.trunc(100000 / V.localEcon - 1000) / 10) / 100), 6.5); } - V.mods.food.cost = Math.trunc(2500 / V.localEcon); V.drugsCost = Math.trunc(10000 / V.localEcon); if (V.dispensaryUpgrade) { V.drugsCost *= 0.5; diff --git a/src/endWeek/reports/farmyardReport.js b/src/endWeek/reports/farmyardReport.js index 762581e593347c4c705e8d36c4cd01949e419aff..c26932624228936cb48d7cd3f0ea8e369921d770 100644 --- a/src/endWeek/reports/farmyardReport.js +++ b/src/endWeek/reports/farmyardReport.js @@ -26,8 +26,6 @@ App.EndWeek.farmyardReport = function farmyardReport() { text.toParagraph(); - // V.mods.food.amount += food; // food amount is changed in endWeek().food() - if (slaves) { const intro = App.UI.DOM.appendNewElement("p", beforeFrag, null, ["indent"]); @@ -511,16 +509,16 @@ App.EndWeek.farmyardReport = function farmyardReport() { if (profits) { text.push(`makes you <span class="cash">${cashFormat(Math.trunc(profits))}</span>`); } - - if (V.mods.food.market) { - if (profits && food) { - text.push(`and`); - } - if (food) { - text.push(`produced <span class="chocolate"> ${massFormat(food)}</span> of food`); + if (profits && food) { + text.push(`and`); + } + text.push(`produced <span class="chocolate"> ${massFormat(food)}</span> of food this week.`); + if (food) { + if (V.mods.food.overstocked > 0) { + text.push(text.pop().replace(".", ",")); + text.push(`of which <span class="chocolate"> ${massFormat(V.mods.food.overstocked)}</span> were sold due to lack of storage space.`); } } - text.push(`this week.`); } return text.join(' '); @@ -530,7 +528,11 @@ App.EndWeek.farmyardReport = function farmyardReport() { let total = 0; for (const slave of slaves) { - total += farmhandProfit(slave); + total += farmhandProfit(slave); // TODO: these values do not match the values from App.Facilities.Farmyard.Stats() + } + + if (V.mods.food.overstocked > 0) { + total += App.Facilities.Farmyard.foodSellValue(V.mods.food.overstocked); } return total; diff --git a/src/endWeek/saWorkTheFarm.js b/src/endWeek/saWorkTheFarm.js index a23ae36411877f326d3692ebf09840ed0c047457..c1de9b888afdf7f8f4af29c74eefa305513ea45c 100644 --- a/src/endWeek/saWorkTheFarm.js +++ b/src/endWeek/saWorkTheFarm.js @@ -44,13 +44,10 @@ App.SlaveAssignment.workTheFarm = function(slave) { } function food() { - if (V.mods.food.market && V.farmyardShows < 2) { + if (V.farmyardShows < 2) { const fsGain = 0.0001 * foodAmount; FutureSocieties.DecorationBonus(V.farmyardDecoration, fsGain); - // V.mods.food.amount += foodAmount; // food amount is changed in endWeek().food() - // V.mods.food.produced += foodAmount; // food amount is changed in endWeek().food() - // V.mods.food.total += foodAmount; // food amount is changed in endWeek().food() incomeStats.food += foodAmount; if (V.farmyardShows !== 2) { diff --git a/src/events/intro/introSummary.js b/src/events/intro/introSummary.js index de9b46f9bb673387394234671e040545776df9d1..25c35a6783b7f56f272bae422d8d494bd0701661 100644 --- a/src/events/intro/introSummary.js +++ b/src/events/intro/introSummary.js @@ -328,6 +328,10 @@ App.Intro.summary = function() { .addValue("Enabled", 1).on().addValue("Disabled", 0).off(); options.addComment(`This mod allows you to start your own lategame bioengineering project, focused around creating functional catgirls for recreational purposes. The ultimate dream of any anime-addicted billionaire. After enabling, bioengineering must be manually activated from the genelab through expensive upgrades to see any of the mod's content.`); + options.addOption("The food mod is", "enabled", V.mods.food) + .addValue("Enabled", true).on().addValue("Disabled", false).off(); + options.addComment("This mod will enable food supply and demand. This mod can be activated/deactivated at any time."); + el.append(options.render()); return el; } diff --git a/src/facilities/farmyard/farmyard.js b/src/facilities/farmyard/farmyard.js index 0cd469ec6666574a686d7fcd67e52fdcd757c572..3f9f911ee25d9362af9b8166a3679a28f32ea1ed 100644 --- a/src/facilities/farmyard/farmyard.js +++ b/src/facilities/farmyard/farmyard.js @@ -31,7 +31,8 @@ App.Facilities.Farmyard.farmyard = class Farmyard extends App.Facilities.Facilit fertilizer: 0, hydroponics: 0, machinery: 0, - seeds: 0 + seeds: 0, + foodStorage: 150, }; V.animals = { @@ -734,10 +735,29 @@ App.Facilities.Farmyard.farmyard = class Farmyard extends App.Facilities.Facilit } } + /** @returns {DocumentFragment} */ + get foodStorage() { + const frag = new DocumentFragment(); + + App.UI.DOM.appendNewElement("h2", frag, `Food Storage`); + + frag.append( + App.UI.foodMarket.foodStorageDescription(), + App.UI.foodMarket.foodStorageUpgrades(), + ); + if (V.mods.food.enabled && V.mods.food.market) { + frag.append(App.UI.foodMarket.buyLinks()); + } + frag.append(App.UI.foodMarket.sellLinks()); + + return frag; + } + /** @returns {DocumentFragment[]} */ get customNodes() { return [ this.animals, + this.foodStorage, ]; } }; diff --git a/src/facilities/farmyard/food/food.js b/src/facilities/farmyard/food/food.js index a6cef721233d7196a88f3a5071cc8838e1362995..970846d5e3f300893ff1e5d1b8ac3a75bb66c5d8 100644 --- a/src/facilities/farmyard/food/food.js +++ b/src/facilities/farmyard/food/food.js @@ -175,3 +175,85 @@ App.Facilities.Farmyard.foodConsumption = function(target = 'both') { return citizenConsumption() + slaveConsumption(); }; + +/** + * @returns {number} The amount of food currently available + */ +App.Facilities.Farmyard.foodAvailable = () => { + return V.mods.food.amount; +}; + +/** + * @returns {number} The amount of storage currently available for food + */ +App.Facilities.Farmyard.foodStorageAvailable = () => { + return (V.farmyardUpgrades.foodStorage * 1000) - V.mods.food.amount; +}; + +/** + * @param {number} foodAmount The amount of food to get the sell value of + * @returns {number} The value of the food if sold in the current economy + */ +App.Facilities.Farmyard.foodSellValue = (foodAmount) => { + if (V.mods.food.enabled === true) { + return Math.ceil((V.mods.food.cost * foodAmount) * 0.7); + } else { + return Math.ceil((V.mods.food.cost * foodAmount) * 0.1); // food sales for a lot less for balancing reasons + } +}; + +/** + * @param {number} foodAmount The amount of food to sell. The arcology must have at least this much food + */ +App.Facilities.Farmyard.foodSell = (foodAmount) => { + if (foodAmount > V.mods.food.amount) { + throw new Error(`App.Facilities.Farmyard.foodSell() was given a food amount greater than the available amount of food.`); + } + cashX(Math.trunc(App.Facilities.Farmyard.foodSellValue(foodAmount)), "food"); + V.mods.food.amount -= foodAmount; +}; + +/** + * @returns {number} The maximum amount of food that can currently be bought + */ +App.Facilities.Farmyard.foodMaxBuyable = () => { + let amount = Math.trunc(V.cash / V.mods.food.cost); + amount = Math.min(amount, App.Facilities.Farmyard.foodStorageAvailable()); + return amount; +}; + +/** + * @param {number} foodAmount The amount of food to get the purchase value of + * @returns {number} The cost of the food if bought in the current economy + */ +App.Facilities.Farmyard.foodBuyCost = (foodAmount) => { + return Math.ceil(V.mods.food.cost * foodAmount); +}; + + +/** + * @param {number} foodAmount The amount of food to add to the total amount. This should not be used for purchased food (use `foodBuy()` instead). + */ +App.Facilities.Farmyard.foodAdd = (foodAmount) => { + V.mods.food.amount += foodAmount; + V.mods.food.total += foodAmount; +}; + +/** + * @param {number} foodAmount The amount of food to remove from the total amount. This should not be used for sold food (use `foodSell()` instead). + */ +App.Facilities.Farmyard.foodRemove = (foodAmount) => { + V.mods.food.amount -= foodAmount; +}; + +/** + * @param {number} foodAmount The amount of food to buy. The arcology must have at least this much food storage available + */ +App.Facilities.Farmyard.foodBuy = (foodAmount) => { + if (foodAmount > App.Facilities.Farmyard.foodStorageAvailable()) { + throw new Error(`App.Facilities.Farmyard.foodBuy() was given an amount greater than App.Facilities.Farmyard.foodStorageAvailable()`); + } + cashX(forceNeg(App.Facilities.Farmyard.foodBuyCost(foodAmount)), "food"); + V.mods.food.amount += foodAmount; + V.mods.food.total += foodAmount; +}; diff --git a/src/facilities/farmyard/food/foodMarket.js b/src/facilities/farmyard/food/foodMarket.js index 6fcbd010a7d7a2c4a0a5f7dce57c07e889502fb1..1c433b893280a4717843066ba0d3e8756abb3830 100644 --- a/src/facilities/farmyard/food/foodMarket.js +++ b/src/facilities/farmyard/food/foodMarket.js @@ -1,105 +1,173 @@ -App.UI.foodMarket = function() { - const frag = new DocumentFragment(); - const quantities = new Set([1, 10, 100, 1000, 10000, 100000]); - - const maxFood = Math.trunc(V.cash / V.mods.food.cost); - - frag.append( - overview(), - buy(), - // sell(), // TODO: temporarily disabled for balancing - remove(), - ); - - return frag; +App.UI.foodMarket = {}; - function overview() { - const div = document.createElement("div"); - const text = new SpacedTextAccumulator(div); - - const consumption = App.Facilities.Farmyard.foodConsumption(); - const foodValue = Math.trunc(V.mods.food.amount * V.mods.food.cost); - const citizens = V.lowerClass + V.middleClass + V.upperClass + V.topClass; - - if (V.useTabs === 0) { - App.UI.DOM.appendNewElement("h2", div, "The Food Market"); - } - - text.push(`The food market has <span class="food">${massFormat(V.mods.food.amount)}</span> in storage, valued at a total of ${cashFormat(foodValue)}.`); - - if (V.mods.food.amount > consumption) { - text.push(`This is enough to provide for ${numberWithPluralOne(V.slaves.length, `slave`)} and ${numberWithPluralOne(citizens, `citizen`)} for about ${years(Math.trunc(V.mods.food.amount / consumption))}.`); - } else if (V.mods.food.amount < consumption) { - text.push(`You will need an additional ${massFormat(consumption - V.mods.food.amount)} to provide for ${numberWithPluralOne(V.slaves.length, `slave`)} and ${numberWithPluralOne(citizens, `citizen`)} during the upcoming week.`); - } +App.UI.foodMarket.quantities = new Set([1, 10, 100, 1000, 10000, 100000]); - text.toChildren(); +App.UI.foodMarket.buyLinks = () => { + const div = App.UI.DOM.makeElement("div", null, ['indent']); + const links = []; + const maxFood = App.Facilities.Farmyard.foodMaxBuyable(); + if (maxFood === 0) { + div.append(`You have no storage space left for food.`); return div; } - function buy() { - const div = App.UI.DOM.makeElement("div", null, ['indent']); - const links = []; + div.append(`Buy `); - div.append(`Buy `); - - for (const q of quantities) { + for (const q of App.UI.foodMarket.quantities) { + if (App.Facilities.Farmyard.foodStorageAvailable() >= q) { links.push(App.UI.DOM.link( massFormat(q), () => { - cashX(forceNeg(V.mods.food.cost * q), "farmyard"); - V.mods.food.amount += q; + App.Facilities.Farmyard.foodBuy(q); App.UI.reload(); - } + }, + undefined, + undefined, + cashFormat(Math.trunc(App.Facilities.Farmyard.foodBuyCost(q))), )); } + } + if (!App.UI.foodMarket.quantities.has(maxFood)) { links.push(App.UI.DOM.link( - "max", + massFormat(maxFood), () => { - cashX(forceNeg(maxFood * V.mods.food.cost), "farmyard"); - V.mods.food.amount += maxFood; + App.Facilities.Farmyard.foodBuy(maxFood); App.UI.reload(); - } + }, + undefined, + undefined, + cashFormat(Math.trunc(App.Facilities.Farmyard.foodBuyCost(maxFood))), )); + } + + div.append(App.UI.DOM.generateLinksStrip(links)); + div.append(` of food.`); + + return div; +}; - div.append(App.UI.DOM.generateLinksStrip(links)); +App.UI.foodMarket.sellLinks = () => { + const div = App.UI.DOM.makeElement("div", null, ['indent']); + const links = []; + if (App.Facilities.Farmyard.foodAvailable() === 0) { + div.append(`You have no food left to sell.`); return div; } - function sell() { - const div = App.UI.DOM.makeElement("div", null, ['indent']); - const links = []; + div.append(`Sell `); - div.append(`Sell `); - - if (V.mods.food.amount > 0) { - for (const q of quantities) { + if (App.Facilities.Farmyard.foodAvailable() > 0) { + for (const q of App.UI.foodMarket.quantities) { + if (App.Facilities.Farmyard.foodAvailable() >= q) { links.push(App.UI.DOM.link( massFormat(q), () => { - cashX(V.mods.food.cost * q, "farmyard"); - V.mods.food.amount -= q; + App.Facilities.Farmyard.foodSell(q); App.UI.reload(); - } + }, + undefined, + undefined, + cashFormat(Math.trunc(App.Facilities.Farmyard.foodSellValue(q))), )); } + } + if (!App.UI.foodMarket.quantities.has(App.Facilities.Farmyard.foodAvailable())) { links.push(App.UI.DOM.link( - "max", + massFormat(App.Facilities.Farmyard.foodAvailable()), () => { - cashX((V.mods.food.cost * V.mods.food.amount), "farmyard"); - V.mods.food.amount = 0; + App.Facilities.Farmyard.foodSell(App.Facilities.Farmyard.foodAvailable()); App.UI.reload(); - } + }, + undefined, + undefined, + cashFormat(Math.trunc(App.Facilities.Farmyard.foodSellValue(App.Facilities.Farmyard.foodAvailable()))), )); } + } - div.append(App.UI.DOM.generateLinksStrip(links)); + div.append(App.UI.DOM.generateLinksStrip(links)); + div.append(` of food.`); - return div; + return div; +}; + + +App.UI.foodMarket.foodStorageDescription = () => { + const div = document.createElement("div"); + const text = new SpacedTextAccumulator(div); + + const consumption = App.Facilities.Farmyard.foodConsumption(); + const foodAvailable = App.Facilities.Farmyard.foodAvailable(); + const foodValue = App.Facilities.Farmyard.foodSellValue(foodAvailable); + const citizens = V.lowerClass + V.middleClass + V.upperClass + V.topClass; + + text.push(`You have <span class="food">${massFormat(foodAvailable)}</span> of food stored in your storehouses, valued at a total of ${cashFormat(foodValue)}.`); + + if (V.mods.food.enabled && V.eventResults.foodCrisis) { + if (foodAvailable > consumption) { + text.push(`This is enough to provide for ${numberWithPluralOne(V.slaves.length, `slave`)} and ${numberWithPluralOne(citizens, `citizen`)} for about ${years(Math.trunc(foodAvailable / consumption))}.`); + } else if (foodAvailable < consumption) { + text.push(`You will need an additional ${massFormat(consumption - foodAvailable)} to provide for ${numberWithPluralOne(V.slaves.length, `slave`)} and ${numberWithPluralOne(citizens, `citizen`)} during the upcoming week.`); + } + } + + text.toChildren(); + + return div; +}; + +App.UI.foodMarket.foodStorageUpgrades = () => { + const div = document.createElement("div"); + const text = new SpacedTextAccumulator(div); + text.push(`You have storage for ${massFormat(V.farmyardUpgrades.foodStorage * 1000)} of food. Any food produced that you don't have storage for will be sold immediately.`); + text.toParagraph(); + text.push(`Buy an extra`); + const costPerTon = Math.trunc((100 + (V.farmyardUpgrades.foodStorage / 10))); + const links = []; + + for (const q of [1, 10, 100, 1000]) { + if ((costPerTon * q) < V.cash) { + links.push(App.UI.DOM.link( + massFormat(q * 1000), + () => { + cashX(forceNeg(costPerTon * q), "farmyard"); + V.farmyardUpgrades.foodStorage += q; + App.UI.reload(); + }, + undefined, + undefined, + cashFormat(costPerTon * q), + )); + } } + text.push(App.UI.DOM.generateLinksStrip(links)); + + text.push(`of food storage.`); + + text.toChildren(); + return div; +}; + +App.UI.foodMarket.main = () => { + const frag = new DocumentFragment(); + + if (V.useTabs === 0) { + frag.append(App.UI.DOM.makeElement("h2", "The Food Market")); + } + + frag.append( + App.UI.foodMarket.foodStorageDescription(), + App.UI.foodMarket.foodStorageUpgrades(), + App.UI.foodMarket.buyLinks(), + App.UI.foodMarket.sellLinks(), + remove(), + ); + + return frag; + function remove() { const div = document.createElement("div"); diff --git a/src/facilities/farmyard/food/foodReport.js b/src/facilities/farmyard/food/foodReport.js index cfea24055a188b25b5036ead0aea5ed7e220596d..495648347d0791fbbe95148d8ad894e2fb40c0e8 100644 --- a/src/facilities/farmyard/food/foodReport.js +++ b/src/facilities/farmyard/food/foodReport.js @@ -18,6 +18,11 @@ App.UI.foodReport = function() { text.push(`${V.arcologies[0].name} produced ${massFormat(production)} of food this week.`); + if (V.mods.food.overstocked > 0) { + text.push(text.pop().replace(".", ",")); + text.push(`of which ${massFormat(V.mods.food.overstocked)} were sold due to lack of storage space.`); + } + if (slaveAmount > 0) { text.push(`${V.farmMenials ? capFirstChar(massFormat(slaveAmount)) : `All of it`} was produced by your ${num(farmhands)} farmhands`); } @@ -31,20 +36,23 @@ App.UI.foodReport = function() { } function consumption() { + if (V.mods.food.enabled === false || V.mods.food.market === false) { + return ""; + } const text = []; const production = App.Facilities.Farmyard.foodProduction(); const consumption = App.Facilities.Farmyard.foodConsumption(); const deficit = Math.abs(consumption - production); - const cost = deficit * V.mods.food.cost; - const storage = V.mods.food.amount; + const cost = App.Facilities.Farmyard.foodBuyCost(deficit); + const storage = App.Facilities.Farmyard.foodAvailable(); if (production > consumption) { - text.push(`${capFirstChar(massFormat(consumption))} of it was consumed, with a spare ${massFormat(production - consumption)} moved into long-term storage.`); + text.push(`${capFirstChar(massFormat(consumption))} of it was consumed, with a spare ${massFormat(production - (consumption + V.mods.food.overstocked))} moved into long-term storage.`); } else { if (storage > deficit) { - text.push(`Unfortunately, this wasn't enough to cover needs of your hungry arcology, and ${massFormat(deficit)} had to be brought up from storage.`); + text.push(`Unfortunately, this wasn't enough to cover the needs of your hungry arcology, and ${massFormat(deficit)} had to be brought out of storage.`); } else if (V.cash > cost) { - text.push(`Unfortunately, this wasn't enough to cover needs of your hungry arcology, and because you didn't have enough food in storage, you has to purchase and additional ${massFormat(deficit - storage)} for ${cashFormat()}.`); + text.push(`Unfortunately, this wasn't enough to cover the needs of your hungry arcology, and because you didn't have enough food in storage, you had to purchase an additional ${massFormat(deficit - storage)} for ${cashFormat(App.Facilities.Farmyard.foodBuyCost(deficit - storage))}.`); } } diff --git a/src/facilities/statistics.js b/src/facilities/statistics.js index beb40f78761ffe197311e1656ac1d2211dd94423..62a810a1215f9eb43dd7d259013bd11797beacba 100644 --- a/src/facilities/statistics.js +++ b/src/facilities/statistics.js @@ -500,22 +500,15 @@ App.Facilities.Farmyard.Stats = function(showDetails) { } const H = new App.Facilities.StatsHelper(["Revenue", "Expenses", "Food [kg]", "Net Income", "Rep. Change"]); - H.addValueRow("Total farmhand income", [ + H.addValueRow("Farmhands", [ H.makeValueCell(b.whoreIncome, {type: "cash"}), - H.makeEmptyCell(), - H.makeEmptyCell(), - H.makeValueCell(b.whoreIncome, {type: "cash"}), - H.makeEmptyCell(), - ]); - H.addValueRow("Total farmhand living costs", [ - H.makeEmptyCell(), H.makeValueCell(b.whoreCosts, {forceNeg: true, type: "cash"}), H.makeEmptyCell(), - H.makeValueCell(b.whoreCosts, {forceNeg: true, showSign: true, type: "cash"}), - H.makeEmptyCell() + H.makeValueCell(b.whoreIncome - b.whoreCosts, {type: "cash"}), + H.makeEmptyCell(), ]); if ( App.Entity.facilities.farmyard.employees().length !== 0) { - H.addValueRow("food produced by farmhands", [ + H.addValueRow("Food produced by farmhands", [ H.makeEmptyCell(), H.makeEmptyCell(), H.makeValueCell(b.slaveFoodCounts, {type: "food"}), @@ -524,7 +517,7 @@ App.Facilities.Farmyard.Stats = function(showDetails) { ]); } if (V.farmMenials) { - H.addValueRow("food produced by menials", [ + H.addValueRow("Food produced by menials", [ H.makeEmptyCell(), H.makeEmptyCell(), H.makeValueCell(b.menialFoodCounts, {type: "food"}), @@ -539,6 +532,15 @@ App.Facilities.Farmyard.Stats = function(showDetails) { H.makeValueCell(b.menialFoodCounts + b.slaveFoodCounts, {type: "food"}), H.makeEmptyCell() ]); + if (V.mods.food.overstocked > 0) { + H.addValueRow("Total food sold", [ + H.makeEmptyCell(), + H.makeValueCell(App.Facilities.Farmyard.foodSellValue(V.mods.food.overstocked), {type: "cash"}), + H.makeValueCell(V.mods.food.overstocked, {type: "food", forceNeg: true}), + H.makeValueCell(App.Facilities.Farmyard.foodSellValue(V.mods.food.overstocked), {type: "cash"}), + H.makeEmptyCell() + ]); + } if (showDetails) { H.startSlaveStatsSection("Farmhand details", ["Farmhand", "Revenue", "Expenses", "Food [kg]", "Net Income", "Rep. Change"]); diff --git a/src/js/economyJS.js b/src/js/economyJS.js index 9cc92d21270496dc8711716173829159c3282442..d031d0f3960e426d45fc3d7814ba42f440b42bc7 100644 --- a/src/js/economyJS.js +++ b/src/js/economyJS.js @@ -815,7 +815,7 @@ globalThis.calculateCosts = (function() { function getPCFoodCosts() { const slimnessFoodMod = V.arcologies[0].FSSlimnessEnthusiastFoodLaw === 1 && !canEatFood(V.PC) ? 1.15 : 1; - const foodCost = V.mods.food.cost * slimnessFoodMod; + const foodCost = App.Facilities.Farmyard.foodBuyCost(slimnessFoodMod); let costs = 0; // Well this ought to be a mess. // Basic food costs @@ -1251,7 +1251,7 @@ globalThis.getSlaveCostArray = function(s) { let t = ""; const slimnessFoodMod = V.arcologies[0].FSSlimnessEnthusiastFoodLaw === 1 ? 1.15 : 1; const rulesCost = V.rulesCost; - const foodCost = V.mods.food.cost * slimnessFoodMod; + const foodCost = App.Facilities.Farmyard.foodBuyCost(slimnessFoodMod); const drugsCost = V.drugsCost; retval.push({text: "Living Expenses", value: getSlaveLivingExpenses(s)}); diff --git a/src/js/utilsMisc.js b/src/js/utilsMisc.js index f03babf0d0e6f84e0cdb8f0a8019ec2339a70af0..c4f71ff87f30770c5947a2144a00f616773101fd 100644 --- a/src/js/utilsMisc.js +++ b/src/js/utilsMisc.js @@ -248,7 +248,7 @@ App.Utils.totalNetWorth = function() { .filter(f => f.established) .reduce((acc, cur) => acc + cur.value, 0); - total += Math.trunc(V.mods.food.amount * V.mods.food.cost); + total += Math.trunc(App.Facilities.Farmyard.foodSellValue(App.Facilities.Farmyard.foodAvailable())); total += App.Mods.SF.totalNetWorth(); total -= App.Mods.SecExp.upkeep.cost();