ogamejs 3.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +485 -228
  2. package/package.json +51 -4
  3. package/src/buildings/buildTime.js +25 -0
  4. package/src/buildings/crystal.js +18 -36
  5. package/src/buildings/deut.js +19 -34
  6. package/src/buildings/fusion-reactor.js +19 -34
  7. package/src/buildings/index.js +11 -0
  8. package/src/buildings/info.js +42 -0
  9. package/src/buildings/infocompte.js +116 -57
  10. package/src/buildings/metal.js +13 -28
  11. package/src/buildings/production.js +150 -0
  12. package/src/buildings/solar-plant.js +13 -21
  13. package/src/buildings/storage.js +59 -0
  14. package/src/cost.js +70 -0
  15. package/src/fleets/combat.js +291 -0
  16. package/src/fleets/distance.js +39 -0
  17. package/src/fleets/flight.js +105 -0
  18. package/src/fleets/getDebris.js +11 -10
  19. package/src/fleets/index.js +12 -0
  20. package/src/fleets/speed.js +108 -0
  21. package/src/i18n.js +43 -0
  22. package/src/index.js +12 -1
  23. package/src/models/buildings.js +285 -25
  24. package/src/models/destroyable.js +216 -110
  25. package/src/models/research.js +169 -0
  26. package/src/research/index.js +9 -0
  27. package/src/research/researchTime.js +25 -0
  28. package/src/types.js +146 -0
  29. package/types/buildings/buildTime.d.ts +16 -0
  30. package/types/buildings/buildTime.d.ts.map +1 -0
  31. package/types/buildings/crystal.d.ts +12 -0
  32. package/types/buildings/crystal.d.ts.map +1 -0
  33. package/types/buildings/deut.d.ts +12 -0
  34. package/types/buildings/deut.d.ts.map +1 -0
  35. package/types/buildings/fusion-reactor.d.ts +15 -0
  36. package/types/buildings/fusion-reactor.d.ts.map +1 -0
  37. package/types/buildings/index.d.ts +27 -0
  38. package/types/buildings/index.d.ts.map +1 -0
  39. package/types/buildings/info.d.ts +17 -0
  40. package/types/buildings/info.d.ts.map +1 -0
  41. package/types/buildings/infocompte.d.ts +41 -0
  42. package/types/buildings/infocompte.d.ts.map +1 -0
  43. package/types/buildings/metal.d.ts +11 -0
  44. package/types/buildings/metal.d.ts.map +1 -0
  45. package/types/buildings/production.d.ts +78 -0
  46. package/types/buildings/production.d.ts.map +1 -0
  47. package/types/buildings/solar-plant.d.ts +10 -0
  48. package/types/buildings/solar-plant.d.ts.map +1 -0
  49. package/types/buildings/storage.d.ts +29 -0
  50. package/types/buildings/storage.d.ts.map +1 -0
  51. package/types/cost.d.ts +25 -0
  52. package/types/cost.d.ts.map +1 -0
  53. package/types/fleets/combat.d.ts +57 -0
  54. package/types/fleets/combat.d.ts.map +1 -0
  55. package/types/fleets/distance.d.ts +13 -0
  56. package/types/fleets/distance.d.ts.map +1 -0
  57. package/types/fleets/flight.d.ts +57 -0
  58. package/types/fleets/flight.d.ts.map +1 -0
  59. package/types/fleets/getDebris.d.ts +12 -0
  60. package/types/fleets/getDebris.d.ts.map +1 -0
  61. package/types/fleets/index.d.ts +18 -0
  62. package/types/fleets/index.d.ts.map +1 -0
  63. package/types/fleets/speed.d.ts +54 -0
  64. package/types/fleets/speed.d.ts.map +1 -0
  65. package/types/i18n.d.ts +23 -0
  66. package/types/i18n.d.ts.map +1 -0
  67. package/types/index.d.ts +1316 -0
  68. package/types/index.d.ts.map +1 -0
  69. package/types/models/buildings.d.ts +373 -0
  70. package/types/models/buildings.d.ts.map +1 -0
  71. package/types/models/destroyable.d.ts +695 -0
  72. package/types/models/destroyable.d.ts.map +1 -0
  73. package/types/models/research.d.ts +262 -0
  74. package/types/models/research.d.ts.map +1 -0
  75. package/types/research/index.d.ts +8 -0
  76. package/types/research/index.d.ts.map +1 -0
  77. package/types/research/researchTime.d.ts +16 -0
  78. package/types/research/researchTime.d.ts.map +1 -0
  79. package/types/trades/crystal.d.ts +6 -0
  80. package/types/trades/crystal.d.ts.map +1 -0
  81. package/types/trades/deut.d.ts +6 -0
  82. package/types/trades/deut.d.ts.map +1 -0
  83. package/types/trades/index.d.ts +12 -0
  84. package/types/trades/index.d.ts.map +1 -0
  85. package/types/trades/metal.d.ts +6 -0
  86. package/types/trades/metal.d.ts.map +1 -0
  87. package/types/trades/parseRate.d.ts +2 -0
  88. package/types/trades/parseRate.d.ts.map +1 -0
  89. package/types/trades/utils.d.ts +2 -0
  90. package/types/trades/utils.d.ts.map +1 -0
  91. package/types/types.d.ts +449 -0
  92. package/types/types.d.ts.map +1 -0
package/package.json CHANGED
@@ -1,22 +1,67 @@
1
1
  {
2
2
  "name": "ogamejs",
3
- "version": "3.0.0",
4
- "description": "",
3
+ "version": "4.0.0",
4
+ "description": "OGame formulas as a dependency-free ESM library: building and research costs, production, build times, fleet debris and marketplace rates.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
7
- "exports": "./src/index.js",
7
+ "sideEffects": false,
8
+ "exports": {
9
+ ".": {
10
+ "types": "./types/index.d.ts",
11
+ "default": "./src/index.js"
12
+ },
13
+ "./buildings": {
14
+ "types": "./types/buildings/index.d.ts",
15
+ "default": "./src/buildings/index.js"
16
+ },
17
+ "./fleets": {
18
+ "types": "./types/fleets/index.d.ts",
19
+ "default": "./src/fleets/index.js"
20
+ },
21
+ "./research": {
22
+ "types": "./types/research/index.d.ts",
23
+ "default": "./src/research/index.js"
24
+ },
25
+ "./trades": {
26
+ "types": "./types/trades/index.d.ts",
27
+ "default": "./src/trades/index.js"
28
+ },
29
+ "./i18n": {
30
+ "types": "./types/i18n.d.ts",
31
+ "default": "./src/i18n.js"
32
+ },
33
+ "./types": {
34
+ "types": "./types/types.d.ts",
35
+ "default": "./src/types.js"
36
+ },
37
+ "./models/buildings": {
38
+ "types": "./types/models/buildings.d.ts",
39
+ "default": "./src/models/buildings.js"
40
+ },
41
+ "./models/destroyable": {
42
+ "types": "./types/models/destroyable.d.ts",
43
+ "default": "./src/models/destroyable.js"
44
+ },
45
+ "./models/research": {
46
+ "types": "./types/models/research.d.ts",
47
+ "default": "./src/models/research.js"
48
+ }
49
+ },
8
50
  "engines": {
9
51
  "node": ">=24"
10
52
  },
11
53
  "scripts": {
12
54
  "commit": "git-cz",
13
55
  "lint": "eslint .",
56
+ "types": "tsc",
57
+ "prepack": "npm run types",
14
58
  "test": "vitest run",
15
59
  "test:watch": "vitest",
16
60
  "release": "semantic-release"
17
61
  },
18
62
  "files": [
19
63
  "src",
64
+ "types",
20
65
  "!src/**/*.test.js"
21
66
  ],
22
67
  "repository": {
@@ -41,11 +86,13 @@
41
86
  "git-cz": "^4.9.0",
42
87
  "globals": "^15.14.0",
43
88
  "semantic-release": "^25.0.8",
89
+ "typescript": "^7.0.2",
44
90
  "vitest": "^3.0.4"
45
91
  },
46
92
  "config": {
47
93
  "commitizen": {
48
94
  "path": "./node_modules/cz-conventional-changelog"
49
95
  }
50
- }
96
+ },
97
+ "types": "./types/index.d.ts"
51
98
  }
@@ -0,0 +1,25 @@
1
+ import getCost, { assertLevel } from '../cost.js';
2
+
3
+ /**
4
+ *
5
+ * Return the time needed to build a building at a given level
6
+ *
7
+ * time = (metal + crystal) / (2500 * (1 + robotics) * 2 ** nanites) hours,
8
+ * divided by the universe economy speed.
9
+ * @param {import('../types.js').BuildingEntry} building A models/buildings.js entry
10
+ * @param {number} targetLevel The level to reach, >= 1
11
+ * @param {number} [roboticsLevel] The robotics factory level on that planet
12
+ * @param {number} [naniteLevel] The nanite factory level on that planet
13
+ * @param {number} [universeSpeed] The universe economy speed
14
+ * @returns {number} The build time, in seconds
15
+ */
16
+ function getBuildTime(building, targetLevel, roboticsLevel = 0, naniteLevel = 0, universeSpeed = 1) {
17
+ assertLevel(targetLevel);
18
+
19
+ const { metal, crystal } = getCost(building, targetLevel);
20
+ const divider = 2500 * (1 + roboticsLevel) * 2 ** naniteLevel * universeSpeed;
21
+
22
+ return Math.round(((metal + crystal) / divider) * 3600);
23
+ }
24
+
25
+ export default getBuildTime;
@@ -1,11 +1,10 @@
1
- function getPositionFactor(pos) {
2
- const factor = {
3
- 1: 1.3,
4
- 2: 1.225,
5
- 3: 1.15,
6
- };
1
+ import buildingInfo from './info.js';
2
+
3
+ // Positions 1, 2 and 3 give a 30%, 22.5% and 15% crystal production bonus.
4
+ const POSITION_FACTOR = { 1: 1.3, 2: 1.225, 3: 1.15 };
7
5
 
8
- return factor[Number.parseInt(pos, 10)] ? factor[Number.parseInt(pos, 10)] : 1;
6
+ function getPositionFactor(pos) {
7
+ return POSITION_FACTOR[Number.parseInt(pos, 10)] ?? 1;
9
8
  }
10
9
 
11
10
  function getMineProduction(baseProduction, targetLevel, pos, universeSpeed) {
@@ -15,39 +14,22 @@ function getMineProduction(baseProduction, targetLevel, pos, universeSpeed) {
15
14
  return Math.floor(baseProduction * targetLevel * levelFactor * universeSpeed * positionFactor);
16
15
  }
17
16
 
18
- function getEnergyCost(baseEnergyCost, targetLevel) {
19
- const level = targetLevel;
20
- const levelFactor = 1.1 ** level;
21
- return Math.floor(baseEnergyCost * level * levelFactor);
22
- }
23
-
24
- function getMetalCost(baseMetalCost, targetLevel) {
25
- const level = targetLevel - 1;
26
- return Math.floor(baseMetalCost * 1.6 ** level);
27
- }
28
-
29
- function getCrystalCost(baseCrystalCost, targetLevel) {
30
- const level = targetLevel - 1;
31
- return Math.floor(baseCrystalCost * 1.6 ** level);
32
- }
33
-
34
17
  /**
35
18
  *
36
- * Return information about the crystal mine given a specific level
37
- * @param {object} mine The crystal mine base information
38
- * @param {number} targetLevel the crystal mine target level
39
- * @param {number} pos pos 1/2/3 have a 15/10/5%
40
- * @param {number} universeSpeed production factor is increased for some universe
41
- * @returns {Object} informations about the crystal mine at this specific level
19
+ * Return information about the crystal mine at a given level
20
+ * @param {import('../types.js').BuildingEntry} mine The crystal mine entry, `Buildings[2]`
21
+ * @param {number} targetLevel The level to reach, >= 1
22
+ * @param {number} pos The planet position, 1, 2 and 3 produce more crystal
23
+ * @param {number} [universeSpeed] Production is increased on faster universes
24
+ * @returns {import('../types.js').BuildingInfo} Cost, consumption and production at that level
42
25
  */
43
26
  function getCrystalMine(mine, targetLevel, pos, universeSpeed = 1) {
44
- return {
45
- crystal: getCrystalCost(mine.crystal, targetLevel),
46
- deuterium: 0,
47
- energy: getEnergyCost(mine.energy, targetLevel),
48
- metal: getMetalCost(mine.metal, targetLevel),
49
- production: getMineProduction(mine.production, targetLevel, pos, universeSpeed),
50
- };
27
+ return buildingInfo(
28
+ mine,
29
+ targetLevel,
30
+ universeSpeed,
31
+ (base) => getMineProduction(base.production, targetLevel, pos, universeSpeed),
32
+ );
51
33
  }
52
34
 
53
35
  export default getCrystalMine;
@@ -1,46 +1,31 @@
1
- function getPositionFactor(avg) {
2
- return 0.68 - 0.002 * avg;
3
- }
4
-
5
- function getMineProduction(energyCost, avg, universeSpeed) {
6
- const factor = getPositionFactor(avg);
7
- return Math.floor(universeSpeed * energyCost * factor);
8
- }
1
+ import buildingInfo from './info.js';
9
2
 
10
- function getEnergyCost(baseEnergyCost, targetLevel) {
11
- const level = targetLevel;
12
- const levelFactor = 1.1 ** level;
13
- return Math.floor(baseEnergyCost * level * levelFactor);
14
- }
15
-
16
- function getMetalCost(baseMetalCost, targetLevel) {
17
- const level = targetLevel - 1;
18
- return Math.floor(baseMetalCost * 1.5 ** level);
3
+ // The colder the planet, the more deuterium the synthesizer draws out of it.
4
+ function getTemperatureFactor(avg) {
5
+ return 0.68 - 0.002 * avg;
19
6
  }
20
7
 
21
- function getCrystalCost(baseCrystalCost, targetLevel) {
22
- const level = targetLevel - 1;
23
- return Math.floor(baseCrystalCost * 1.5 ** level);
8
+ function getMineProduction(energyConsumption, avg, universeSpeed) {
9
+ return Math.floor(universeSpeed * energyConsumption * getTemperatureFactor(avg));
24
10
  }
25
11
 
26
12
  /**
27
13
  *
28
- * Return information about the deuterium synth given a specific level
29
- * @param {object} mine The deut synth base information
30
- * @param {number} targetLevel the deuterieum synth target level
31
- * @param {number} avg planet average temperature - The lower the higher the prod is
32
- * @param {number} universeSpeed production factor is increased for some universe
33
- * @returns {Object} informations about the deut synth at this specific level
14
+ * Return information about the deuterium synthesizer at a given level
15
+ * @param {import('../types.js').BuildingEntry} mine The deuterium synthesizer entry, `Buildings[3]`
16
+ * @param {number} targetLevel The level to reach, >= 1
17
+ * @param {number} avg The planet average temperature, the lower the higher the production
18
+ * @param {number} [universeSpeed] Production is increased on faster universes
19
+ * @returns {import('../types.js').BuildingInfo} Cost, consumption and production at that level
34
20
  */
35
21
  function getDeutSynth(mine, targetLevel, avg, universeSpeed = 1) {
36
- const energyCost = getEnergyCost(mine.energy, targetLevel);
37
- return {
38
- crystal: getCrystalCost(mine.crystal, targetLevel),
39
- energy: energyCost,
40
- deuterium: 0,
41
- metal: getMetalCost(mine.metal, targetLevel),
42
- production: getMineProduction(energyCost, avg, universeSpeed),
43
- };
22
+ return buildingInfo(
23
+ mine,
24
+ targetLevel,
25
+ universeSpeed,
26
+ // The synthesizer production is driven by the energy it consumes.
27
+ (base, flows) => getMineProduction(flows.energyConsumption, avg, universeSpeed),
28
+ );
44
29
  }
45
30
 
46
31
  export default getDeutSynth;
@@ -1,50 +1,35 @@
1
+ import buildingInfo from './info.js';
2
+
3
+ // Energy technology makes each reactor level a little more efficient.
1
4
  function getEnergyFactor(energyTech) {
2
5
  return 1.05 + 0.01 * energyTech;
3
6
  }
4
7
 
5
8
  function getEnergyProduction(baseProduction, targetLevel, energyTech) {
6
9
  const factor = getEnergyFactor(energyTech) ** targetLevel;
7
- return Math.floor(baseProduction * targetLevel * factor);
8
- }
9
10
 
10
- function getMetalCost(baseMetalCost, targetLevel) {
11
- const level = targetLevel - 1;
12
- return Math.floor(baseMetalCost * 1.8 ** level);
13
- }
14
-
15
- function getCrystalCost(baseCrystalCost, targetLevel) {
16
- const level = targetLevel - 1;
17
- return Math.floor(baseCrystalCost * 1.8 ** level);
18
- }
19
-
20
- function getDeuteriumCost(baseDeuteriumCost, targetLevel) {
21
- const level = targetLevel - 1;
22
- return Math.floor(baseDeuteriumCost * 1.8 ** level);
23
- }
24
-
25
- function getConsumption(baseConsumption, targetLevel, universeSpeed) {
26
- const levelFactor = 1.1 ** targetLevel;
27
- return Math.floor(baseConsumption * targetLevel * levelFactor * universeSpeed);
11
+ return Math.floor(baseProduction * targetLevel * factor);
28
12
  }
29
13
 
30
14
  /**
31
15
  *
32
- * Return information about the fusion reactor given a specific level
33
- * @param {object} reactor The fusion react base information
34
- * @param {number} targetLevel
35
- * @param {number} energyTech The technology energy level
36
- * @param {number} universeSpeed production factor is increased for some universe
37
- * @returns {Object} informations about the fusion reactor at this specific level
16
+ * Return information about the fusion reactor at a given level
17
+ *
18
+ * `deuteriumConsumption` is the deuterium the reactor burns per hour, and
19
+ * `production` the energy it delivers.
20
+ * @param {import('../types.js').BuildingEntry} reactor The fusion reactor entry, `Buildings[5]`
21
+ * @param {number} targetLevel The level to reach, >= 1
22
+ * @param {number} energyTech The Energy Technology level
23
+ * @param {number} [universeSpeed] Consumption is increased on faster universes
24
+ * @returns {import('../types.js').BuildingInfo} Cost, deuterium consumption and energy production
38
25
  */
39
26
  function getFusionReactor(reactor, targetLevel, energyTech, universeSpeed = 1) {
40
- return {
41
- crystal: getCrystalCost(reactor.crystal, targetLevel),
42
- energy: 0,
43
- consumption: getConsumption(reactor.consumption, targetLevel, universeSpeed),
44
- deuterium: getDeuteriumCost(reactor.deutrium, targetLevel),
45
- metal: getMetalCost(reactor.metal, targetLevel),
46
- production: getEnergyProduction(reactor.production, targetLevel, energyTech),
47
- };
27
+ return buildingInfo(
28
+ reactor,
29
+ targetLevel,
30
+ universeSpeed,
31
+ (base) => getEnergyProduction(base.production, targetLevel, energyTech),
32
+ );
48
33
  }
49
34
 
50
35
  export default getFusionReactor;
@@ -4,6 +4,10 @@ import getMetalMine from './metal.js';
4
4
  import getSolarPlant from './solar-plant.js';
5
5
  import getFusionReactor from './fusion-reactor.js';
6
6
  import parseInfoCompteData from './infocompte.js';
7
+ import getCost from '../cost.js';
8
+ import getStorage, { getStorageCapacity, getStorageLevelFor } from './storage.js';
9
+ import getBuildTime from './buildTime.js';
10
+ import getPlanetProduction, { getProductionBonus } from './production.js';
7
11
 
8
12
  const Buildings = {
9
13
  getCrystalMine,
@@ -12,6 +16,13 @@ const Buildings = {
12
16
  getSolarPlant,
13
17
  getFusionReactor,
14
18
  parseInfoCompteData,
19
+ getBuildingCost: getCost,
20
+ getBuildTime,
21
+ getStorage,
22
+ getStorageCapacity,
23
+ getStorageLevelFor,
24
+ getPlanetProduction,
25
+ getProductionBonus,
15
26
  };
16
27
 
17
28
  export default Buildings;
@@ -0,0 +1,42 @@
1
+ import getCost, { assertEntry } from '../cost.js';
2
+
3
+ /**
4
+ * Energy and deuterium flows both grow the same way: base * level * 1.1 ** level.
5
+ */
6
+ function flow(baseValue, targetLevel, universeSpeed = 1) {
7
+ return Math.floor(baseValue * targetLevel * 1.1 ** targetLevel * universeSpeed);
8
+ }
9
+
10
+ /**
11
+ *
12
+ * Assemble the information every building calculator returns
13
+ *
14
+ * Keeping a single shape means a caller can read `production` or
15
+ * `energyConsumption` without knowing which building it is looking at; the
16
+ * fields that do not apply are simply `0`.
17
+ * @param {import('../types.js').BuildingEntry} entry A models/buildings.js entry
18
+ * @param {number} targetLevel The level to reach, >= 1
19
+ * @param {number} universeSpeed The universe economy speed
20
+ * @param {(base: object, flows: object) => number} computeProduction Production of
21
+ * that building, given its base stats and its already computed consumptions
22
+ * @returns {import('../types.js').BuildingInfo} The full picture of that building at that level
23
+ */
24
+ function buildingInfo(entry, targetLevel, universeSpeed, computeProduction) {
25
+ assertEntry(entry, targetLevel);
26
+
27
+ const { base } = entry;
28
+ const flows = {
29
+ // The energy a building consumes does not depend on the universe speed,
30
+ // the deuterium a fusion reactor burns does.
31
+ energyConsumption: flow(base.energyConsumption, targetLevel),
32
+ deuteriumConsumption: flow(base.deuteriumConsumption, targetLevel, universeSpeed),
33
+ };
34
+
35
+ return {
36
+ ...getCost(entry, targetLevel),
37
+ ...flows,
38
+ production: computeProduction(base, flows),
39
+ };
40
+ }
41
+
42
+ export default buildingInfo;
@@ -1,64 +1,137 @@
1
- function toNumber(number = '') {
2
- return Number(number.split('.').join(''));
3
- }
4
-
5
1
  /**
2
+ * Labels used by the Infocompte export, per game language.
6
3
  *
7
- * Return information about the crystal mine given a specific level
8
- * @param {object} data The infocompte bb-code
9
- * @returns {Object} The parsed JSON object of infocompte
4
+ * The French set is the reference one; the others are best effort. A locale can
5
+ * be overridden per call through the `labels` option, so an unsupported
6
+ * language does not require a release.
10
7
  */
11
- function parseInfoCompteData(data) {
12
- const universeDataRe = new RegExp('([0-9]{3}-[a-z]{2})');
13
- const [universe, lang] = data.match(universeDataRe)[1].split('-');
8
+ const LOCALES = Object.freeze({
9
+ fr: {
10
+ planet: 'Planète',
11
+ metalPoints: 'Points dans les mines de métal',
12
+ crystalPoints: 'Points dans les mines de cristal',
13
+ deutPoints: 'Points dans les mines de deut',
14
+ plasma: 'Technologie Plasma',
15
+ hourly: 'Par heure',
16
+ },
17
+ en: {
18
+ planet: 'Planet',
19
+ metalPoints: 'Points in metal mines',
20
+ crystalPoints: 'Points in crystal mines',
21
+ deutPoints: 'Points in deut mines',
22
+ plasma: 'Plasma Technology',
23
+ hourly: 'Per hour',
24
+ },
25
+ });
14
26
 
15
- // eslint-disable-next-line security/detect-unsafe-regex
16
- const planetsRe = new RegExp('(?:Planète [0-9]+(.+))(?:s+(?:Planète [0-9]+))*', 'g');
17
- const planets = data.matchAll(planetsRe);
27
+ function escapeRegExp(value) {
28
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
29
+ }
18
30
 
19
- const metalPointRe = new RegExp('Points dans les mines de métal : (.+)');
20
- const metalPoint = toNumber(data.match(metalPointRe)[1]);
31
+ // Infocompte groups thousands with dots, spaces or non breaking spaces.
32
+ function toNumber(value = '') {
33
+ return Number(String(value).replace(/[^\d-]/g, ''));
34
+ }
21
35
 
22
- const crystalPointRe = new RegExp('Points dans les mines de cristal : (.+)');
23
- const crystalPoint = toNumber(data.match(crystalPointRe)[1]);
36
+ function matchOne(data, label, what) {
37
+ // The label is escaped just above, so it cannot inject a pattern.
38
+ // eslint-disable-next-line security/detect-non-literal-regexp
39
+ const match = data.match(new RegExp(`${escapeRegExp(label)}\\s*:\\s*(.+)`));
24
40
 
25
- const deutRe = new RegExp('Points dans les mines de deut : (.+)');
26
- const deutPoint = toNumber(data.match(deutRe)[1]);
41
+ if (!match) {
42
+ throw new Error(`could not find ${what} in the report, is the language supported?`);
43
+ }
27
44
 
28
- const plasmaRe = new RegExp('Technologie Plasma : (.+)');
29
- const plasmaTechLevel = toNumber(data.match(plasmaRe)[1]);
45
+ return match[1];
46
+ }
30
47
 
31
- const hourlyRe = new RegExp('Par heure : (.+)');
32
- const hourly = data.match(hourlyRe);
48
+ function parseTriplet(line, what) {
49
+ // Digits, plus the dot, space, non breaking space and narrow no-break space
50
+ // Infocompte may use as a thousands separator.
51
+ const numbers = line.match(/\d[\d.\u0020\u00A0\u202F]*/g);
33
52
 
34
- const [hourlyMetal, hourlyCristal, hourlyDeut] = hourly[1].split('/');
53
+ if (!numbers || numbers.length < 3) {
54
+ throw new Error(`could not parse ${what} in the report`);
55
+ }
35
56
 
36
- const [hourlyMetalValue] = hourlyMetal.split(' ');
37
- const hourlyMetalNumber = toNumber(hourlyMetalValue);
57
+ return numbers.slice(0, 3).map(toNumber);
58
+ }
38
59
 
39
- const [hourlyCristalValue] = hourlyCristal.split(' ').filter(Boolean);
40
- const hourlyCristalNumber = toNumber(hourlyCristalValue);
60
+ function parsePlanets(data, labels) {
61
+ // Matches `Planète 01 : Métal 36 / Cristal 31 / Deutérium 31 / -94°C` without
62
+ // depending on the resource names, only on the shape of the line.
63
+ // eslint-disable-next-line security/detect-non-literal-regexp
64
+ const planetRe = new RegExp(
65
+ `^[^\\S\\n]*(${escapeRegExp(labels.planet)}\\s+\\d+)\\s*:`
66
+ + '[^\\d]*(\\d+)\\s*/[^\\d-]*(\\d+)\\s*/[^\\d-]*(\\d+)\\s*/\\s*(-?\\d+)\\s*°',
67
+ 'gm',
68
+ );
41
69
 
42
- const [hourlyDeutValue] = hourlyDeut.split(' ').filter(Boolean);
43
- const hourlyDeutNumber = toNumber(hourlyDeutValue);
70
+ return [...data.matchAll(planetRe)].map(([, planet, metal, crystal, deut, temperature]) => ({
71
+ planet: planet.trim(),
72
+ metal: Number(metal),
73
+ crystal: Number(crystal),
74
+ deut: Number(deut),
75
+ temperature: Number(temperature),
76
+ }));
77
+ }
44
78
 
45
- const response = {
46
- planets: [],
79
+ /**
80
+ *
81
+ * Parse the BBCode of an Infocompte report into a structured object
82
+ * @param {string} data The infocompte bb-code
83
+ * @param {object} [options] Parsing options
84
+ * @param {string} [options.locale] Force a locale instead of reading it from the report
85
+ * @param {object} [options.labels] Override the labels, see LOCALES for the shape
86
+ * @returns {Object} The parsed JSON object of infocompte
87
+ */
88
+ function parseInfoCompteData(data, options = {}) {
89
+ if (typeof data !== 'string' || data.trim() === '') {
90
+ throw new Error('data must be a non empty string');
91
+ }
92
+
93
+ const universeData = data.match(/([0-9]{3}-[a-z]{2})/);
94
+
95
+ if (!universeData) {
96
+ throw new Error('could not find the universe and language header in the report');
97
+ }
98
+
99
+ const [universe, lang] = universeData[1].split('-');
100
+ const locale = options.locale ?? lang;
101
+ // eslint-disable-next-line security/detect-object-injection
102
+ const knownLocale = Object.hasOwn(LOCALES, locale) ? LOCALES[locale] : undefined;
103
+ const labels = options.labels ?? knownLocale ?? LOCALES.fr;
104
+
105
+ const metalPoint = toNumber(matchOne(data, labels.metalPoints, 'the metal mine points'));
106
+ const crystalPoint = toNumber(matchOne(data, labels.crystalPoints, 'the crystal mine points'));
107
+ const deutPoint = toNumber(matchOne(data, labels.deutPoints, 'the deuterium mine points'));
108
+ const plasmaTechLevel = toNumber(matchOne(data, labels.plasma, 'the plasma technology level'));
109
+
110
+ const [hourlyMetal, hourlyCrystal, hourlyDeut] = parseTriplet(
111
+ matchOne(data, labels.hourly, 'the hourly production'),
112
+ 'the hourly production',
113
+ );
114
+
115
+ const perDay = 24;
116
+ const perWeek = 24 * 7;
117
+
118
+ return {
119
+ planets: parsePlanets(data, labels),
47
120
  production: {
48
121
  hourly: {
49
- metal: hourlyMetalNumber,
50
- crystal: hourlyCristalNumber,
51
- deut: hourlyDeutNumber,
122
+ metal: hourlyMetal,
123
+ crystal: hourlyCrystal,
124
+ deut: hourlyDeut,
52
125
  },
53
126
  daily: {
54
- metal: hourlyMetalNumber * 24,
55
- crystal: hourlyCristalNumber * 24,
56
- deut: hourlyDeutNumber * 24,
127
+ metal: hourlyMetal * perDay,
128
+ crystal: hourlyCrystal * perDay,
129
+ deut: hourlyDeut * perDay,
57
130
  },
58
131
  weekly: {
59
- metal: hourlyMetalNumber * 24 * 7,
60
- crystal: hourlyCristalNumber * 24 * 7,
61
- deut: hourlyDeutNumber * 24 * 7,
132
+ metal: hourlyMetal * perWeek,
133
+ crystal: hourlyCrystal * perWeek,
134
+ deut: hourlyDeut * perWeek,
62
135
  },
63
136
  },
64
137
  points: {
@@ -71,21 +144,7 @@ function parseInfoCompteData(data) {
71
144
  universe: Number(universe),
72
145
  lang,
73
146
  };
74
-
75
- for (const planet of planets) {
76
- const [planetMetal, crystal, deut, temperatureText] = planet[0].split('/');
77
- const [temperature] = temperatureText.split('°');
78
- const [thePlanet, metal] = planetMetal.split(':');
79
-
80
- response.planets.push({
81
- planet: thePlanet.trim(),
82
- metal: metal.trim().split(' ')[1],
83
- crystal: crystal.trim().split(' ')[1],
84
- deut: deut.trim().split(' ')[1],
85
- temperature: Number.parseInt(temperature.trim(), 10),
86
- });
87
- }
88
-
89
- return response;
90
147
  }
148
+
149
+ export { LOCALES };
91
150
  export default parseInfoCompteData;
@@ -1,41 +1,26 @@
1
+ import buildingInfo from './info.js';
2
+
1
3
  function getMineProduction(baseProduction, targetLevel, universeSpeed) {
2
4
  const levelFactor = 1.1 ** targetLevel;
3
5
 
4
6
  return Math.floor(baseProduction * targetLevel * levelFactor * universeSpeed);
5
7
  }
6
8
 
7
- function getEnergyCost(baseEnergyCost, targetLevel) {
8
- const level = targetLevel;
9
- const levelFactor = 1.1 ** level;
10
- return Math.floor(baseEnergyCost * level * levelFactor);
11
- }
12
-
13
- function getMetalCost(baseMetalCost, targetLevel) {
14
- const level = targetLevel - 1;
15
- return Math.floor(baseMetalCost * 1.5 ** level);
16
- }
17
-
18
- function getCrystalCost(baseCrystalCost, targetLevel) {
19
- const level = targetLevel - 1;
20
- return Math.floor(baseCrystalCost * 1.5 ** level);
21
- }
22
-
23
9
  /**
24
10
  *
25
- * Return information about the metal mine given a specific level
26
- * @param {object} mine The metal mine base information
27
- * @param {number} targetLevel
28
- * @param {number} universeSpeed production factor is increased for some universe
29
- * @returns {Object} informations about the metal mine at this specific level
11
+ * Return information about the metal mine at a given level
12
+ * @param {import('../types.js').BuildingEntry} mine The metal mine entry, `Buildings[1]`
13
+ * @param {number} targetLevel The level to reach, >= 1
14
+ * @param {number} [universeSpeed] Production is increased on faster universes
15
+ * @returns {import('../types.js').BuildingInfo} Cost, consumption and production at that level
30
16
  */
31
17
  function getMetalMine(mine, targetLevel, universeSpeed = 1) {
32
- return {
33
- crystal: getCrystalCost(mine.crystal, targetLevel),
34
- deuterium: 0,
35
- energy: getEnergyCost(mine.energy, targetLevel),
36
- metal: getMetalCost(mine.metal, targetLevel),
37
- production: getMineProduction(mine.production, targetLevel, universeSpeed),
38
- };
18
+ return buildingInfo(
19
+ mine,
20
+ targetLevel,
21
+ universeSpeed,
22
+ (base) => getMineProduction(base.production, targetLevel, universeSpeed),
23
+ );
39
24
  }
40
25
 
41
26
  export default getMetalMine;