aminus 2.0.0 → 2.0.1

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 CHANGED
@@ -21,17 +21,14 @@ let ayaka = new StatTable(
21
21
  );
22
22
 
23
23
  const rotation = new Rotation(
24
- ["n1", dmg_formula("Cryo", "Normal", 0.84, new StatTable(), 3)],
25
- ["n2", dmg_formula("Cryo", "Normal", 0.894, new StatTable(), 2)],
26
- ["ca", dmg_formula("Cryo", "Charged", 3.039, new StatTable(), 2)],
27
- ["skill", dmg_formula("Cryo", "Skill", 4.07, new StatTable(), 2)],
28
- ["burstcuts", dmg_formula("Cryo", "Burst", 1.91, new StatTable(), 19)],
29
- ["burstexplosion", dmg_formula("Cryo", "Burst", 2.86, new StatTable(), 1)],
24
+ ["n1", dmg_formula("Cryo", "Normal", 0.84, 3)],
25
+ ["n2", dmg_formula("Cryo", "Normal", 0.894, 2)],
26
+ ["ca", dmg_formula("Cryo", "Charged", 3.039, 2)],
27
+ ["skill", dmg_formula("Cryo", "Skill", 4.07, 2)],
28
+ ["burstcuts", dmg_formula("Cryo", "Burst", 1.91, 19)],
29
+ ["burstexplosion", dmg_formula("Cryo", "Burst", 2.86)],
30
30
  );
31
- let energyRechargeRequirement = 1.30;
32
-
33
- ayaka = ayaka.merge(optimalKqmc5ArtifactsStats(ayaka, rotation, energyRechargeRequirement));
34
-
31
+ ayaka = ayaka.merge(optimalKqmc5ArtifactsStats(ayaka, rotation, 1.30));
35
32
  const dps = rotation.execute(ayaka) / 21.0;
36
33
  ```
37
34
 
@@ -13,6 +13,9 @@ export declare class ArtifactBuilder {
13
13
  rolls: Map<[StatType, ArtifactRollQuality, number], number>;
14
14
  constraints: Map<[StatType, number], number>;
15
15
  rollLimit?: number;
16
+ private findRollKey;
17
+ private getConstraint;
18
+ private addConstraint;
16
19
  constructor(flower?: Artifact, feather?: Artifact, sands?: Artifact, goblet?: Artifact, circlet?: Artifact);
17
20
  private initConstraints;
18
21
  static kqmc(flower?: Artifact, feather?: Artifact, sands?: Artifact, goblet?: Artifact, circlet?: Artifact): ArtifactBuilder;
@@ -4,6 +4,27 @@ import { POSSIBLE_SUB_STATS, rollQualityMultiplier, maxRollsFor, maxRollsForGive
4
4
  * Way to build 1-5 artifact and distrubte substats accordingly
5
5
  */
6
6
  export class ArtifactBuilder {
7
+ findRollKey(statType, quality, rarity) {
8
+ for (const key of this.rolls.keys()) {
9
+ if (key[0] === statType && key[1] === quality && key[2] === rarity) {
10
+ return key;
11
+ }
12
+ }
13
+ return undefined;
14
+ }
15
+ getConstraint(statType, rarity) {
16
+ let sum = 0;
17
+ for (const [[stat, r], count] of this.constraints.entries()) {
18
+ if (stat === statType && r === rarity) {
19
+ sum += count;
20
+ }
21
+ }
22
+ return sum;
23
+ }
24
+ addConstraint(statType, rarity, amount) {
25
+ const key = [statType, rarity];
26
+ this.constraints.set(key, amount);
27
+ }
7
28
  //general constructor to building any set of artifacts
8
29
  constructor(flower, feather, sands, goblet, circlet) {
9
30
  this.rolls = new Map();
@@ -27,9 +48,8 @@ export class ArtifactBuilder {
27
48
  ].filter((p) => p !== undefined);
28
49
  for (const piece of pieces) {
29
50
  if (piece.main_stat !== stat) {
30
- const key = [stat, piece.rarity];
31
- const current = this.constraints.get(key) || 0;
32
- this.constraints.set(key, current + maxRollsForGiven(piece, stat));
51
+ const current = this.getConstraint(stat, piece.rarity);
52
+ this.addConstraint(stat, piece.rarity, current + maxRollsForGiven(piece, stat));
33
53
  }
34
54
  }
35
55
  }
@@ -54,8 +74,8 @@ export class ArtifactBuilder {
54
74
  for (const stat of POSSIBLE_SUB_STATS) {
55
75
  for (const piece of pieces) {
56
76
  if (piece.main_stat !== stat) {
57
- const key = [stat, piece.rarity];
58
- builder.constraints.set(key, (builder.constraints.get(key) || 0) + 2);
77
+ const current = builder.getConstraint(stat, piece.rarity);
78
+ builder.addConstraint(stat, piece.rarity, current + 2);
59
79
  }
60
80
  }
61
81
  }
@@ -65,8 +85,8 @@ export class ArtifactBuilder {
65
85
  // Roll 2 of each substat at AVG quality and rollRarity
66
86
  for (const stat of POSSIBLE_SUB_STATS) {
67
87
  builder.roll(stat, "AVG", rollRarity, 2);
68
- const key = [stat, rollRarity];
69
- builder.constraints.set(key, (builder.constraints.get(key) || 0) + 2);
88
+ const current = builder.getConstraint(stat, rollRarity);
89
+ builder.addConstraint(stat, rollRarity, current + 2);
70
90
  }
71
91
  return builder;
72
92
  }
@@ -172,21 +192,20 @@ export class ArtifactBuilder {
172
192
  const current = this.currentRollsForGiven(substatValue, quality, rarity);
173
193
  if (current + num > this.substatConstraint(substatValue, rarity))
174
194
  throw new Error("Exceeds constraint");
175
- const key = [
176
- substatValue,
177
- quality,
178
- rarity,
179
- ];
180
- this.rolls.set(key, (this.rolls.get(key) || 0) + num);
195
+ const existingKey = this.findRollKey(substatValue, quality, rarity);
196
+ if (existingKey) {
197
+ this.rolls.set(existingKey, (this.rolls.get(existingKey) || 0) + num);
198
+ return;
199
+ }
200
+ const key = [substatValue, quality, rarity];
201
+ this.rolls.set(key, num);
181
202
  }
182
203
  unroll(substatValue, quality, rarity, num) {
183
204
  if (!isValidSubstatType(substatValue))
184
205
  throw new Error("Invalid substat type");
185
- const key = [
186
- substatValue,
187
- quality,
188
- rarity,
189
- ];
206
+ const key = this.findRollKey(substatValue, quality, rarity);
207
+ if (!key)
208
+ return;
190
209
  const current = this.rolls.get(key) || 0;
191
210
  if (current >= num) {
192
211
  const newValue = current - num;
@@ -202,11 +221,9 @@ export class ArtifactBuilder {
202
221
  return Array.from(this.rolls.values()).reduce((sum, v) => sum + v, 0);
203
222
  }
204
223
  currentRollsForGiven(statType, quality, rarity) {
205
- const key = [
206
- statType,
207
- quality,
208
- rarity,
209
- ];
224
+ const key = this.findRollKey(statType, quality, rarity);
225
+ if (!key)
226
+ return 0;
210
227
  return this.rolls.get(key) || 0;
211
228
  }
212
229
  maxRolls() {
@@ -222,8 +239,7 @@ export class ArtifactBuilder {
222
239
  return pieces.reduce((sum, p) => sum + maxRollsFor(p), 0);
223
240
  }
224
241
  substatConstraint(statType, rarity) {
225
- const key = [statType, rarity];
226
- return this.constraints.get(key) || 0;
242
+ return this.getConstraint(statType, rarity);
227
243
  }
228
244
  rollsLeft() {
229
245
  return this.maxRolls() - this.currentRolls();
@@ -1,6 +1,5 @@
1
1
  import { ArtifactBuilder } from "./artifact-builder";
2
- import { ArtifactFactory } from "./artifact-factory";
3
2
  import { Artifact, ArtifactType } from "./artifact-constants";
4
3
  type ArtifactRollQuality = "MAX" | "HIGH" | "MID" | "LOW" | "AVG";
5
- export { ArtifactBuilder, ArtifactFactory };
4
+ export { ArtifactBuilder };
6
5
  export type { Artifact, ArtifactType, ArtifactRollQuality };
@@ -1,3 +1,2 @@
1
1
  import { ArtifactBuilder } from "./artifact-builder";
2
- import { ArtifactFactory } from "./artifact-factory";
3
- export { ArtifactBuilder, ArtifactFactory };
2
+ export { ArtifactBuilder };
@@ -8,5 +8,5 @@ declare const def_multiplier: (character_level: number, enemy_level: number, def
8
8
  declare const res_multiplier: (enemy_base_resistance: number, resistance_reduction: number) => number;
9
9
  declare const amplifier_multiplier: (amplifier: number, elemental_mastery: number, reaction_bonus: number) => number;
10
10
  declare const calculate_damage: (element: Element, damage_type: DamageType, scaling: BaseScaling, amplifier: Amplifier, instances: number, motion_value: number, character: StatTable, buffs: StatTable | undefined) => number;
11
- declare const dmg_formula: (element: Element, damage_type: DamageType, motion_value: number, buffs?: StatTable, instances?: number, scaling?: BaseScaling, amplifier?: Amplifier) => (s: StatTable) => number;
11
+ declare const dmg_formula: (element: Element, damage_type: DamageType, motion_value: number, instances?: number, buffs?: StatTable, scaling?: BaseScaling, amplifier?: Amplifier) => (s: StatTable) => number;
12
12
  export { default_damage_formula, avg_crit_multiplier, def_multiplier, res_multiplier, amplifier_multiplier, calculate_damage, total_attack, total_defense, total_health, dmg_formula, };
@@ -196,7 +196,7 @@ const calculate_damage = (element, damage_type, scaling, amplifier, instances, m
196
196
  }
197
197
  return default_damage_formula(instances, total_base_scaling_stat, motion_value, 1.0, 0.0, avg_crit_multiplier(total), total_dmg_bonus, 0.0, def_multiplier(90, 100, def_reduction, def_ignore), res_multiplier(0.1, resistance_reduction), amp_multiplier);
198
198
  };
199
- const dmg_formula = (element, damage_type, motion_value, buffs = new StatTable(), instances = 1, scaling = "ATK", amplifier = "None") => (s) => {
199
+ const dmg_formula = (element, damage_type, motion_value, instances = 1, buffs = new StatTable(), scaling = "ATK", amplifier = "None") => (s) => {
200
200
  const total = s.clone();
201
201
  if (buffs) {
202
202
  for (const [key, value] of buffs) {
@@ -1,2 +1,8 @@
1
1
  import { StatType, StatTable, Rotation } from "./stat";
2
- export declare function optimalMainStats(stats: StatTable, rotation: Rotation, getMainStatValue: (stat: StatType) => number): [StatType, StatType, StatType];
2
+ import { Artifact } from "./artifact-constants";
3
+ /** Public API */
4
+ type SubstatDistribution = Map<StatType, number>;
5
+ export declare function optimalMainStats(stats: StatTable, rotation: Rotation): [StatType, StatType, StatType];
6
+ export declare function gradient5StarKqmcArtifactSubstatOptimizer(stats: StatTable, target: Rotation, flower: Artifact | undefined, feather: Artifact | undefined, sands: Artifact | undefined, goblet: Artifact | undefined, circlet: Artifact | undefined, energyRechargeRequirement: number): SubstatDistribution;
7
+ export declare function optimalKqmc5ArtifactsStats(stats: StatTable, target: Rotation, energyRechargeRequirement: number): StatTable;
8
+ export {};
@@ -1,35 +1,90 @@
1
1
  import { StatTable } from "./stat";
2
- // Constants for possible main stats
3
- const POSSIBLE_SANDS_STATS = [
4
- "HPPercent",
5
- "ATKPercent",
6
- "DEFPercent",
7
- "ElementalMastery",
8
- "EnergyRecharge",
9
- ];
10
- const POSSIBLE_GOBLET_STATS = [
11
- "HPPercent",
12
- "ATKPercent",
13
- "DEFPercent",
14
- "ElementalMastery",
15
- "PyroDMGBonus",
16
- "CryoDMGBonus",
17
- "GeoDMGBonus",
18
- "DendroDMGBonus",
19
- "ElectroDMGBonus",
20
- "HydroDMGBonus",
21
- "AnemoDMGBonus",
22
- "PhysicalDMGBonus",
23
- ];
24
- const POSSIBLE_CIRCLE_STATS = [
25
- "HPPercent",
26
- "ATKPercent",
27
- "DEFPercent",
28
- "ElementalMastery",
29
- "CritRate",
30
- "CritDMG",
31
- "HealingBonus",
32
- ];
2
+ import { POSSIBLE_SANDS_STATS, POSSIBLE_GOBLET_STATS, POSSIBLE_CIRCLE_STATS, POSSIBLE_SUB_STATS, getMainStatValue, } from "./artifact-constants";
3
+ import { ArtifactBuilder } from "./artifact-builder";
4
+ export function optimalMainStats(stats, rotation) {
5
+ return globalKqmcArtifactMainStatOptimizer(stats, rotation);
6
+ }
7
+ export function gradient5StarKqmcArtifactSubstatOptimizer(stats, target, flower, feather, sands, goblet, circlet, energyRechargeRequirement) {
8
+ const builder = ArtifactBuilder.kqmc(flower, feather, sands, goblet, circlet);
9
+ while (true) {
10
+ const combinedStats = stats.merge(builder.build());
11
+ if (combinedStats.get("EnergyRecharge") >= energyRechargeRequirement) {
12
+ break;
13
+ }
14
+ if (builder.rollsLeft() <= 0 ||
15
+ builder.rollsLeftForGiven("EnergyRecharge", "AVG", 5) <= 0) {
16
+ throw new Error("Energy Recharge requirements cannot be met with substats alone");
17
+ }
18
+ builder.roll("EnergyRecharge", "AVG", 5, 1);
19
+ }
20
+ const possibleSubsToRoll = new Set(POSSIBLE_SUB_STATS);
21
+ while (builder.rollsLeft() > 0 && possibleSubsToRoll.size > 0) {
22
+ let bestSub = "None";
23
+ let bestValue = Number.NEGATIVE_INFINITY;
24
+ for (const substat of possibleSubsToRoll) {
25
+ if (builder.currentRollsForGiven(substat, "AVG", 5) >=
26
+ builder.substatConstraint(substat, 5)) {
27
+ continue;
28
+ }
29
+ builder.roll(substat, "AVG", 5, 1);
30
+ const value = target.execute(stats.merge(builder.build()));
31
+ builder.unroll(substat, "AVG", 5, 1);
32
+ if (value > bestValue) {
33
+ bestValue = value;
34
+ bestSub = substat;
35
+ }
36
+ }
37
+ if (bestSub === "None") {
38
+ break;
39
+ }
40
+ builder.roll(bestSub, "AVG", 5, 1);
41
+ }
42
+ const distribution = new Map();
43
+ for (const [[stat], count] of builder.rolls.entries()) {
44
+ distribution.set(stat, (distribution.get(stat) || 0) + count);
45
+ }
46
+ return distribution;
47
+ }
48
+ export function optimalKqmc5ArtifactsStats(stats, target, energyRechargeRequirement) {
49
+ const [sandsMain, gobletMain, circletMain] = globalKqmcArtifactMainStatOptimizer(stats, target);
50
+ const flower = {
51
+ type: "flower",
52
+ rarity: 5,
53
+ level: 20,
54
+ main_stat: "FlatHP",
55
+ };
56
+ const feather = {
57
+ type: "feather",
58
+ rarity: 5,
59
+ level: 20,
60
+ main_stat: "FlatATK",
61
+ };
62
+ const sands = {
63
+ type: "sands",
64
+ rarity: 5,
65
+ level: 20,
66
+ main_stat: sandsMain,
67
+ };
68
+ const goblet = {
69
+ type: "goblet",
70
+ rarity: 5,
71
+ level: 20,
72
+ main_stat: gobletMain,
73
+ };
74
+ const circlet = {
75
+ type: "circlet",
76
+ rarity: 5,
77
+ level: 20,
78
+ main_stat: circletMain,
79
+ };
80
+ const builder = new ArtifactBuilder(flower, feather, sands, goblet, circlet);
81
+ const optimalSubstats = gradient5StarKqmcArtifactSubstatOptimizer(stats, target, flower, feather, sands, goblet, circlet, energyRechargeRequirement);
82
+ for (const [stat, count] of optimalSubstats.entries()) {
83
+ builder.roll(stat, "AVG", 5, count);
84
+ }
85
+ return stats.merge(builder.build());
86
+ }
87
+ /** Ecapsulated implementation */
33
88
  // Computes gradients of stats based on slopes
34
89
  function statGradients(base, target, slopes) {
35
90
  const gradients = new Map();
@@ -58,7 +113,7 @@ function reluHeuristic(base, target, slopes) {
58
113
  return effectiveSet;
59
114
  }
60
115
  // Finds best artifact main stat combo for given stats and rotation
61
- function globalKqmcArtifactMainStatOptimizer(stats, target, getMainStatValue) {
116
+ function globalKqmcArtifactMainStatOptimizer(stats, target) {
62
117
  const sandsStats = new Set(POSSIBLE_SANDS_STATS);
63
118
  const gobletStats = new Set(POSSIBLE_GOBLET_STATS);
64
119
  const circletStats = new Set(POSSIBLE_CIRCLE_STATS);
@@ -95,6 +150,3 @@ function globalKqmcArtifactMainStatOptimizer(stats, target, getMainStatValue) {
95
150
  }
96
151
  return bestCombo;
97
152
  }
98
- export function optimalMainStats(stats, rotation, getMainStatValue) {
99
- return globalKqmcArtifactMainStatOptimizer(stats, rotation, getMainStatValue);
100
- }
@@ -22,7 +22,8 @@ type DamageCompute = (s: StatTable) => number;
22
22
  declare const compose: (...funcs: DamageCompute[]) => DamageCompute;
23
23
  declare class Rotation {
24
24
  actions: [string, DamageCompute][];
25
- constructor(actions: [string, DamageCompute][] | DamageCompute[]);
25
+ constructor(actions: ([string, DamageCompute] | DamageCompute)[]);
26
+ constructor(...actions: ([string, DamageCompute] | DamageCompute)[]);
26
27
  add(action: [string, DamageCompute]): void;
27
28
  execute(s: StatTable): number;
28
29
  }
@@ -148,15 +148,21 @@ const compose = (...funcs) => {
148
148
  return (s) => funcs.reduce((acc, fn) => acc + fn(s), 0);
149
149
  };
150
150
  class Rotation {
151
- constructor(actions) {
151
+ constructor(...args) {
152
152
  this.actions = [];
153
- if (actions.length > 0 && typeof actions[0] === "function") {
154
- for (let compute of actions) {
153
+ // Support both `new Rotation([...])` and `new Rotation(a, b, c)`.
154
+ const first = args[0];
155
+ const isSingleArrayArg = args.length === 1 && Array.isArray(first);
156
+ const normalized = isSingleArrayArg
157
+ ? first
158
+ : args;
159
+ if (normalized.length > 0 && typeof normalized[0] === "function") {
160
+ for (let compute of normalized) {
155
161
  this.actions.push(["", compute]);
156
162
  }
157
163
  }
158
164
  else {
159
- this.actions = actions;
165
+ this.actions = normalized;
160
166
  }
161
167
  }
162
168
  add(action) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aminus",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -286,8 +286,8 @@ const dmg_formula =
286
286
  element: Element,
287
287
  damage_type: DamageType,
288
288
  motion_value: number,
289
- buffs: StatTable = new StatTable(),
290
289
  instances = 1,
290
+ buffs: StatTable = new StatTable(),
291
291
  scaling: BaseScaling = "ATK",
292
292
  amplifier: Amplifier = "None",
293
293
  ) =>
@@ -19,17 +19,14 @@ describe("Damage Calculation Integration", () => {
19
19
  );
20
20
 
21
21
  const rotation = new Rotation(
22
- ["n1", dmg_formula("Cryo", "Normal", 0.84, new StatTable(), 3)],
23
- ["n2", dmg_formula("Cryo", "Normal", 0.894, new StatTable(), 2)],
24
- ["ca", dmg_formula("Cryo", "Charged", 3.039, new StatTable(), 2)],
25
- ["skill", dmg_formula("Cryo", "Skill", 4.07, new StatTable(), 2)],
26
- ["burstcuts", dmg_formula("Cryo", "Burst", 1.91, new StatTable(), 19)],
27
- ["burstexplosion", dmg_formula("Cryo", "Burst", 2.86, new StatTable(), 1)],
22
+ ["n1", dmg_formula("Cryo", "Normal", 0.84, 3)],
23
+ ["n2", dmg_formula("Cryo", "Normal", 0.894, 2)],
24
+ ["ca", dmg_formula("Cryo", "Charged", 3.039, 2)],
25
+ ["skill", dmg_formula("Cryo", "Skill", 4.07, 2)],
26
+ ["burstcuts", dmg_formula("Cryo", "Burst", 1.91, 19)],
27
+ ["burstexplosion", dmg_formula("Cryo", "Burst", 2.86)],
28
28
  );
29
- let energyRechargeRequirement = 1.3;
30
-
31
- ayaka = ayaka.merge(optimalKqmc5ArtifactsStats(ayaka, rotation, energyRechargeRequirement));
32
-
29
+ ayaka = ayaka.merge(optimalKqmc5ArtifactsStats(ayaka, rotation, 1.30));
33
30
  const dps = rotation.execute(ayaka) / 21.0;
34
31
 
35
32
  expect(dps).toBeGreaterThan(0);