ogamejs 2.1.6 → 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.
- package/README.md +485 -228
- package/package.json +71 -33
- package/src/buildings/buildTime.js +25 -0
- package/src/buildings/crystal.js +35 -0
- package/src/buildings/deut.js +31 -0
- package/src/buildings/fusion-reactor.js +35 -0
- package/src/buildings/index.js +28 -0
- package/src/buildings/info.js +42 -0
- package/src/buildings/infocompte.js +150 -0
- package/src/buildings/metal.js +26 -0
- package/src/buildings/production.js +150 -0
- package/src/buildings/solar-plant.js +25 -0
- package/src/buildings/storage.js +59 -0
- package/src/cost.js +70 -0
- package/src/fleets/combat.js +291 -0
- package/src/fleets/distance.js +39 -0
- package/src/fleets/flight.js +105 -0
- package/src/fleets/getDebris.js +21 -0
- package/src/fleets/index.js +19 -0
- package/src/fleets/speed.js +108 -0
- package/src/i18n.js +43 -0
- package/src/index.js +28 -0
- package/src/models/buildings.js +319 -0
- package/src/models/destroyable.js +929 -0
- package/src/models/research.js +169 -0
- package/src/research/index.js +9 -0
- package/src/research/researchTime.js +25 -0
- package/src/trades/crystal.js +16 -0
- package/src/trades/deut.js +16 -0
- package/src/trades/index.js +13 -0
- package/src/trades/metal.js +16 -0
- package/src/trades/parseRate.js +49 -0
- package/src/trades/utils.js +9 -0
- package/src/types.js +146 -0
- package/types/buildings/buildTime.d.ts +16 -0
- package/types/buildings/buildTime.d.ts.map +1 -0
- package/types/buildings/crystal.d.ts +12 -0
- package/types/buildings/crystal.d.ts.map +1 -0
- package/types/buildings/deut.d.ts +12 -0
- package/types/buildings/deut.d.ts.map +1 -0
- package/types/buildings/fusion-reactor.d.ts +15 -0
- package/types/buildings/fusion-reactor.d.ts.map +1 -0
- package/types/buildings/index.d.ts +27 -0
- package/types/buildings/index.d.ts.map +1 -0
- package/types/buildings/info.d.ts +17 -0
- package/types/buildings/info.d.ts.map +1 -0
- package/types/buildings/infocompte.d.ts +41 -0
- package/types/buildings/infocompte.d.ts.map +1 -0
- package/types/buildings/metal.d.ts +11 -0
- package/types/buildings/metal.d.ts.map +1 -0
- package/types/buildings/production.d.ts +78 -0
- package/types/buildings/production.d.ts.map +1 -0
- package/types/buildings/solar-plant.d.ts +10 -0
- package/types/buildings/solar-plant.d.ts.map +1 -0
- package/types/buildings/storage.d.ts +29 -0
- package/types/buildings/storage.d.ts.map +1 -0
- package/types/cost.d.ts +25 -0
- package/types/cost.d.ts.map +1 -0
- package/types/fleets/combat.d.ts +57 -0
- package/types/fleets/combat.d.ts.map +1 -0
- package/types/fleets/distance.d.ts +13 -0
- package/types/fleets/distance.d.ts.map +1 -0
- package/types/fleets/flight.d.ts +57 -0
- package/types/fleets/flight.d.ts.map +1 -0
- package/types/fleets/getDebris.d.ts +12 -0
- package/types/fleets/getDebris.d.ts.map +1 -0
- package/types/fleets/index.d.ts +18 -0
- package/types/fleets/index.d.ts.map +1 -0
- package/types/fleets/speed.d.ts +54 -0
- package/types/fleets/speed.d.ts.map +1 -0
- package/types/i18n.d.ts +23 -0
- package/types/i18n.d.ts.map +1 -0
- package/types/index.d.ts +1316 -0
- package/types/index.d.ts.map +1 -0
- package/types/models/buildings.d.ts +373 -0
- package/types/models/buildings.d.ts.map +1 -0
- package/types/models/destroyable.d.ts +695 -0
- package/types/models/destroyable.d.ts.map +1 -0
- package/types/models/research.d.ts +262 -0
- package/types/models/research.d.ts.map +1 -0
- package/types/research/index.d.ts +8 -0
- package/types/research/index.d.ts.map +1 -0
- package/types/research/researchTime.d.ts +16 -0
- package/types/research/researchTime.d.ts.map +1 -0
- package/types/trades/crystal.d.ts +6 -0
- package/types/trades/crystal.d.ts.map +1 -0
- package/types/trades/deut.d.ts +6 -0
- package/types/trades/deut.d.ts.map +1 -0
- package/types/trades/index.d.ts +12 -0
- package/types/trades/index.d.ts.map +1 -0
- package/types/trades/metal.d.ts +6 -0
- package/types/trades/metal.d.ts.map +1 -0
- package/types/trades/parseRate.d.ts +2 -0
- package/types/trades/parseRate.d.ts.map +1 -0
- package/types/trades/utils.d.ts +2 -0
- package/types/trades/utils.d.ts.map +1 -0
- package/types/types.d.ts +449 -0
- package/types/types.d.ts.map +1 -0
- package/CHANGELOG.md +0 -50
- package/build/buildings/crystal.js +0 -61
- package/build/buildings/crystal.test.js +0 -32
- package/build/buildings/deut.js +0 -56
- package/build/buildings/deut.test.js +0 -32
- package/build/buildings/fusion-reactor.js +0 -60
- package/build/buildings/fusion-reactor.test.js +0 -22
- package/build/buildings/index.js +0 -31
- package/build/buildings/infocompte.js +0 -150
- package/build/buildings/infocompte.test.js +0 -107
- package/build/buildings/metal.js +0 -50
- package/build/buildings/metal.test.js +0 -32
- package/build/buildings/solar-plant.js +0 -42
- package/build/buildings/solar-plant.test.js +0 -21
- package/build/fleets/getDebris.js +0 -27
- package/build/fleets/getDebris.test.js +0 -26
- package/build/fleets/index.js +0 -16
- package/build/index.js +0 -30
- package/build/models/buildings.js +0 -65
- package/build/models/destroyable.js +0 -730
- package/build/trades/crystal.js +0 -33
- package/build/trades/crystal.test.js +0 -16
- package/build/trades/deut.js +0 -33
- package/build/trades/deut.test.js +0 -16
- package/build/trades/index.js +0 -25
- package/build/trades/metal.js +0 -33
- package/build/trades/metal.test.js +0 -16
- package/build/trades/parseRate.js +0 -60
- package/build/trades/parseRate.test.js +0 -40
- package/build/trades/trades.js +0 -119
- package/build/trades/trades.test.js +0 -65
- package/build/trades/utils.js +0 -17
- package/build/trades/utils.test.js +0 -16
- package/build/winston.js +0 -16
package/package.json
CHANGED
|
@@ -1,17 +1,68 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ogamejs",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "",
|
|
5
|
-
"
|
|
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
|
+
"type": "module",
|
|
6
|
+
"main": "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
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=24"
|
|
52
|
+
},
|
|
6
53
|
"scripts": {
|
|
7
|
-
"start": "babel-node index.js",
|
|
8
54
|
"commit": "git-cz",
|
|
9
|
-
"build": "babel src -d build",
|
|
10
55
|
"lint": "eslint .",
|
|
11
|
-
"
|
|
56
|
+
"types": "tsc",
|
|
57
|
+
"prepack": "npm run types",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"test:watch": "vitest",
|
|
60
|
+
"release": "semantic-release"
|
|
12
61
|
},
|
|
13
62
|
"files": [
|
|
14
|
-
"
|
|
63
|
+
"src",
|
|
64
|
+
"types",
|
|
65
|
+
"!src/**/*.test.js"
|
|
15
66
|
],
|
|
16
67
|
"repository": {
|
|
17
68
|
"type": "git",
|
|
@@ -25,36 +76,23 @@
|
|
|
25
76
|
],
|
|
26
77
|
"author": "n.riquelmebareiro@gmail.com",
|
|
27
78
|
"license": "MIT",
|
|
28
|
-
"jest": {
|
|
29
|
-
"transform": {
|
|
30
|
-
"^.+\\.jsx?$": "babel-jest"
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
79
|
"devDependencies": {
|
|
34
|
-
"@
|
|
35
|
-
"@
|
|
36
|
-
"@
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"cz
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"eslint-plugin-react": "^7.19.0",
|
|
46
|
-
"eslint-plugin-security": "^1.4.0",
|
|
47
|
-
"git-cz": "^4.3.1",
|
|
48
|
-
"jest": "^25.3.0",
|
|
49
|
-
"regenerator-runtime": "^0.13.5",
|
|
50
|
-
"standard-version": "^8.0.1"
|
|
51
|
-
},
|
|
52
|
-
"dependencies": {
|
|
53
|
-
"lodash": "^4.17.15"
|
|
80
|
+
"@eslint/js": "^9.19.0",
|
|
81
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
82
|
+
"@semantic-release/git": "^10.0.1",
|
|
83
|
+
"cz-conventional-changelog": "^3.3.0",
|
|
84
|
+
"eslint": "^9.19.0",
|
|
85
|
+
"eslint-plugin-security": "^3.0.1",
|
|
86
|
+
"git-cz": "^4.9.0",
|
|
87
|
+
"globals": "^15.14.0",
|
|
88
|
+
"semantic-release": "^25.0.8",
|
|
89
|
+
"typescript": "^7.0.2",
|
|
90
|
+
"vitest": "^3.0.4"
|
|
54
91
|
},
|
|
55
92
|
"config": {
|
|
56
93
|
"commitizen": {
|
|
57
94
|
"path": "./node_modules/cz-conventional-changelog"
|
|
58
95
|
}
|
|
59
|
-
}
|
|
96
|
+
},
|
|
97
|
+
"types": "./types/index.d.ts"
|
|
60
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;
|
|
@@ -0,0 +1,35 @@
|
|
|
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 };
|
|
5
|
+
|
|
6
|
+
function getPositionFactor(pos) {
|
|
7
|
+
return POSITION_FACTOR[Number.parseInt(pos, 10)] ?? 1;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function getMineProduction(baseProduction, targetLevel, pos, universeSpeed) {
|
|
11
|
+
const positionFactor = getPositionFactor(pos);
|
|
12
|
+
const levelFactor = 1.1 ** targetLevel;
|
|
13
|
+
|
|
14
|
+
return Math.floor(baseProduction * targetLevel * levelFactor * universeSpeed * positionFactor);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
*
|
|
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
|
|
25
|
+
*/
|
|
26
|
+
function getCrystalMine(mine, targetLevel, pos, universeSpeed = 1) {
|
|
27
|
+
return buildingInfo(
|
|
28
|
+
mine,
|
|
29
|
+
targetLevel,
|
|
30
|
+
universeSpeed,
|
|
31
|
+
(base) => getMineProduction(base.production, targetLevel, pos, universeSpeed),
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default getCrystalMine;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import buildingInfo from './info.js';
|
|
2
|
+
|
|
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;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function getMineProduction(energyConsumption, avg, universeSpeed) {
|
|
9
|
+
return Math.floor(universeSpeed * energyConsumption * getTemperatureFactor(avg));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
*
|
|
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
|
|
20
|
+
*/
|
|
21
|
+
function getDeutSynth(mine, targetLevel, avg, universeSpeed = 1) {
|
|
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
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default getDeutSynth;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import buildingInfo from './info.js';
|
|
2
|
+
|
|
3
|
+
// Energy technology makes each reactor level a little more efficient.
|
|
4
|
+
function getEnergyFactor(energyTech) {
|
|
5
|
+
return 1.05 + 0.01 * energyTech;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function getEnergyProduction(baseProduction, targetLevel, energyTech) {
|
|
9
|
+
const factor = getEnergyFactor(energyTech) ** targetLevel;
|
|
10
|
+
|
|
11
|
+
return Math.floor(baseProduction * targetLevel * factor);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
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
|
|
25
|
+
*/
|
|
26
|
+
function getFusionReactor(reactor, targetLevel, energyTech, universeSpeed = 1) {
|
|
27
|
+
return buildingInfo(
|
|
28
|
+
reactor,
|
|
29
|
+
targetLevel,
|
|
30
|
+
universeSpeed,
|
|
31
|
+
(base) => getEnergyProduction(base.production, targetLevel, energyTech),
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default getFusionReactor;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import getCrystalMine from './crystal.js';
|
|
2
|
+
import getDeutSynth from './deut.js';
|
|
3
|
+
import getMetalMine from './metal.js';
|
|
4
|
+
import getSolarPlant from './solar-plant.js';
|
|
5
|
+
import getFusionReactor from './fusion-reactor.js';
|
|
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';
|
|
11
|
+
|
|
12
|
+
const Buildings = {
|
|
13
|
+
getCrystalMine,
|
|
14
|
+
getDeutSynth,
|
|
15
|
+
getMetalMine,
|
|
16
|
+
getSolarPlant,
|
|
17
|
+
getFusionReactor,
|
|
18
|
+
parseInfoCompteData,
|
|
19
|
+
getBuildingCost: getCost,
|
|
20
|
+
getBuildTime,
|
|
21
|
+
getStorage,
|
|
22
|
+
getStorageCapacity,
|
|
23
|
+
getStorageLevelFor,
|
|
24
|
+
getPlanetProduction,
|
|
25
|
+
getProductionBonus,
|
|
26
|
+
};
|
|
27
|
+
|
|
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;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Labels used by the Infocompte export, per game language.
|
|
3
|
+
*
|
|
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.
|
|
7
|
+
*/
|
|
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
|
+
});
|
|
26
|
+
|
|
27
|
+
function escapeRegExp(value) {
|
|
28
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Infocompte groups thousands with dots, spaces or non breaking spaces.
|
|
32
|
+
function toNumber(value = '') {
|
|
33
|
+
return Number(String(value).replace(/[^\d-]/g, ''));
|
|
34
|
+
}
|
|
35
|
+
|
|
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*(.+)`));
|
|
40
|
+
|
|
41
|
+
if (!match) {
|
|
42
|
+
throw new Error(`could not find ${what} in the report, is the language supported?`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return match[1];
|
|
46
|
+
}
|
|
47
|
+
|
|
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);
|
|
52
|
+
|
|
53
|
+
if (!numbers || numbers.length < 3) {
|
|
54
|
+
throw new Error(`could not parse ${what} in the report`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return numbers.slice(0, 3).map(toNumber);
|
|
58
|
+
}
|
|
59
|
+
|
|
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
|
+
);
|
|
69
|
+
|
|
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
|
+
}
|
|
78
|
+
|
|
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),
|
|
120
|
+
production: {
|
|
121
|
+
hourly: {
|
|
122
|
+
metal: hourlyMetal,
|
|
123
|
+
crystal: hourlyCrystal,
|
|
124
|
+
deut: hourlyDeut,
|
|
125
|
+
},
|
|
126
|
+
daily: {
|
|
127
|
+
metal: hourlyMetal * perDay,
|
|
128
|
+
crystal: hourlyCrystal * perDay,
|
|
129
|
+
deut: hourlyDeut * perDay,
|
|
130
|
+
},
|
|
131
|
+
weekly: {
|
|
132
|
+
metal: hourlyMetal * perWeek,
|
|
133
|
+
crystal: hourlyCrystal * perWeek,
|
|
134
|
+
deut: hourlyDeut * perWeek,
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
points: {
|
|
138
|
+
metal: metalPoint,
|
|
139
|
+
crystal: crystalPoint,
|
|
140
|
+
deut: deutPoint,
|
|
141
|
+
total: metalPoint + crystalPoint + deutPoint,
|
|
142
|
+
},
|
|
143
|
+
plasma: plasmaTechLevel,
|
|
144
|
+
universe: Number(universe),
|
|
145
|
+
lang,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export { LOCALES };
|
|
150
|
+
export default parseInfoCompteData;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import buildingInfo from './info.js';
|
|
2
|
+
|
|
3
|
+
function getMineProduction(baseProduction, targetLevel, universeSpeed) {
|
|
4
|
+
const levelFactor = 1.1 ** targetLevel;
|
|
5
|
+
|
|
6
|
+
return Math.floor(baseProduction * targetLevel * levelFactor * universeSpeed);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
*
|
|
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
|
|
16
|
+
*/
|
|
17
|
+
function getMetalMine(mine, targetLevel, universeSpeed = 1) {
|
|
18
|
+
return buildingInfo(
|
|
19
|
+
mine,
|
|
20
|
+
targetLevel,
|
|
21
|
+
universeSpeed,
|
|
22
|
+
(base) => getMineProduction(base.production, targetLevel, universeSpeed),
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default getMetalMine;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import getMetalMine from './metal.js';
|
|
2
|
+
import getCrystalMine from './crystal.js';
|
|
3
|
+
import getDeutSynth from './deut.js';
|
|
4
|
+
import BUILDINGS from '../models/buildings.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Plasma technology is worth more on metal than on crystal, and more on crystal
|
|
8
|
+
* than on deuterium.
|
|
9
|
+
*/
|
|
10
|
+
const PLASMA_BONUS = Object.freeze({ metal: 0.01, crystal: 0.0066, deuterium: 0.0033 });
|
|
11
|
+
|
|
12
|
+
/** The geologist officer adds a flat 10% to the three mines. */
|
|
13
|
+
const GEOLOGIST_BONUS = 0.1;
|
|
14
|
+
|
|
15
|
+
/** The collector class adds 25% to mine production. */
|
|
16
|
+
const COLLECTOR_BONUS = 0.25;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Every planet produces this much per hour on its own, mines or not. It is a
|
|
20
|
+
* flat income: no bonus applies to it.
|
|
21
|
+
*/
|
|
22
|
+
const BASE_INCOME = Object.freeze({ metal: 30, crystal: 15, deuterium: 0 });
|
|
23
|
+
|
|
24
|
+
const RESOURCES = Object.freeze(['metal', 'crystal', 'deuterium']);
|
|
25
|
+
|
|
26
|
+
function clampRatio(value, name) {
|
|
27
|
+
if (!Number.isFinite(value) || value < 0 || value > 1) {
|
|
28
|
+
throw new Error(`${name} must be a number between 0 and 1, received ${value}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
*
|
|
36
|
+
* Return the production multiplier of each resource
|
|
37
|
+
*
|
|
38
|
+
* The multipliers add up, they are not compounded: plasma 15, a geologist and
|
|
39
|
+
* the collector class give 1 + 0.15 + 0.1 + 0.25 on metal.
|
|
40
|
+
* @param {object} [options] The bonuses that apply
|
|
41
|
+
* @param {number} [options.plasmaTech] The Plasma Technology level
|
|
42
|
+
* @param {boolean} [options.geologist] Whether the geologist officer is hired
|
|
43
|
+
* @param {boolean} [options.collectorClass] Whether the player plays collector
|
|
44
|
+
* @param {object} [options.items] Booster items in place, as fractions per
|
|
45
|
+
* resource — a 30% metal booster is `{ metal: 0.3 }`
|
|
46
|
+
* @returns {import('../types.js').Resources} One multiplier per resource
|
|
47
|
+
*/
|
|
48
|
+
function getProductionBonus(options = {}) {
|
|
49
|
+
const {
|
|
50
|
+
plasmaTech = 0,
|
|
51
|
+
geologist = false,
|
|
52
|
+
collectorClass = false,
|
|
53
|
+
items = {},
|
|
54
|
+
} = options;
|
|
55
|
+
|
|
56
|
+
if (!Number.isInteger(plasmaTech) || plasmaTech < 0) {
|
|
57
|
+
throw new Error(`plasmaTech must be an integer >= 0, received ${plasmaTech}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const flat = (geologist ? GEOLOGIST_BONUS : 0) + (collectorClass ? COLLECTOR_BONUS : 0);
|
|
61
|
+
|
|
62
|
+
return Object.fromEntries(RESOURCES.map((resource) => {
|
|
63
|
+
/* eslint-disable security/detect-object-injection -- resource comes from RESOURCES */
|
|
64
|
+
const item = clampRatio(items[resource] ?? 0, `items.${resource}`);
|
|
65
|
+
|
|
66
|
+
return [resource, 1 + PLASMA_BONUS[resource] * plasmaTech + flat + item];
|
|
67
|
+
/* eslint-enable security/detect-object-injection */
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
*
|
|
73
|
+
* Return the hourly production of a whole planet, bonuses included
|
|
74
|
+
*
|
|
75
|
+
* Mine levels, planet position and temperature give the raw mine output; the
|
|
76
|
+
* plasma, officer, class and item bonuses are then applied, and the flat planet
|
|
77
|
+
* income is added on top.
|
|
78
|
+
* @param {object} planet The planet description
|
|
79
|
+
* @param {number} planet.metalMine The metal mine level
|
|
80
|
+
* @param {number} planet.crystalMine The crystal mine level
|
|
81
|
+
* @param {number} planet.deutSynth The deuterium synthesizer level
|
|
82
|
+
* @param {number} planet.position The planet position, 1 to 3 produce more crystal
|
|
83
|
+
* @param {number} planet.temperature The planet average temperature
|
|
84
|
+
* @param {number} [planet.universeSpeed] The universe economy speed
|
|
85
|
+
* @param {number} [planet.energyEfficiency] The share of the required energy that
|
|
86
|
+
* is actually available, between 0 and 1 — mines run at that rate when the
|
|
87
|
+
* planet is in an energy deficit
|
|
88
|
+
* @param {object} [bonuses] Passed straight to `getProductionBonus`
|
|
89
|
+
* @returns {import('../types.js').Resources & {energyConsumption: number, bonus: import('../types.js').Resources}}
|
|
90
|
+
* The hourly production, what it costs in energy, and the multipliers used
|
|
91
|
+
*/
|
|
92
|
+
function getPlanetProduction(planet, bonuses = {}) {
|
|
93
|
+
const {
|
|
94
|
+
metalMine,
|
|
95
|
+
crystalMine,
|
|
96
|
+
deutSynth,
|
|
97
|
+
position,
|
|
98
|
+
temperature,
|
|
99
|
+
universeSpeed = 1,
|
|
100
|
+
energyEfficiency = 1,
|
|
101
|
+
} = planet;
|
|
102
|
+
|
|
103
|
+
clampRatio(energyEfficiency, 'energyEfficiency');
|
|
104
|
+
|
|
105
|
+
for (const [name, level] of Object.entries({ metalMine, crystalMine, deutSynth })) {
|
|
106
|
+
if (!Number.isInteger(level) || level < 0) {
|
|
107
|
+
throw new Error(`${name} must be an integer >= 0, received ${level}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// A mine that is not built yet neither produces nor consumes anything, and
|
|
112
|
+
// has no level 0 cost to speak of.
|
|
113
|
+
const idle = { production: 0, energyConsumption: 0 };
|
|
114
|
+
const mines = {
|
|
115
|
+
metal: metalMine > 0 ? getMetalMine(BUILDINGS[1], metalMine, universeSpeed) : idle,
|
|
116
|
+
crystal: crystalMine > 0
|
|
117
|
+
? getCrystalMine(BUILDINGS[2], crystalMine, position, universeSpeed)
|
|
118
|
+
: idle,
|
|
119
|
+
deuterium: deutSynth > 0
|
|
120
|
+
? getDeutSynth(BUILDINGS[3], deutSynth, temperature, universeSpeed)
|
|
121
|
+
: idle,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const bonus = getProductionBonus(bonuses);
|
|
125
|
+
|
|
126
|
+
const production = Object.fromEntries(RESOURCES.map((resource) => {
|
|
127
|
+
/* eslint-disable security/detect-object-injection -- resource comes from RESOURCES */
|
|
128
|
+
const mined = mines[resource].production * bonus[resource] * energyEfficiency;
|
|
129
|
+
|
|
130
|
+
return [resource, Math.floor(mined) + BASE_INCOME[resource] * universeSpeed];
|
|
131
|
+
/* eslint-enable security/detect-object-injection */
|
|
132
|
+
}));
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
...production,
|
|
136
|
+
energyConsumption: RESOURCES
|
|
137
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
138
|
+
.reduce((total, resource) => total + mines[resource].energyConsumption, 0),
|
|
139
|
+
bonus,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export {
|
|
144
|
+
getProductionBonus,
|
|
145
|
+
PLASMA_BONUS,
|
|
146
|
+
GEOLOGIST_BONUS,
|
|
147
|
+
COLLECTOR_BONUS,
|
|
148
|
+
BASE_INCOME,
|
|
149
|
+
};
|
|
150
|
+
export default getPlanetProduction;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import buildingInfo from './info.js';
|
|
2
|
+
|
|
3
|
+
function getEnergyProduction(baseProduction, targetLevel) {
|
|
4
|
+
const levelFactor = 1.1 ** targetLevel;
|
|
5
|
+
|
|
6
|
+
return Math.floor(baseProduction * targetLevel * levelFactor);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
*
|
|
11
|
+
* Return information about the solar plant at a given level
|
|
12
|
+
* @param {import('../types.js').BuildingEntry} solarPlant The solar plant entry, `Buildings[4]`
|
|
13
|
+
* @param {number} targetLevel The level to reach, >= 1
|
|
14
|
+
* @returns {import('../types.js').BuildingInfo} Cost of the plant, and the energy it produces
|
|
15
|
+
*/
|
|
16
|
+
function getSolarPlant(solarPlant, targetLevel) {
|
|
17
|
+
return buildingInfo(
|
|
18
|
+
solarPlant,
|
|
19
|
+
targetLevel,
|
|
20
|
+
1,
|
|
21
|
+
(base) => getEnergyProduction(base.production, targetLevel),
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default getSolarPlant;
|