From 1edbc72b2db78c87731011373fc0d4681370b165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20H=C3=B6llerich?= Date: Tue, 12 Apr 2022 18:49:23 +0200 Subject: [PATCH] Game Update 14 * Hacienda: quarters, farms, fertilizer, policy * Include items that change workforce * Section for notes * Various bug fixes --- AnnoCalculator.js | 302 +- README.md | 2 +- i18n.js | 34 +- index.html | 350 +- params.js | 54317 +++++++++++++++++++++++--------------------- style.css | 11 + 6 files changed, 28399 insertions(+), 26617 deletions(-) diff --git a/AnnoCalculator.js b/AnnoCalculator.js index 5775e00..174c6f7 100644 --- a/AnnoCalculator.js +++ b/AnnoCalculator.js @@ -1,4 +1,4 @@ -let versionCalculator = "v7.4"; +let versionCalculator = "v8.0"; let ACCURACY = 0.01; let EPSILON = 0.0000001; let ALL_ISLANDS = "All Islands"; @@ -110,7 +110,7 @@ class Option extends NamedElement { } class Island { - constructor(params, localStorage, session) { + constructor(params, localStorage, isNew, session) { if (localStorage instanceof Storage) { this.name = ko.observable(localStorage.key); this.name.subscribe(name => this.storage.updateKey(name)); @@ -120,7 +120,6 @@ class Island { this.isAllIslands = function () { return true; }; } this.storage = localStorage; - var isNew = !localStorage.length; this.session = session || this.storage.getItem("session"); this.session = this.session instanceof Session ? this.session : view.assetsMap.get(this.session); @@ -140,7 +139,7 @@ class Island { }); // procedures to persist inputs - var persistBool, persistInt, persistFloat; + var persistBool, persistInt, persistFloat, persistString; if (localStorage) { persistBool = (obj, attributeName, storageName) => { @@ -192,8 +191,21 @@ class Island { }); } } + + persistString = (obj, attributeName, storageName) => { + + var attr = obj[attributeName]; + if (attr) { + let id = storageName ? storageName : (obj.guid + "." + attributeName); + if (localStorage.getItem(id) != null) + attr(localStorage.getItem(id)); + + attr.subscribe(val => localStorage.setItem(id, val)); + } + } + } else { - persistBool = persistFloat = persistInt = () => { }; + persistBool = persistFloat = persistInt = persistString = () => { }; } // objects @@ -267,6 +279,7 @@ class Island { // set moduleChecked before boost, otherwise boost would be increased persistBool(f, "moduleChecked", `${f.guid}.module.checked`); + persistBool(f, "fertilizerModuleChecked", `${f.guid}.fertilizerModule.checked`); persistBool(f, "palaceBuffChecked", `${f.guid}.palaceBuff.checked`); persistInt(f, "percentBoost"); } @@ -342,13 +355,24 @@ class Island { }); // must be set after items so that extraDemand is correctly handled - this.consumers.forEach(f => f.referenceProducts(assetsMap)); + this.consumers.forEach(f => { + f.createWorkforceDemand(assetsMap); + f.referenceProducts(assetsMap); + }); // setup demands induced by modules for (let factory of params.factories) { let f = assetsMap.get(factory.guid); - if (f && f.module) - f.moduleDemand = new Demand({ guid: f.module.getInputs()[0].Product, region: f.region }, assetsMap); + + if (!f) + continue; + + for (var m of ["module", "fertilizerModule"]) { + var module = f[m]; + + if (module) + f[m + "Demand"] = new Demand({ guid: module.getInputs()[0].Product, region: f.region }, assetsMap); + } } @@ -375,6 +399,10 @@ class Island { this.populationLevels.push(l); } + for (let b of this.residenceBuildings) + if (typeof b.populationLevel === "number") + b.populationLevel = assetsMap.get(parseInt(b.populationLevel)); + for (let l of this.populationLevels) { l.initBans(assetsMap); @@ -385,11 +413,12 @@ class Island { persistInt(l, "amount"); persistBool(l, "fixLimitPerHouse"); persistBool(l, "fixAmountPerHouse"); + persistString(l, "notes"); for (let n of l.needs) { persistBool(n, "checked", `${l.guid}[${n.guid}].checked`); persistFloat(n, "percentBoost", `${l.guid}[${n.guid}].percentBoost`); - + persistString(n, "notes", `${l.guid}[${n.guid}].notes`); } for (let n of l.buildingNeeds) { @@ -466,6 +495,7 @@ class Island { for (let f of this.consumers) { persistInt(f, "existingBuildings"); + persistString(f, "notes"); if (f.workforceDemand) persistInt(f.workforceDemand, "percentBoost", `${f.guid}.workforce.percentBoost`); } @@ -534,8 +564,11 @@ class Island { if (a instanceof Factory) { if (a.clipped) a.clipped(false); - if (a.moduleChecked) - a.moduleChecked(false); + for (var m of ["module", "fertilizerModule"]) { + var checked = a[m + "Checked"]; + if (checked) + checked(false); + } if (a.palaceBuffChecked) a.palaceBuffChecked(false); a.percentBoost(100); @@ -556,13 +589,17 @@ class Island { a.limit(0); a.fixAmountPerHouse(true); a.fixLimitPerHouse(true); + for (n of a.needs) + if (n.notes) + n.notes(""); } if (a instanceof Item) { a.checked(false); for (var i of a.equipments) i.checked(false); } - + if (a.notes) + a.notes(""); }); setDefaultFixedFactories(this.assetsMap); @@ -602,16 +639,12 @@ class Consumer extends NamedElement { this.outputAmount = ko.computed(() => this.amount()); - this.workforceDemand = this.getWorkforceDemand(assetsMap); - if (this.workforceDemand) { - this.existingBuildings.subscribe(val => this.workforceDemand.updateAmount(Math.max(val, this.buildings()))); - this.buildings.subscribe(val => this.workforceDemand.updateAmount(Math.max(val, this.buildings()))); - } - this.tradeList = new TradeList(island, this); if (params.tradeContracts && (!this.island.region || this.island.region.guid == 5000000)) this.contractList = new ContractList(island, this); + + this.notes = ko.observable(""); } getInputs() { @@ -624,18 +657,26 @@ class Consumer extends NamedElement { } - getWorkforceDemand(assetsMap) { + createWorkforceDemand(assetsMap) { for (let m of this.maintenances || []) { let a = assetsMap.get(m.Product); - if (a instanceof Workforce) - return new WorkforceDemand($.extend({ factory: this, workforce: a }, m), assetsMap); + if (a instanceof Workforce) { + let items = this.items.filter(item => item.replacingWorkforce); + if (items.length) + this.workforceDemand = new WorkforceDemandSwitch($.extend({ factory: this, workforce: a }, m), items[0], assetsMap); + else + this.workforceDemand = new WorkforceDemand($.extend({ factory: this, workforce: a }, m), assetsMap); + + this.existingBuildings.subscribe(val => this.workforceDemand.updateAmount(Math.max(val, this.buildings()))); + this.buildings.subscribe(val => this.workforceDemand.updateAmount(Math.max(val, this.buildings()))); + } } return null; } getRegionExtendedName() { if (!this.region || !this.product || this.product.factories.length <= 1) - return this.name; + return this.name(); return `${this.name()} (${this.region.name()})`; } @@ -750,17 +791,37 @@ class Factory extends Consumer { if (this.module) { this.module = assetsMap.get(this.module); this.moduleChecked = ko.observable(false); + var workforceUpgrade = this.module.workforceAmountUpgrade ? this.module.workforceAmountUpgrade.Value : 0; this.moduleChecked.subscribe(checked => { - if (checked) + if (checked) { this.percentBoost(parseInt(this.percentBoost()) + this.module.productivityUpgrade); - else { + if (this.workforceDemand) + this.workforceDemand.percentBoost(this.workforceDemand.percentBoost() + workforceUpgrade); + } else { var val = Math.max(1, parseInt(this.percentBoost()) - this.module.productivityUpgrade); this.percentBoost(val); + + if (this.workforceDemand) + this.workforceDemand.percentBoost(Math.max(0, this.workforceDemand.percentBoost() - workforceUpgrade)); } }); //moduleDemand created in island constructor after referencing products } + if (this.fertilizerModule) { + this.fertilizerModule = assetsMap.get(this.fertilizerModule); + this.fertilizerModuleChecked = ko.observable(false); + this.fertilizerModuleChecked.subscribe(checked => { + if (checked) { + this.percentBoost(parseInt(this.percentBoost()) + this.fertilizerModule.productivityUpgrade); + } else { + var val = Math.max(1, parseInt(this.percentBoost()) - this.fertilizerModule.productivityUpgrade); + this.percentBoost(val); + } + }); + //fertilizerModuleDemand created in island constructor after referencing products + } + if (config.palaceBuff) { this.palaceBuff = assetsMap.get(config.palaceBuff); this.palaceBuffChecked = ko.observable(false); @@ -773,8 +834,14 @@ class Factory extends Consumer { this.extraGoodFactor = ko.computed(() => { var factor = 1; - if (this.module && this.moduleChecked() && this.module.additionalOutputCycle) - factor += 1 / this.module.additionalOutputCycle; + + for (var m of ["module", "fertilizerModule"]) { + var module = this[m]; + var checked = this[m + "Checked"]; + + if (module && checked() && module.additionalOutputCycle) + factor += 1 / module.additionalOutputCycle; + } if (this.palaceBuff && this.palaceBuffChecked()) factor += (this.clipped && this.clipped() && this.palaceBuff.guid !== 191141 /* bronce age gives no benefit from boosting */ ? 2 : 1) / this.palaceBuff.additionalOutputCycle; @@ -801,11 +868,18 @@ class Factory extends Consumer { this.buildings = ko.computed(() => { var buildings = this.requiredOutputAmount() / this.tpmin / this.boost(); - if (this.moduleDemand) - if (this.moduleChecked()) - this.moduleDemand.updateAmount(Math.max(Math.ceil(buildings), this.existingBuildings()) * this.module.tpmin); + for (var m of ["module", "fertilizerModule"]) { + var module = this[m]; + var checked = this[m + "Checked"]; + var demand = this[m + "Demand"]; + + if (demand) + if (checked()) + demand.updateAmount(Math.max(Math.ceil(buildings), this.existingBuildings()) * module.tpmin); else - this.moduleDemand.updateAmount(0); + demand.updateAmount(0); + } + return buildings; }); @@ -994,19 +1068,23 @@ class Factory extends Consumer { for (var i = 0; i < this.items.length; i++) other.items[i].checked(this.items[i].checked()); - other.percentBoost(this.percentBoost()); - if (this.clipped) other.clipped(this.clipped()); - if (this.moduleChecked) - other.moduleChecked(this.moduleChecked()); + for (var m of ["module", "fertilizerModule"]) { + var checked = this[m + "Checked"]; + if (checked()) + other[m + "Checked"](checked()); + } if (this.palaceBuffChecked) other.palaceBuffChecked(this.palaceBuffChecked()); if (this.workforceDemand && this.workforceDemand.percentBoost) other.workforceDemand.percentBoost(this.workforceDemand.percentBoost()); + + // set boost after modules + other.percentBoost(this.percentBoost()); } } } @@ -1020,6 +1098,8 @@ class Product extends NamedElement { this.factories = this.producers.map(p => assetsMap.get(p)).filter(p => !!p); this.fixedFactory = ko.observable(null); + if (this.mainFactory) + this.mainFactory = assetsMap.get(this.mainFactory); if (this.producers && this.factories.length) { this.amount = ko.computed(() => this.factories.map(f => f.amount()).reduce((a, b) => a + b)); @@ -1041,8 +1121,13 @@ class PublicBuildingNeed extends Option { this.product = assetsMap.get(this.guid); this.initBans = PopulationNeed.prototype.initBans; + + if (this.requiredToBeBuilding) { + this.residence = assetsMap.get(this.requiredToBeBuilding); + this.hidden = ko.pureComputed(() => !this.residence.existingBuildings()); } } +} class StoreNeed extends PublicBuildingNeed { constructor(config, floors, assetsMap) { @@ -1124,10 +1209,9 @@ class Demand extends NamedElement { } updateFixedProductFactory(f) { - if (f == null) { - if (this.consumer || this.region) { // find factory in the same region as consumer + if (f == null && (this.consumer || this.region)) { // find factory in the same region as consumer let region = this.region || this.consumer.factory().region; - if (region) { + if (region && !(this.product.mainFactory && this.product.mainFactory.region === region)) { for (let fac of this.product.factories) { if (fac.region === region) { f = fac; @@ -1136,10 +1220,9 @@ class Demand extends NamedElement { } } } - } if (f == null) // region based approach not successful - f = this.product.factories[0]; + f = this.product.mainFactory || this.product.factories[0]; if (f != this.factory()) { if (this.factory()) @@ -1215,8 +1298,12 @@ class FactoryDemandSwitch { this.amount = amount; var factory = this.consumer.factory(); - if (factory.module && factory.moduleChecked() && factory.module.additionalOutputCycle) - amount *= factory.module.additionalOutputCycle / (factory.module.additionalOutputCycle + 1); + for (var m of ["module", "fertilizerModule"]) { + var module = factory[m]; + var checked = factory[m + "Checked"]; + if (module && checked() && module.additionalOutputCycle) + amount *= module.additionalOutputCycle / (module.additionalOutputCycle + 1); + } if (factory != this.factory) { for (var d of this.demandsMap.get(this.factory)) { @@ -1274,6 +1361,8 @@ class PopulationNeed extends Need { this.checked = ko.observable(true); this.optionalAmount = ko.observable(0); + + this.notes = ko.observable(""); } initBans(level, assetsMap) { @@ -1283,7 +1372,7 @@ class PopulationNeed extends Need { if (!config || !view.settings.needUnlockConditions.checked()) return false; - if (level.floorsSummedExistingBuildings && level.hasSkyscrapers()) + if (level.skyscraperLevels && level.hasSkyscrapers()) return false; if (config.populationLevel != level.guid) { @@ -1354,15 +1443,25 @@ class PopulationNeed extends Need { class PopulationLevelNeed extends PopulationNeed { constructor(config, level, assetsMap) { super(config, assetsMap); - level.limit.subscribe(limit => this.updateAmount(limit - (level.skylineTower ? level.skylineTower.existingBuildings() * level.skylineTower.freeResidents : 0))); + + this.region = level.region; + + this.freeResidents = 0; + if (level.specialResidence && level.specialResidence.freeResidents) { + this.freeResidents = level.specialResidence.freeResidents; + level.limit.subscribe(limit => this.updateAmount(limit - level.specialResidence.existingBuildings() * this.freeResidents)); + } else { + level.limit.subscribe(limit => this.updateAmount(limit)); } } +} class SkyscraperPopulationNeed extends PopulationNeed { constructor(config, floors, assetsMap) { super(config, assetsMap); this.floors = typeof floors[0] === 'number' ? floors.map(f => assetsMap.get(f)) : floors; + this.region = this.floors[0].region this.floorSubscription = ko.computed(() => { var amount = 0; for (var i = this.requiredFloorLevel - 1; i < floors.length; i++) @@ -1381,6 +1480,23 @@ class SkyscraperPopulationNeed extends PopulationNeed { } } +class SpecialResidenceNeed extends PopulationNeed { + constructor(config, assetsMap) { + super(config, assetsMap); + + this.residence = assetsMap.get(this.requiredToBeBuilding); + this.region = this.residence.region; + + this.residenceSubscription = ko.computed(() => { + this.updateAmount(this.residence.limit()); + }); + + this.hidden = ko.computed(() => { + return !this.residence.existingBuildings(); + }); + } +} + class BuildingMaterialsNeed extends Need { constructor(config, assetsMap) { super(config, assetsMap); @@ -1479,6 +1595,7 @@ class PopulationLevel extends NamedElement { this.region = assetsMap.get(config.region); this.allResidences = []; + this.notes = ko.observable(""); if (this.residence) { this.residence = assetsMap.get(this.residence); @@ -1490,9 +1607,9 @@ class PopulationLevel extends NamedElement { this.skyscraperLevels = config.skyscraperLevels.map(l => assetsMap.get(l)); this.allResidences = this.allResidences.concat(this.skyscraperLevels); } - if (config.skylineTower) { - this.skylineTower = assetsMap.get(config.skylineTower); - this.allResidences.push(this.skylineTower); + if (config.specialResidence) { + this.specialResidence = assetsMap.get(config.specialResidence); + this.allResidences.push(this.specialResidence); } this.amount = createIntInput(0, 0); @@ -1530,6 +1647,8 @@ class PopulationLevel extends NamedElement { if (n.tpmin > 0 && assetsMap.get(n.guid)) { if (n.requiredFloorLevel) need = new SkyscraperPopulationNeed(n, this.skyscraperLevels, assetsMap); + else if (n.requiredToBeBuilding) + need = new SpecialResidenceNeed(n, assetsMap); else need = new PopulationLevelNeed(n, this, assetsMap); this.needs.push(need); @@ -1581,18 +1700,20 @@ class PopulationLevel extends NamedElement { this.amount(val * this.existingBuildings()); }); - if (this.skyscraperLevels) { + if (this.skyscraperLevels || this.specialResidence) { // ensure that the value for the population level and those summed over the buildings match // the observables are only used for change propagation, the up-to-date values are available via the functions this.getFloorsSummedExistingBuildings = () => { - var tower = this.skylineTower ? this.skylineTower.existingBuildings() : 0; - return tower + this.skyscraperLevels.map(s => s.existingBuildings()).reduce((a, b) => a + b); + var specialResidence = this.specialResidence ? this.specialResidence.existingBuildings() : 0; + var levelSum = this.skyscraperLevels ? this.skyscraperLevels.map(s => s.existingBuildings()).reduce((a, b) => a + b) : 0; + return specialResidence + levelSum; }; this.floorsSummedExistingBuildings = ko.computed(() => this.getFloorsSummedExistingBuildings()); this.getFloorsSummedLimit = () => { - var tower = this.skylineTower ? this.skylineTower.limit() : 0; - return tower + this.skyscraperLevels.map(s => s.limit()).reduce((a, b) => a + b); + var specialResidence = this.specialResidence ? this.specialResidence.limit() : 0; + var levelSum = this.skyscraperLevels ? this.skyscraperLevels.map(s => s.limit()).reduce((a, b) => a + b) : 0; + return specialResidence + levelSum; }; this.floorsSummedLimit = ko.computed(() => this.getFloorsSummedLimit()); @@ -1805,8 +1926,8 @@ class PopulationLevel extends NamedElement { other.skyscraperLevels.fixLimitPerHouse(true); } - if (this.skylineTower && other.skylineTower) { - other.skylineTower.limitPerHouse(this.skylineTower.limitPerHouse()); + if (this.specialResidence && other.specialResidence) { + other.specialResidence.limitPerHouse(this.specialResidence.limitPerHouse()); } } } @@ -1828,7 +1949,7 @@ class Workforce extends NamedElement { updateAmount() { var sum = 0; - this.demands.forEach(d => sum += d.amount()); + this.demands.forEach(d => sum += d.workforce() == this ? d.amount() : 0); this.amount(sum); } @@ -1848,9 +1969,10 @@ class WorkforceDemand extends NamedElement { this.updateAmount(this.buildings); }); - this.workforce.add(this); + this.workforce = ko.observable(config.workforce); + this.workforce().add(this); - this.amount.subscribe(val => this.workforce.updateAmount()); + this.amount.subscribe(val => this.workforce().updateAmount()); } updateAmount(buildings) { @@ -1861,6 +1983,23 @@ class WorkforceDemand extends NamedElement { } } +class WorkforceDemandSwitch extends WorkforceDemand { + constructor(config, item, assetsMap) { + super(config, assetsMap) + this.item = item; + this.defaultWorkforce = this.workforce(); + this.replacingWorkforce = this.item.replacingWorkforce; + + this.replacingWorkforce.add(this); + + this.item.checked.subscribe(checked => { + this.workforce(checked ? this.replacingWorkforce : this.defaultWorkforce); + this.defaultWorkforce.updateAmount(); + this.replacingWorkforce.updateAmount(); + }); + } +} + class Item extends NamedElement { constructor(config, assetsMap, region) { super(config); @@ -1883,6 +2022,9 @@ class Item extends NamedElement { this.extraGoods = this.additionalOutputs.map(p => assetsMap.get(p.Product)); } + if (this.replacingWorkforce) + this.replacingWorkforce = assetsMap.get(this.replacingWorkforce); + this.factories = this.factories.map(f => assetsMap.get(f)).filter(f => !!f); this.equipments = this.factories.map(f => new EquippedItem({ item: this, factory: f, locaText: this.locaText }, assetsMap)); @@ -1924,6 +2066,7 @@ class EquippedItem extends Option { this.replacements = config.item.replacements; this.replacementArray = config.item.replacementArray; + this.replacingWorkforce = config.item.replacingWorkforce; if (config.item.additionalOutputs) { this.extraGoods = config.item.additionalOutputs.map(cfg => { @@ -2310,9 +2453,9 @@ class TradeList { var sIdxB = view.sessions.indexOf(b.session); if (sIdxA == sIdxB) { - return a.name() > b.name(); + return a.name().localeCompare(b.name()); } else { - return sIdxA > sIdxB; + return sIdxA - sIdxB; } }); var overProduction = this.factory.overProduction(); @@ -2799,7 +2942,7 @@ class ContractUpgradeManager { this.products = ko.computed(() => { return [...this.productsMap.values()] .filter(p => !this.upgradesMap().has(p.guid)) - .sort((a, b) => a.name() > b.name()); + .sort((a, b) => a.name().localeCompare(b.name())); }); this.product = ko.observable(null); this.factors = ko.computed(() => { @@ -2830,9 +2973,9 @@ class ContractUpgradeManager { sortUpgrades() { this.upgrades.sort((a, b) => { if (a.factor != b.factor) - return a.factor < b.factor; + return b.factor - a.factor; - return a.product.name() > b.product.name(); + return a.product.name().localeCompare(b.product.name()); }); } } @@ -2851,7 +2994,7 @@ class ContractCreatorFactory { var contractList = i.isAllIslands() ? f.contractList.imports().concat(f.contractList.exports()) : i.contractManager.contracts(); - var usedProducts = new Set(contractList.map(c => c.product)); + var usedProducts = new Set(contractList.map(c => c.importProduct).concat(contractList.map(c => c.exportProduct))); usedProducts.add(f.product); var list; @@ -2862,7 +3005,7 @@ class ContractCreatorFactory { list = i.products .filter(p => p.guid != 1010566 && p.guid != 270042 && !usedProducts.has(p)); - return list.sort((a, b) => a.name() > b.name()); + return list.sort((a, b) => a.name().localeCompare(b.name())); }); this.exchangeProduct = ko.observable(null); @@ -2874,7 +3017,7 @@ class ContractCreatorFactory { list = this.exchangeProducts().flatMap(p => p.factories); - return list.sort((a, b) => a.getRegionExtendedName() > b.getRegionExtendedName()); + return list.sort((a, b) => a.getRegionExtendedName().localeCompare(b.getRegionExtendedName())); }); this.exchangeFactory = ko.observable(); this.exchangeFactory.subscribe(f => this.exchangeProduct(f ? f.product : null)); @@ -3259,7 +3402,7 @@ class PopulationReader { } class IslandManager { - constructor(params) { + constructor(params, isFirstRun = false) { let islandKey = "islandName"; let islandsKey = "islandNames"; @@ -3289,7 +3432,7 @@ class IslandManager { view.island.subscribe(isl => window.document.title = isl.name()); for (var name of islandNames) { - var island = new Island(params, new Storage(name)); + var island = new Island(params, new Storage(name), false); view.islands.push(island); this.serverNamesMap.set(island.name(), island); @@ -3299,7 +3442,7 @@ class IslandManager { this.sortIslands(); - var allIslands = new Island(params, localStorage); + var allIslands = new Island(params, localStorage, isFirstRun); this.allIslands = allIslands; view.islands.unshift(allIslands); this.serverNamesMap.set(allIslands.name(), allIslands); @@ -3340,7 +3483,7 @@ class IslandManager { if (this.serverNamesMap.has(name) && this.serverNamesMap.get(name).name() == name) return; - var island = new Island(this.params, new Storage(name), session); + var island = new Island(this.params, new Storage(name), true, session); view.islands.push(island); this.sortIslands(); @@ -3480,15 +3623,15 @@ class IslandManager { sortIslands() { view.islands.sort((a, b) => { if (a.isAllIslands() || a.name() == ALL_ISLANDS) - return false; + return -Infinity; else if (b.isAllIslands() || b.name() == ALL_ISLANDS) - return true; + return Infinity; var sIdxA = view.sessions.indexOf(a.session); var sIdxB = view.sessions.indexOf(b.session); if (sIdxA == sIdxB) { - return a.name() - b.name(); + return a.name().localeCompare(b.name()); } else { return sIdxA - sIdxB; } @@ -3501,7 +3644,7 @@ class IslandManager { var sIdxB = view.sessions.indexOf(b.session); if (sIdxA == sIdxB) { - return a.name - b.name; + return a.name.localeCompare(b.name); } else { return sIdxA - sIdxB; } @@ -3647,6 +3790,7 @@ class Template { this.index = index; this.name = asset.name; + this.recipeName = asset.recipeName; this.guid = asset.guid; this.getRegionExtendedName = asset.getRegionExtendedName; this.editable = asset.editable; @@ -3813,7 +3957,7 @@ function init(isFirstRun) { } // set up island management - view.islandManager = new IslandManager(params); + view.islandManager = new IslandManager(params, isFirstRun); if (localStorage) { let id = "language"; @@ -3936,6 +4080,9 @@ function init(isFirstRun) { if (evt.altKey || evt.ctrlKey || evt.shiftKey) return true; + if (evt.target.tagName === 'TEXTAREA') + return true; + if (evt.target.tagName === 'INPUT' && evt.target.type === "text") return true; @@ -3991,6 +4138,13 @@ ko.components.register('number-input-increment', { ` }); +ko.components.register('notes-section', { + template: + `
+ +
` +}); + function formatPercentage(number) { var str = window.formatNumber(Math.ceil(10 * parseFloat(number)) / 10) + ' %'; if (number > 0) @@ -4114,6 +4268,8 @@ function setDefaultFixedFactories(assetsMap) { assetsMap.get(1010240).fixedFactory(assetsMap.get(1010318)); assetsMap.get(1010257).fixedFactory(assetsMap.get(1010340)); assetsMap.get(120032).fixedFactory(assetsMap.get(101252)); + assetsMap.get(1010216).fixedFactory(assetsMap.get(1010294)); + assetsMap.get(1010214).fixedFactory(assetsMap.get(1010292)); } function isLocal() { diff --git a/README.md b/README.md index e8d39a1..1aa8ea4 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ * A calculator for the computer game [Anno 1800](https://www.ubisoft.com/de-de/game/anno-1800/) to compute the required production depending on the population * [YouTube-Tutorial](https://youtu.be/YxU-8YCzpec) * To use the calculator go to the following website: https://nihoel.github.io/Anno1800Calculator/ -* To use it offline, download, unzip and open index.html with a browser: https://github.com/NiHoel/Anno1800Calculator/archive/v7.4.zip +* To use it offline, download, unzip and open index.html with a browser: https://github.com/NiHoel/Anno1800Calculator/archive/v8.0.zip [![Tutorial](CalculatorExtractionScreenshot.png?raw=true "Calculator Extraction Screenshot")](https://youtu.be/k4WmgEIkp4s) diff --git a/i18n.js b/i18n.js index faa5a32..c62da28 100644 --- a/i18n.js +++ b/i18n.js @@ -39,6 +39,20 @@ texts = { "korean": "주민", "russian": "Жители" }, + residences: { + "chinese": "住所", + "english": "Residences", + "french": "Résidences", + "german": "Wohngebäude", + //"guid": 22976, + "italian": "Residenze", + "japanese": "住居", + "korean": "주거지", + "polish": "Domy mieszkalne", + "russian": "Жилые здания", + "spanish": "Residencias", + "taiwanese": "住所" + }, buildings: { "english": "Buildings", //"guid": 22659, @@ -582,6 +596,10 @@ texts = { english: "Use as input on main page", german: "Verwende als Eingabe auf der Hauptseite" }, + notes: { + english: "Note", + german: "Notizen" + }, // view mode viewMode: { english: "View Mode", @@ -664,8 +682,8 @@ Siehe folgenden Link für weitere Informationen: `, korean: "새로운 Anno1800 계산기 버전이 제공됩니다. 다운로드 버튼을 클릭하십시오." }, newFeature: { - english: "Game Update 13.1: Correction of all exchange ratios for Docklands.", - german: "Game Update 13.1: Korrektur aller Tauschverhältnisse in der Speicherstadt.", + english: "Game Update 14: Hacienda with quarters, farms, fertilizer production and policies (see the help on how to activate them). Section for notes for each need, population level and factory.", + german: "Game Update 14: Hacienda mit Unterkünften, farmen, Düngerproduktion und Richtlinien (siehe Hilfe, wie man es aktiviert). Notizfunktion für jedes Bedürfnis, Bevölkerungsstufe und Fabrik.", }, helpContent: { german: @@ -754,6 +772,11 @@ Siehe folgenden Link für weitere Informationen: `,

Eine Besonderheit ergibt sich bei Allen Inseln (standardmäßig ausgewählt, wenn Inselverwaltung deaktiviert). Dort ist es erlaubt dieselbe Ware zu importieren und exportieren, um so Verträge für mehrere Speicherstädte zusammenfassen zu können. Allerdings werden keine zusammenfassenden Informationen zum Handel angezeigt.


+
Keim der Hoffnung
+

Die Hacienda beeinflusst viele Spielmechaniken und die Konfigurationsmöglichkeiten verteilen sich über den gesamten Warenrechner. Hier eine kurze Übersicht: Die Einwohnerunterkünfte können in der Bevölkerungskonfiguration durch Ausklappen des Abschnitts Wohnhäuser gefunden werden. Die Initiative für maßvolle Ernährung ist im Zeitungsmenü auswählbar. Hacienda-Farmen können im Konfigurationsmenü für Produktionsketten ausgewählt werden. Die Gleiche Region-Option bevorzugt die traditionellen Produktionsgebäude - wenn beide in derselben Region sind. Neue Inseln werden so eingestellt, dass die traditionellen Produktionsgebäude gewählt werden. Vorhandene Inseln aus älteren Konfigurationen des Warenrechners verwenden die gleiche-Region-Regel für alle Produkte, die jetzt neue Produktionsgebäude erhalten haben. Das bedeutet, dass von Obreras verbrauchtes Bier in der Hacienda-Brauerei hergestellt wird.

+

Die Düngerproduktionskette funktioniert ähnlich wie bei Silos. Jede Farm hat eine zusätzliches Kontrollkästchen, um diese zu aktivieren. Damit werden Produktionsboost, Zusatzwaren und Düngerverbrauch ausgelöst. Bei Farmen der Alten Welt muss man das Konfigurationsdialog der Fabrik öffnen, um die Option angezeigt zu bekommen. Die Mistproduktion ist allerding besonders. Es gibt keine Standard-Fabrik, welche Mist produziert. Also habe ich eine künstliche erstellt: Alle Tierhöfe. Deren Zweck ist den Überblick über die komplette Mistproduktion zu behalten. Die Produktionsparameter sind von der Mistproduktion einer Alpkafarm abgeleitet. Man könnte also das künstliche Produktionsgebäude direkt verwenden. Der korrekte Weg geht allerdings über Zusatzwaren - welche in den Einstellungen aktiviert werden müssen. Den zugehörigen Abschnitt in der Hilfe bitte vorher lesen! Die Mistproduktion wird auf Tierhöfen genauso aktiviert wie man ein Item ausrüsten würde. Der gesammelte Mist wird dann unter Zusatzwaren bei Allen Tierhöfen angezeigt. Danach wird er vom Düngerwerk verarbeitet - übrigens das einzige Produktionsgebäude, bei dem sich das Icon von Ware und Fabrik unterscheiden.

+
+
Haftungsausschluss

Der Warenrechner wird ohne irgendeine Gewährleistung zur Verfügung gestellt. Die Arbeit wurde in KEINER Weise von Ubisoft Blue Byte unterstützt. Alle Assets aus dem Spiel Anno 1800 sind © by Ubisoft.


Darunter fallen insbesondere, aber nicht ausschließlich alle Icons, Bezeichnungen und Verbrauchswerte.


@@ -785,7 +808,7 @@ Siehe folgenden Link für weitere Informationen: `,

The button top left of the population levels opens a dedicated menu. The above graphic illustrates which values from the calculator correspond to which values in the game. The "Per House" entries are an average value and can have decimals. After unlocking the field, its value gets updated whenever the value for buildings or residents is changed. If the field is locked, the other values in the right column are updated to be consistent. If the statistic extraction is used or the setting that residents per house are calculated based on fulfilled needs, then the field must be locked but gets updated.

The radio buttons in the left column determine which value is displayed on the main page. The buttons next to the goods lock/unlock the need. The button Apply Globally applies both per house entries, the locked needs, and all selected consumption effects to all islands.


-

When clicking on the heading Skyscrapers the skyscraper levels and the Skyline Tower are shown. The simple view only shows the building counters. The panorama effect cannot be incorporated. The complex view shows the building counter, the residents limit per house (5), and the population limit of the level. On must manually estimate the number of residents per house based on the panorama effect, items and other effects. Except for the skyscraper maintenance in the finance screen, the game provides no help to estimate these values. It is recommended to set the per residence values before the number of buildings. This ensures that the population limit is updated correctly. The consumption depends on the population limit, whereas it is irrelevant how many residents live in a skyscraper. The upper input fields become summary fields once the first skyscraper is built. Thus, max. residents per house cannot be changed and the amount per residence not locked. That the input fields work without skyscrapers in the same way as before, it was necessary to add regular houses and synchronize their values with the summary fields. Changing the summary fields only affects the houses and should be avoided.


+

When clicking on the heading Skyscrapers the skyscraper levels and the Skyline Tower are shown. The simple view only shows the building counters. The panorama effect cannot be incorporated. The complex view shows the building counter, the residents limit per house (5), and the population limit of the level. One must manually estimate the number of residents per house based on the panorama effect, items and other effects. Except for the skyscraper maintenance in the finance screen, the game provides no help to estimate these values. It is recommended to set the per residence values before the number of buildings. This ensures that the population limit is updated correctly. The consumption depends on the population limit, whereas it is irrelevant how many residents live in a skyscraper. The upper input fields become summary fields once the first skyscraper is built. Thus, max. residents per house cannot be changed and the amount per residence not locked. That the input fields work without skyscrapers in the same way as before, it was necessary to add regular houses and synchronize their values with the summary fields. Changing the summary fields only affects the houses and should be avoided.


Global Settings
@@ -854,6 +877,11 @@ Siehe folgenden Link für weitere Informationen: `,

There is a second use case, where one wants to trade as many goods as possible per trade. First of all, one has to specify all the trade contracts and the loading speed. Here, the absolute amount per contract does not matter, only the relative difference between different contracts. Then, enter the total island storage capacity and click the button Set total capacity next to it. It determines the good that requires the maximal storage capacity c. It scales the contract by a factor f such that c matches the island storage capacity. Finally, f is applied to all other contracts.

All Islands (selected by default, if no island management is active) behaves a bit different. It allows to import and export the same good so that one can aggregate the contracts of several docklands. But it does not show the summary information about the trade.


+
Seeds of Change
+

The Hacienda affects many game mechanics and configuration options are scattered across the calculator. Here is a short overview: The resident quarters can be found in the population configuration menu by expanding the Residences dropdown. The Dietary Education Initiative is available in the newspaper dialog. Hacienda farms can be selected in the production chain configuration menu. The same region option favors the traditional production buildings - if both are in the same region. New islands will be configured to use the traditional ones. Existing islands from older configurations use the same region option for all products that now have new production buildings. This means that beer consumed by Obreras is assumed to be produced by Hacienda Brewery.

+

The fertiliser chain works like silos. Each farm has an extra checkbox to enable it. This will induce production increase, extra goods, and fertiliser consumption. For old world farms, one has to open the factory configuration dialog to see this checkbox. Dung production is special, however. There is no standard factory in the game to produce it, so I created an artificial one: All Animal Farms. Its purpose is to keep track of all produced dung. The production statistics are derived from the dung production of an alpaca farm. So, one can use this artificial production building directly. The correct way, however, is via extra goods - which must be enabled in the settings. Please read the corresponding help section first! The dung production on an animal farm is enabled like you would equip an item. The collected dung is shown in the extra goods section of the All Animal Farms and then processed by the Fertilizer Works - which is the only factory where product and factory icon differ.

+
+
Disclaimer

The calculator is provided without warranty of any kind. The work was NOT endorsed by Ubisoft Blue Byte in any kind. All the assets from Anno 1800 game are © by Ubisoft.


These are especially but not exclusively all icons, designators, and consumption values.


diff --git a/index.html b/index.html index 57b186c..9f758ba 100644 --- a/index.html +++ b/index.html @@ -30,25 +30,25 @@ @@ -141,7 +141,7 @@

View Mode

- + + + + + + : + + + +
+ + t/min +
+ + + + + + + : + + + +
+ + t/min +
+ +
@@ -1657,6 +1699,19 @@
Trade Contracts
+ +
+
+ + +
+
+
+ +
+
@@ -1715,6 +1770,7 @@
Extra Goods
+