ogamejs 4.0.2 → 4.2.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.
@@ -0,0 +1,124 @@
1
+ /**
2
+ * The maximum metal an expedition can find, tiered by the score of the
3
+ * universe's top player. `below` is an exclusive upper bound; above the last
4
+ * tier every universe shares `EXPEDITION_TOP_TIER_BASE`.
5
+ */
6
+ const EXPEDITION_FIND_TIERS = Object.freeze([
7
+ Object.freeze({ below: 1e4, base: 40000 }),
8
+ Object.freeze({ below: 1e5, base: 500000 }),
9
+ Object.freeze({ below: 1e6, base: 1200000 }),
10
+ Object.freeze({ below: 5e6, base: 1800000 }),
11
+ Object.freeze({ below: 25e6, base: 2400000 }),
12
+ Object.freeze({ below: 50e6, base: 3000000 }),
13
+ Object.freeze({ below: 75e6, base: 3600000 }),
14
+ Object.freeze({ below: 100e6, base: 4200000 }),
15
+ ]);
16
+ const EXPEDITION_TOP_TIER_BASE = 5000000;
17
+
18
+ /** The Discoverer class bonus on expedition finds, `explorerBonusIncreasedExpeditionOutcome` in serverData. */
19
+ const DEFAULT_EXPLORER_BONUS = 0.5;
20
+
21
+ /** The share of base cargo each Hyperspace Technology level adds, `cargoHyperspaceTechMultiplier` in serverData. */
22
+ const DEFAULT_HYPERSPACE_MULTIPLIER = 5;
23
+
24
+ function assertBonus(name, value) {
25
+ if (!Number.isFinite(value) || value < 0) {
26
+ throw new Error(`${name} must be a number >= 0, received ${value}`);
27
+ }
28
+ }
29
+
30
+ /**
31
+ *
32
+ * Return the expedition find base of a universe, before any multiplier
33
+ * @param {number} topScore The points of the universe's top player
34
+ * @return {number} The base, in metal
35
+ */
36
+ function getExpeditionFindBase(topScore) {
37
+ if (!Number.isFinite(topScore) || topScore < 0) {
38
+ throw new Error(`topScore must be a number >= 0, received ${topScore}`);
39
+ }
40
+
41
+ const tier = EXPEDITION_FIND_TIERS.find(({ below }) => topScore < below);
42
+ return tier ? tier.base : EXPEDITION_TOP_TIER_BASE;
43
+ }
44
+
45
+ /**
46
+ *
47
+ * Return the largest metal find a single expedition can bring back
48
+ *
49
+ * `base * classFactor * (pathfinder ? 2 : 1) * (1 + lifeformResourceBonus)`
50
+ *
51
+ * - a Discoverer gets `classFactor = (1 + explorerBonus * (1 + lifeformExplorerBonus)) * economySpeed`:
52
+ * the lifeform class enhancement amplifies the class bonus itself, and only
53
+ * the Discoverer's finds scale with the economy speed;
54
+ * - any other class gets `classFactor = 1`.
55
+ *
56
+ * Crystal and deuterium finds are smaller than metal finds. Every bonus is a
57
+ * fraction: 0.2 for the 20 % the in-game lifeform bonus page shows.
58
+ * @param {object} options The universe and the player's bonuses
59
+ * @param {number} options.topScore The points of the universe's top player
60
+ * @param {number} options.economySpeed The universe economy speed
61
+ * @param {boolean} [options.explorer] Whether the player is a Discoverer
62
+ * @param {boolean} [options.pathfinder] Whether the fleet holds a Pathfinder
63
+ * @param {number} [options.explorerBonus] The Discoverer class bonus, 0.5 by default
64
+ * @param {number} [options.lifeformExplorerBonus] The lifeform Discoverer enhancement
65
+ * @param {number} [options.lifeformResourceBonus] The lifeform expedition resource bonus
66
+ * @return {number} The maximum find, in metal
67
+ */
68
+ function getExpeditionMaxFind({
69
+ topScore,
70
+ economySpeed,
71
+ explorer = false,
72
+ pathfinder = false,
73
+ explorerBonus = DEFAULT_EXPLORER_BONUS,
74
+ lifeformExplorerBonus = 0,
75
+ lifeformResourceBonus = 0,
76
+ }) {
77
+ if (!(economySpeed > 0)) {
78
+ throw new Error(`economySpeed must be greater than 0, received ${economySpeed}`);
79
+ }
80
+ assertBonus('explorerBonus', explorerBonus);
81
+ assertBonus('lifeformExplorerBonus', lifeformExplorerBonus);
82
+ assertBonus('lifeformResourceBonus', lifeformResourceBonus);
83
+
84
+ const classFactor = explorer
85
+ ? (1 + explorerBonus * (1 + lifeformExplorerBonus)) * economySpeed
86
+ : 1;
87
+
88
+ return getExpeditionFindBase(topScore) * classFactor * (pathfinder ? 2 : 1) * (1 + lifeformResourceBonus);
89
+ }
90
+
91
+ /**
92
+ *
93
+ * Return the cargo capacity of one ship, with its bonuses
94
+ *
95
+ * `ship.cargo * (1 + hyperspaceLevel * hyperspaceMultiplier / 100 + bonus)`:
96
+ * every bonus is a share of the base cargo, so they add up.
97
+ * @param {import('../types.js').DestroyableEntry} ship An entry of models/destroyable.js
98
+ * @param {object} [options] The bonuses
99
+ * @param {number} [options.hyperspaceLevel] The Hyperspace Technology level
100
+ * @param {number} [options.hyperspaceMultiplier] The % of base cargo per level, 5 by default
101
+ * @param {number} [options.bonus] Any other bonus as a fraction: class (Collector 0.25 on cargo ships), lifeform…
102
+ * @return {number} The capacity
103
+ */
104
+ function getCargoCapacity(ship, {
105
+ hyperspaceLevel = 0,
106
+ hyperspaceMultiplier = DEFAULT_HYPERSPACE_MULTIPLIER,
107
+ bonus = 0,
108
+ } = {}) {
109
+ if (!Number.isInteger(hyperspaceLevel) || hyperspaceLevel < 0) {
110
+ throw new Error(`hyperspaceLevel must be an integer >= 0, received ${hyperspaceLevel}`);
111
+ }
112
+ assertBonus('hyperspaceMultiplier', hyperspaceMultiplier);
113
+ assertBonus('bonus', bonus);
114
+
115
+ return ship.cargo * (1 + (hyperspaceLevel * hyperspaceMultiplier) / 100 + bonus);
116
+ }
117
+
118
+ export {
119
+ getExpeditionFindBase,
120
+ getExpeditionMaxFind,
121
+ getCargoCapacity,
122
+ EXPEDITION_FIND_TIERS,
123
+ EXPEDITION_TOP_TIER_BASE,
124
+ };
@@ -2,7 +2,25 @@ import getDebris from './getDebris.js';
2
2
  import getDistance from './distance.js';
3
3
  import getShipSpeed, { getActiveDrive, getFleetSpeed } from './speed.js';
4
4
  import { getFlightTime, getFuelConsumption, getTrip } from './flight.js';
5
- import simulateCombat from './combat.js';
5
+ import simulateCombat, { DEFAULT_REPAIR_FACTOR, DEFAULT_PLUNDER_RATIO } from './combat.js';
6
+ import { getCombatStatistics, simulateCombats, DEFAULT_RUNS } from './combatStatistics.js';
7
+ import {
8
+ getWaveChance,
9
+ getMoonbreakWaves,
10
+ getMoonbreakChance,
11
+ getMoonbreakLosses,
12
+ MIN_MOON_SIZE,
13
+ MAX_MOON_SIZE,
14
+ WAVES_PER_ATTACKER,
15
+ } from './moonbreak.js';
16
+ import {
17
+ getExpeditionFindBase,
18
+ getExpeditionMaxFind,
19
+ getCargoCapacity,
20
+ EXPEDITION_FIND_TIERS,
21
+ EXPEDITION_TOP_TIER_BASE,
22
+ } from './expedition.js';
23
+ import { getMoonLockShips, MOON_DEBRIS_THRESHOLD, MAX_MOON_CHANCE } from './moonLock.js';
6
24
 
7
25
  const Fleets = {
8
26
  getDebris,
@@ -14,6 +32,29 @@ const Fleets = {
14
32
  getFuelConsumption,
15
33
  getTrip,
16
34
  simulateCombat,
35
+ simulateCombats,
36
+ getCombatStatistics,
37
+ getWaveChance,
38
+ getMoonbreakWaves,
39
+ getMoonbreakChance,
40
+ getMoonbreakLosses,
41
+ getExpeditionFindBase,
42
+ getExpeditionMaxFind,
43
+ getCargoCapacity,
44
+ getMoonLockShips,
45
+ };
46
+
47
+ export {
48
+ MIN_MOON_SIZE,
49
+ MAX_MOON_SIZE,
50
+ WAVES_PER_ATTACKER,
51
+ EXPEDITION_FIND_TIERS,
52
+ EXPEDITION_TOP_TIER_BASE,
53
+ MOON_DEBRIS_THRESHOLD,
54
+ MAX_MOON_CHANCE,
55
+ DEFAULT_REPAIR_FACTOR,
56
+ DEFAULT_PLUNDER_RATIO,
57
+ DEFAULT_RUNS,
17
58
  };
18
59
 
19
60
  export default Fleets;
@@ -0,0 +1,31 @@
1
+ /** The debris that gives a moon its maximum chance to appear. */
2
+ const MOON_DEBRIS_THRESHOLD = 2000000;
3
+
4
+ /** The maximum moon chance, in %, reached at `MOON_DEBRIS_THRESHOLD`. */
5
+ const MAX_MOON_CHANCE = 20;
6
+
7
+ /**
8
+ *
9
+ * Return how many ships must be destroyed on a position to reach the maximum moon chance
10
+ *
11
+ * Only metal and crystal land in the debris field here, and the universe keeps
12
+ * `debrisFactor` of them.
13
+ * @param {import('../types.js').DestroyableEntry} ship An entry of models/destroyable.js
14
+ * @param {number} debrisFactor The universe debris factor, e.g. 0.3 for 30%
15
+ * @param {number} [threshold] The debris to reach, 2 000 000 by default
16
+ * @return {number} The number of ships
17
+ */
18
+ function getMoonLockShips(ship, debrisFactor, threshold = MOON_DEBRIS_THRESHOLD) {
19
+ if (!(debrisFactor > 0)) {
20
+ throw new Error(`debrisFactor must be greater than 0, received ${debrisFactor}`);
21
+ }
22
+
23
+ const debris = (ship.cost.metal ?? 0) + (ship.cost.crystal ?? 0);
24
+ if (!(debris > 0)) {
25
+ throw new Error('the ship leaves no metal or crystal debris');
26
+ }
27
+
28
+ return Math.ceil(threshold / (debrisFactor * debris));
29
+ }
30
+
31
+ export { getMoonLockShips, MOON_DEBRIS_THRESHOLD, MAX_MOON_CHANCE };
@@ -0,0 +1,133 @@
1
+ /** A moon size, in km, can only be in this range. */
2
+ const MIN_MOON_SIZE = 3464;
3
+ const MAX_MOON_SIZE = 8944;
4
+
5
+ /** Each attacker spreads their Deathstars over this many waves. */
6
+ const WAVES_PER_ATTACKER = 6;
7
+
8
+ function assertMoonSize(moonSize) {
9
+ if (!Number.isFinite(moonSize) || moonSize < MIN_MOON_SIZE || moonSize > MAX_MOON_SIZE) {
10
+ throw new Error(`moonSize must be between ${MIN_MOON_SIZE} and ${MAX_MOON_SIZE}, received ${moonSize}`);
11
+ }
12
+ }
13
+
14
+ function assertFleets(fleets) {
15
+ if (!Array.isArray(fleets) || fleets.length === 0) {
16
+ throw new Error('fleets must be a non-empty array of Deathstar counts');
17
+ }
18
+ for (const rip of fleets) {
19
+ if (!Number.isInteger(rip) || rip < 0) {
20
+ throw new Error(`each fleet must be an integer >= 0, received ${rip}`);
21
+ }
22
+ }
23
+ }
24
+
25
+ /**
26
+ *
27
+ * Return the chance that a single wave of Deathstars destroys the moon
28
+ *
29
+ * `(100 - sqrt(moonSize)) * sqrt(rip) / 100`, capped at 1
30
+ * @param {number} moonSize The moon diameter, in km
31
+ * @param {number} rip The number of Deathstars in the wave
32
+ * @return {number} The chance, between 0 and 1
33
+ */
34
+ function getWaveChance(moonSize, rip) {
35
+ return Math.min(1, ((100 - Math.sqrt(moonSize)) * Math.sqrt(rip)) / 100);
36
+ }
37
+
38
+ /**
39
+ *
40
+ * Return the waves one attacker sends, in firing order
41
+ *
42
+ * The Deathstars are spread over 6 waves: when the count is not a multiple of 6,
43
+ * the first waves carry one extra ship. Under 6 Deathstars, the last waves are
44
+ * empty, which neither threaten the moon nor lose a ship.
45
+ * @param {number} rip The attacker's Deathstars
46
+ * @return {number[]} The size of each of the 6 waves
47
+ */
48
+ function getMoonbreakWaves(rip) {
49
+ const base = Math.floor(rip / WAVES_PER_ATTACKER);
50
+ const remainder = rip % WAVES_PER_ATTACKER;
51
+
52
+ return Array.from({ length: WAVES_PER_ATTACKER }, (_, i) => (i < remainder ? base + 1 : base));
53
+ }
54
+
55
+ /**
56
+ *
57
+ * Return the chance that a coordinated attack destroys the moon
58
+ *
59
+ * The attackers fire one after the other and the moon is gone as soon as one
60
+ * wave succeeds, so the attack fails only when every wave fails.
61
+ * @param {number} moonSize The moon diameter, in km (3464 to 8944)
62
+ * @param {number[]} fleets The Deathstars of each attacker, in firing order
63
+ * @return {number} The chance, between 0 and 1
64
+ */
65
+ function getMoonbreakChance(moonSize, fleets) {
66
+ assertMoonSize(moonSize);
67
+ assertFleets(fleets);
68
+
69
+ const failure = fleets
70
+ .flatMap(getMoonbreakWaves)
71
+ .reduce((acc, rip) => acc * (1 - getWaveChance(moonSize, rip)), 1);
72
+
73
+ return 1 - failure;
74
+ }
75
+
76
+ /**
77
+ *
78
+ * Return the expected Deathstar losses of a coordinated attack
79
+ *
80
+ * Each Deathstar of a wave is destroyed with probability `sqrt(moonSize) / 200`,
81
+ * and a wave only costs ships if every wave before it failed. The variance sums
82
+ * the per-wave binomial variances (the waves are treated as independent), and
83
+ * the spread is summarised as a gaussian around the mean: 1σ / 2σ / 3σ for the
84
+ * 68 / 95 / 99 % bands, clamped between 0 and the whole fleet.
85
+ * @param {number} moonSize The moon diameter, in km (3464 to 8944)
86
+ * @param {number[]} fleets The Deathstars of each attacker, in firing order
87
+ * @return {{
88
+ * mean: number, sigma: number,
89
+ * bands: { confidence: number, min: number, max: number }[],
90
+ * }} The losses, unrounded
91
+ */
92
+ function getMoonbreakLosses(moonSize, fleets) {
93
+ assertMoonSize(moonSize);
94
+ assertFleets(fleets);
95
+
96
+ const destructionRate = Math.sqrt(moonSize) / 200;
97
+ const total = fleets.reduce((acc, rip) => acc + rip, 0);
98
+
99
+ let mean = 0;
100
+ let variance = 0;
101
+ // The chance that every wave so far has failed, so that this one fires.
102
+ let reached = 1;
103
+
104
+ for (const size of fleets.flatMap(getMoonbreakWaves)) {
105
+ const p = destructionRate * reached;
106
+ mean += size * p;
107
+ variance += size * p * (1 - p);
108
+ reached *= 1 - getWaveChance(moonSize, size);
109
+ }
110
+
111
+ const sigma = Math.sqrt(variance);
112
+ const band = (confidence, sigmas) => ({
113
+ confidence,
114
+ min: Math.max(mean - sigmas * sigma, 0),
115
+ max: Math.min(mean + sigmas * sigma, total),
116
+ });
117
+
118
+ return {
119
+ mean,
120
+ sigma,
121
+ bands: [band(68, 1), band(95, 2), band(99, 3)],
122
+ };
123
+ }
124
+
125
+ export {
126
+ getWaveChance,
127
+ getMoonbreakWaves,
128
+ getMoonbreakChance,
129
+ getMoonbreakLosses,
130
+ MIN_MOON_SIZE,
131
+ MAX_MOON_SIZE,
132
+ WAVES_PER_ATTACKER,
133
+ };
@@ -1,19 +1,84 @@
1
1
  declare const ROUNDS = 6;
2
+ /** The share of destroyed defenses that come back after a battle, on most universes. */
3
+ declare const DEFAULT_REPAIR_FACTOR = 0.7;
4
+ /** The share of the defender's resources a winning attacker can carry away. */
5
+ declare const DEFAULT_PLUNDER_RATIO = 0.5;
2
6
  /**
3
7
  * A tiny deterministic PRNG (mulberry32), so a battle can be replayed.
4
8
  */
5
9
  declare function createRandom(seed: any): () => number;
10
+ export type CombatResult = {
11
+ /**
12
+ * Who won
13
+ */
14
+ winner: 'attacker' | 'defender' | 'draw';
15
+ /**
16
+ * How many rounds it lasted
17
+ */
18
+ rounds: number;
19
+ /**
20
+ * The PRNG seed it ran with
21
+ */
22
+ seed: number;
23
+ /**
24
+ * What is left of the attacker, and what it lost
25
+ */
26
+ attacker: {
27
+ survivors: import('../types.js').FleetEntry[];
28
+ losses: import('../types.js').FleetEntry[];
29
+ };
30
+ /**
31
+ * What is left of the defender, what it lost, and which of its lost defenses come back
32
+ */
33
+ defender: {
34
+ survivors: import('../types.js').FleetEntry[];
35
+ losses: import('../types.js').FleetEntry[];
36
+ rebuilt: import('../types.js').FleetEntry[];
37
+ };
38
+ /**
39
+ * The debris field
40
+ */
41
+ debris: import('../types.js').Resources;
42
+ /**
43
+ * The moon chance it gives, in %
44
+ */
45
+ moonChance: number;
46
+ /**
47
+ * What the attacker carries away
48
+ */
49
+ plunder: import('../types.js').Resources;
50
+ };
51
+ /**
52
+ * @typedef {object} CombatResult One simulated battle
53
+ * @property {'attacker'|'defender'|'draw'} winner Who won
54
+ * @property {number} rounds How many rounds it lasted
55
+ * @property {number} seed The PRNG seed it ran with
56
+ * @property {{survivors: import('../types.js').FleetEntry[], losses: import('../types.js').FleetEntry[]}} attacker
57
+ * What is left of the attacker, and what it lost
58
+ * @property {{
59
+ * survivors: import('../types.js').FleetEntry[], losses: import('../types.js').FleetEntry[],
60
+ * rebuilt: import('../types.js').FleetEntry[],
61
+ * }} defender What is left of the defender, what it lost, and which of its lost defenses come back
62
+ * @property {import('../types.js').Resources} debris The debris field
63
+ * @property {number} moonChance The moon chance it gives, in %
64
+ * @property {import('../types.js').Resources} plunder What the attacker carries away
65
+ */
6
66
  /**
7
67
  *
8
68
  * Simulate a battle between two fleets
9
69
  *
10
70
  * Follows the game rules: up to six rounds, every unit fires once per round at a
11
71
  * random enemy, rapid fire grants extra shots, shots below 1% of the target
12
- * shield bounce off, shields come back every round, and a unit under 70% hull
13
- * may explode at the end of the round.
72
+ * shield bounce off, shields come back every round, and a unit hit while under
73
+ * 70% hull may explode.
74
+ *
75
+ * Afterwards, each destroyed defense is rebuilt with a `repairFactor` chance,
76
+ * the debris field gives the moon chance (1% per 100 000, up to 20%), and a
77
+ * winning attacker loads what it can of the defender's resources when
78
+ * `plunder` says what lies on the planet.
14
79
  *
15
80
  * A battle is random, so one run is one possible outcome. The `seed` makes a run
16
- * reproducible; average several seeds to get a feel for the likely result.
81
+ * reproducible; `getCombatStatistics` averages several of them.
17
82
  * @param {object} attacker The attacking side
18
83
  * @param {import('../types.js').FleetEntry[]} attacker.fleet Its ships
19
84
  * @param {import('../types.js').CombatTechs} [attacker.techs] Its combat technology levels
@@ -22,13 +87,16 @@ declare function createRandom(seed: any): () => number;
22
87
  * @param {number} [options.seed] The PRNG seed, for a reproducible battle
23
88
  * @param {number} [options.debrisFactor] The universe debris factor
24
89
  * @param {number} [options.deuteriumDebrisFactor] The universe deuterium debris factor
25
- * @param {boolean} [options.defenseDebris] Whether destroyed defenses leave debris
26
- * @returns {{
27
- * winner: 'attacker'|'defender'|'draw', rounds: number, seed: number,
28
- * attacker: {survivors: import('../types.js').FleetEntry[], losses: import('../types.js').FleetEntry[]},
29
- * defender: {survivors: import('../types.js').FleetEntry[], losses: import('../types.js').FleetEntry[]},
30
- * debris: import('../types.js').Resources,
31
- * }} Who won, how long it took, what is left on each side, and the debris field
90
+ * @param {boolean} [options.defenseDebris] Whether destroyed defenses leave debris, at `debrisFactor`
91
+ * @param {number} [options.defenseDebrisFactor] The universe defense debris factor, overrides `defenseDebris`
92
+ * @param {number} [options.repairFactor] The chance for a destroyed defense to be rebuilt, 0.7 by default
93
+ * @param {object} [options.plunder] What the attacker may carry away if it wins
94
+ * @param {import('../types.js').Resources} options.plunder.resources The resources on the planet
95
+ * @param {number} [options.plunder.ratio] The share of them up for grabs, 0.5 by default
96
+ * @param {number} [options.plunder.hyperspaceLevel] The attacker's Hyperspace Technology level
97
+ * @param {number} [options.plunder.hyperspaceMultiplier] The % of base cargo per level, 5 by default
98
+ * @param {number} [options.plunder.bonus] Any other cargo bonus, as a fraction
99
+ * @returns {CombatResult} Who won, how long it took, what is left on each side, and what the battle left behind
32
100
  */
33
101
  declare function simulateCombat(attacker: {
34
102
  fleet: import('../types.js').FleetEntry[];
@@ -38,20 +106,16 @@ declare function simulateCombat(attacker: {
38
106
  debrisFactor?: number;
39
107
  deuteriumDebrisFactor?: number;
40
108
  defenseDebris?: boolean;
41
- }): {
42
- winner: 'attacker' | 'defender' | 'draw';
43
- rounds: number;
44
- seed: number;
45
- attacker: {
46
- survivors: import('../types.js').FleetEntry[];
47
- losses: import('../types.js').FleetEntry[];
109
+ defenseDebrisFactor?: number;
110
+ repairFactor?: number;
111
+ plunder?: {
112
+ resources: import('../types.js').Resources;
113
+ ratio?: number;
114
+ hyperspaceLevel?: number;
115
+ hyperspaceMultiplier?: number;
116
+ bonus?: number;
48
117
  };
49
- defender: {
50
- survivors: import('../types.js').FleetEntry[];
51
- losses: import('../types.js').FleetEntry[];
52
- };
53
- debris: import('../types.js').Resources;
54
- };
55
- export { createRandom, ROUNDS };
118
+ }): CombatResult;
119
+ export { createRandom, ROUNDS, DEFAULT_REPAIR_FACTOR, DEFAULT_PLUNDER_RATIO };
56
120
  export default simulateCombat;
57
121
  //# sourceMappingURL=combat.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"combat.d.ts","sourceRoot":"","sources":["../../src/fleets/combat.js"],"names":[],"mappings":"AAGA,QAAA,MAAM,MAAM,IAAI,CAAC;AAmCjB;;GAEG;AACH,iBAAS,YAAY,CAAC,IAAI,KAAA,gBAWzB;AAqJD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,iBAAS,cAAc,CAAC,QAAQ,EAf7B;IAAqD,KAAK,EAAlD,OAAO,aAAa,EAAE,UAAU,EAAE,CAC1C;IAAqD,KAAK,AAA1D,CACA,EADQ,OAAO,aAAa,EAAE,WAAW,CACzC;CAa6B,EAAE,QAAQ,EAb/B,MAa+B,EAAE,OAAO,AAZhD,CAKA,EAJA;IAAyB,IAAI,AAA7B,CACA,EADQ,MAAM,CACd;IAAyB,YAAY,AAArC,CACA,EADQ,MAAM,CACd;IAAyB,qBAAqB,AAA9C,CACA,EADQ,MAAM,CACd;IAA0B,aAAa,AAAvC,CACA,EADQ,OAAO,CACf;CAOqD,GAP3C;IACR,MAAM,EAAE,UAAU,GAAC,UAAU,GAAC,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IACnE,QAAQ,EAAE;QAAC,SAAS,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC;QAAC,MAAM,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAA;KAAC,CAAC;IACtG,QAAQ,EAAE;QAAC,SAAS,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC;QAAC,MAAM,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAA;KAAC,CAAC;IACtG,MAAM,EAAE,OAAO,aAAa,EAAE,SAAS,CAAC;CACzC,CA6DH;AAED,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC;eACjB,cAAc"}
1
+ {"version":3,"file":"combat.d.ts","sourceRoot":"","sources":["../../src/fleets/combat.js"],"names":[],"mappings":"AAKA,QAAA,MAAM,MAAM,IAAI,CAAC;AAcjB,wFAAwF;AACxF,QAAA,MAAM,qBAAqB,MAAM,CAAC;AAElC,+EAA+E;AAC/E,QAAA,MAAM,qBAAqB,MAAM,CAAC;AA0BlC;;GAEG;AACH,iBAAS,YAAY,CAAC,IAAI,KAAA,gBAWzB;AAuNE,YAAkB,YAAY,GAC9B;;;;IAAyC,MAAM,EAApC,UAAU,GAAC,UAAU,GAAC,MAAM,CACvC;;;;IAAmB,MAAM,EAAd,MAAM,CACjB;;;;IAAmB,IAAI,EAAZ,MAAM,CACjB;;;;IAAwG,QAAQ,EAArG;QAAC,SAAS,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC;QAAC,MAAM,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAA;KAAC,CAEtG;;;;IAGG,QAAQ,EAHA;QACT,SAAS,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC;QAAC,MAAM,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC;QAC1F,OAAO,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC;KAC7C,CACD;;;;IAA4C,MAAM,EAAvC,OAAO,aAAa,EAAE,SAAS,CAC1C;;;;IAAmB,UAAU,EAAlB,MAAM,CACjB;;;;IAA4C,OAAO,EAAxC,OAAO,aAAa,EAAE,SAAS,CAC5C;CAAA,CAAA;AAdD;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,iBAAS,cAAc,CAAC,QAAQ,EAlB7B;IAAqD,KAAK,EAAlD,OAAO,aAAa,EAAE,UAAU,EAAE,CAC1C;IAAqD,KAAK,AAA1D,CACA,EADQ,OAAO,aAAa,EAAE,WAAW,CACzC;CAgB6B,EAAE,QAAQ,EAhB/B,MAgB+B,EAAE,OAAO,AAfhD,CAaA,EAZA;IAAyB,IAAI,AAA7B,CACA,EADQ,MAAM,CACd;IAAyB,YAAY,AAArC,CACA,EADQ,MAAM,CACd;IAAyB,qBAAqB,AAA9C,CACA,EADQ,MAAM,CACd;IAA0B,aAAa,AAAvC,CACA,EADQ,OAAO,CACf;IAAyB,mBAAmB,AAA5C,CACA,EADQ,MAAM,CACd;IAAyB,YAAY,AAArC,CACA,EADQ,MAAM,CACd;IAAyB,OAAO,AAAhC,CAMA,EALA;QAAyD,SAAS,EAA1D,OAAO,aAAa,EAAE,SAAS,CACvC;QAAiC,KAAK,AAAtC,CACA,EADQ,MAAM,CACd;QAAiC,eAAe,AAAhD,CACA,EADQ,MAAM,CACd;QAAiC,oBAAoB,AAArD,CACA,EADQ,MAAM,CACd;QAAiC,KAAK,AAAtC,CACA,EADQ,MAAM,CACd;KAAA,CAAA;CAEqD,GAF3C,YAAY,CAsExB;AAED,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,CAAC;eAC/D,cAAc"}
@@ -0,0 +1,67 @@
1
+ import simulateCombat from './combat.js';
2
+ /** Enough runs for the averages to settle, without making a big battle wait. */
3
+ declare const DEFAULT_RUNS = 100;
4
+ /**
5
+ *
6
+ * Average a set of simulated battles
7
+ *
8
+ * Counts are averages, so they are not whole numbers. `lostResources` is what
9
+ * the units lost for good would cost to build again: every loss for the
10
+ * attacker, the defender's losses minus its rebuilt defenses.
11
+ * @param {import('./combat.js').CombatResult[]} results Battles from `simulateCombat`
12
+ * @returns {{
13
+ * runs: number,
14
+ * outcomes: {attacker: number, defender: number, draw: number},
15
+ * rounds: number,
16
+ * attacker: {
17
+ * survivors: import('../types.js').FleetEntry[], losses: import('../types.js').FleetEntry[],
18
+ * lostResources: import('../types.js').Resources,
19
+ * },
20
+ * defender: {
21
+ * survivors: import('../types.js').FleetEntry[], losses: import('../types.js').FleetEntry[],
22
+ * rebuilt: import('../types.js').FleetEntry[], lostResources: import('../types.js').Resources,
23
+ * },
24
+ * debris: import('../types.js').Resources,
25
+ * moonChance: number,
26
+ * plunder: import('../types.js').Resources,
27
+ * }} The share of battles each side won (from 0 to 1), and the average of everything else
28
+ */
29
+ declare function getCombatStatistics(results: import('./combat.js').CombatResult[]): {
30
+ runs: number;
31
+ outcomes: {
32
+ attacker: number;
33
+ defender: number;
34
+ draw: number;
35
+ };
36
+ rounds: number;
37
+ attacker: {
38
+ survivors: import('../types.js').FleetEntry[];
39
+ losses: import('../types.js').FleetEntry[];
40
+ lostResources: import('../types.js').Resources;
41
+ };
42
+ defender: {
43
+ survivors: import('../types.js').FleetEntry[];
44
+ losses: import('../types.js').FleetEntry[];
45
+ rebuilt: import('../types.js').FleetEntry[];
46
+ lostResources: import('../types.js').Resources;
47
+ };
48
+ debris: import('../types.js').Resources;
49
+ moonChance: number;
50
+ plunder: import('../types.js').Resources;
51
+ };
52
+ /**
53
+ *
54
+ * Simulate the same battle many times and average the outcomes
55
+ *
56
+ * Run `i` uses the seed `seed + i`, so the whole set is reproducible.
57
+ * @param {Parameters<typeof simulateCombat>[0]} attacker The attacking side, as for `simulateCombat`
58
+ * @param {Parameters<typeof simulateCombat>[1]} defender The defending side, as for `simulateCombat`
59
+ * @param {Parameters<typeof simulateCombat>[2] & {runs?: number}} [options] The `simulateCombat`
60
+ * options, plus `runs`, how many battles to simulate (100 by default)
61
+ * @returns {ReturnType<typeof getCombatStatistics>} The averaged outcome, see `getCombatStatistics`
62
+ */
63
+ declare function simulateCombats(attacker: Parameters<typeof simulateCombat>[0], defender: Parameters<typeof simulateCombat>[1], options?: Parameters<typeof simulateCombat>[2] & {
64
+ runs?: number;
65
+ }): ReturnType<typeof getCombatStatistics>;
66
+ export { getCombatStatistics, simulateCombats, DEFAULT_RUNS };
67
+ //# sourceMappingURL=combatStatistics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"combatStatistics.d.ts","sourceRoot":"","sources":["../../src/fleets/combatStatistics.js"],"names":[],"mappings":"AAAA,OAAO,cAAc,MAAM,aAAa,CAAC;AAEzC,gFAAgF;AAChF,QAAA,MAAM,YAAY,MAAM,CAAC;AAoEzB;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,iBAAS,mBAAmB,CAAC,OAAO,EAlBzB,OAAO,aAAa,EAAE,YAAY,EAkBT,GAjBvB;IACR,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,CAAC;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE;QACR,SAAS,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC;QAAC,MAAM,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC;QAC1F,aAAa,EAAE,OAAO,aAAa,EAAE,SAAS,CAAC;KAChD,CAAC;IACF,QAAQ,EAAE;QACR,SAAS,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC;QAAC,MAAM,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC;QAC1F,OAAO,EAAE,OAAO,aAAa,EAAE,UAAU,EAAE,CAAC;QAAC,aAAa,EAAE,OAAO,aAAa,EAAE,SAAS,CAAC;KAC7F,CAAC;IACF,MAAM,EAAE,OAAO,aAAa,EAAE,SAAS,CAAC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,aAAa,EAAE,SAAS,CAAC;CAC1C,CAiEH;AAED;;;;;;;;;;GAUG;AACH,iBAAS,eAAe,CAAC,QAAQ,EANtB,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC,CAAC,CAMb,EAAE,QAAQ,EALhC,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC,CAAC,CAKH,EAAE,OAAO,AAJjD,CAEA,EAFQ,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC,CAAC,CAAC,GAAG;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAIP,GAF5C,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAgBlD;AAED,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * The maximum metal an expedition can find, tiered by the score of the
3
+ * universe's top player. `below` is an exclusive upper bound; above the last
4
+ * tier every universe shares `EXPEDITION_TOP_TIER_BASE`.
5
+ */
6
+ declare const EXPEDITION_FIND_TIERS: readonly (Readonly<{
7
+ below: 10000;
8
+ base: 40000;
9
+ }> | Readonly<{
10
+ below: 100000;
11
+ base: 500000;
12
+ }> | Readonly<{
13
+ below: 1000000;
14
+ base: 1200000;
15
+ }> | Readonly<{
16
+ below: 5000000;
17
+ base: 1800000;
18
+ }> | Readonly<{
19
+ below: 25000000;
20
+ base: 2400000;
21
+ }> | Readonly<{
22
+ below: 50000000;
23
+ base: 3000000;
24
+ }> | Readonly<{
25
+ below: 75000000;
26
+ base: 3600000;
27
+ }> | Readonly<{
28
+ below: 100000000;
29
+ base: 4200000;
30
+ }>)[];
31
+ declare const EXPEDITION_TOP_TIER_BASE = 5000000;
32
+ /**
33
+ *
34
+ * Return the expedition find base of a universe, before any multiplier
35
+ * @param {number} topScore The points of the universe's top player
36
+ * @return {number} The base, in metal
37
+ */
38
+ declare function getExpeditionFindBase(topScore: number): number;
39
+ /**
40
+ *
41
+ * Return the largest metal find a single expedition can bring back
42
+ *
43
+ * `base * classFactor * (pathfinder ? 2 : 1) * (1 + lifeformResourceBonus)`
44
+ *
45
+ * - a Discoverer gets `classFactor = (1 + explorerBonus * (1 + lifeformExplorerBonus)) * economySpeed`:
46
+ * the lifeform class enhancement amplifies the class bonus itself, and only
47
+ * the Discoverer's finds scale with the economy speed;
48
+ * - any other class gets `classFactor = 1`.
49
+ *
50
+ * Crystal and deuterium finds are smaller than metal finds. Every bonus is a
51
+ * fraction: 0.2 for the 20 % the in-game lifeform bonus page shows.
52
+ * @param {object} options The universe and the player's bonuses
53
+ * @param {number} options.topScore The points of the universe's top player
54
+ * @param {number} options.economySpeed The universe economy speed
55
+ * @param {boolean} [options.explorer] Whether the player is a Discoverer
56
+ * @param {boolean} [options.pathfinder] Whether the fleet holds a Pathfinder
57
+ * @param {number} [options.explorerBonus] The Discoverer class bonus, 0.5 by default
58
+ * @param {number} [options.lifeformExplorerBonus] The lifeform Discoverer enhancement
59
+ * @param {number} [options.lifeformResourceBonus] The lifeform expedition resource bonus
60
+ * @return {number} The maximum find, in metal
61
+ */
62
+ declare function getExpeditionMaxFind({ topScore, economySpeed, explorer, pathfinder, explorerBonus, lifeformExplorerBonus, lifeformResourceBonus, }: {
63
+ topScore: number;
64
+ economySpeed: number;
65
+ explorer?: boolean;
66
+ pathfinder?: boolean;
67
+ explorerBonus?: number;
68
+ lifeformExplorerBonus?: number;
69
+ lifeformResourceBonus?: number;
70
+ }): number;
71
+ /**
72
+ *
73
+ * Return the cargo capacity of one ship, with its bonuses
74
+ *
75
+ * `ship.cargo * (1 + hyperspaceLevel * hyperspaceMultiplier / 100 + bonus)`:
76
+ * every bonus is a share of the base cargo, so they add up.
77
+ * @param {import('../types.js').DestroyableEntry} ship An entry of models/destroyable.js
78
+ * @param {object} [options] The bonuses
79
+ * @param {number} [options.hyperspaceLevel] The Hyperspace Technology level
80
+ * @param {number} [options.hyperspaceMultiplier] The % of base cargo per level, 5 by default
81
+ * @param {number} [options.bonus] Any other bonus as a fraction: class (Collector 0.25 on cargo ships), lifeform…
82
+ * @return {number} The capacity
83
+ */
84
+ declare function getCargoCapacity(ship: import('../types.js').DestroyableEntry, { hyperspaceLevel, hyperspaceMultiplier, bonus, }?: {
85
+ hyperspaceLevel?: number;
86
+ hyperspaceMultiplier?: number;
87
+ bonus?: number;
88
+ }): number;
89
+ export { getExpeditionFindBase, getExpeditionMaxFind, getCargoCapacity, EXPEDITION_FIND_TIERS, EXPEDITION_TOP_TIER_BASE, };
90
+ //# sourceMappingURL=expedition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expedition.d.ts","sourceRoot":"","sources":["../../src/fleets/expedition.js"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,QAAA,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;KASzB,CAAC;AACH,QAAA,MAAM,wBAAwB,UAAU,CAAC;AAczC;;;;;GAKG;AACH,iBAAS,qBAAqB,CAAC,QAAQ,EAH5B,MAG4B,GAF3B,MAAM,CASjB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,iBAAS,oBAAoB,CAAC,EAC5B,QAAQ,EACR,YAAY,EACZ,QAAgB,EAChB,UAAkB,EAClB,aAAsC,EACtC,qBAAyB,EACzB,qBAAyB,GAC1B,EAjBE;IAAwB,QAAQ,EAAxB,MAAM,CACd;IAAwB,YAAY,EAA5B,MAAM,CACd;IAA0B,QAAQ,AAAlC,CACA,EADQ,OAAO,CACf;IAA0B,UAAU,AAApC,CACA,EADQ,OAAO,CACf;IAAyB,aAAa,AAAtC,CACA,EADQ,MAAM,CACd;IAAyB,qBAAqB,AAA9C,CACA,EADQ,MAAM,CACd;IAAyB,qBAAqB,AAA9C,CACA,EADQ,MAAM,CACd;CAUF,GAVW,MAAM,CAuBjB;AAED;;;;;;;;;;;;GAYG;AACH,iBAAS,gBAAgB,CAAC,IAAI,EAPnB,OAAO,aAAa,EAAE,gBAOH,EAAE,EAC9B,eAAmB,EACnB,oBAAoD,EACpD,KAAS,GACV,AAVE,CAIA,EAHA;IAAyB,eAAe,AAAxC,CACA,EADQ,MAAM,CACd;IAAyB,oBAAoB,AAA7C,CACA,EADQ,MAAM,CACd;IAAyB,KAAK,AAA9B,CACA,EADQ,MAAM,CACd;CAMG,GANM,MAAM,CAcjB;AAED,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,EAChB,qBAAqB,EACrB,wBAAwB,GACzB,CAAC"}