mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-28 06:08:13 -05:00
Add damage multiplires for brellas+blasters, ensure each weapon only has one multiplier Closes #1044
This commit is contained in:
@@ -403,11 +403,7 @@ const damageTypeToParamsKey: Record<
|
||||
DIRECT: "DamageParam_ValueDirect",
|
||||
DIRECT_MIN: "DamageParam_ValueDirectMin",
|
||||
DIRECT_MAX: "DamageParam_ValueDirectMax",
|
||||
DISTANCE: [
|
||||
"BlastParam_DistanceDamage",
|
||||
"DistanceDamage_BlastParamArray",
|
||||
"DistanceDamage_BlastParamChase",
|
||||
],
|
||||
DISTANCE: ["BlastParam_DistanceDamage", "DistanceDamage_BlastParamArray"],
|
||||
SPLASH: ["BlastParam_SplashDamage", "DistanceDamage_SplashBlastParam"],
|
||||
SPLASH_MIN: "SwingUnitGroupParam_DamageParam_DamageMinValue",
|
||||
SPLASH_MAX: "SwingUnitGroupParam_DamageParam_DamageMaxValue",
|
||||
@@ -424,7 +420,7 @@ const damageTypeToParamsKey: Record<
|
||||
SPLATANA_HORIZONTAL: "DamageParam_SplatanaHorizontal",
|
||||
SPLATANA_HORIZONTAL_DIRECT: "DamageParam_SplatanaHorizontalDirect",
|
||||
BOMB_NORMAL: "DistanceDamage",
|
||||
BOMB_DIRECT: "DirectDamage",
|
||||
BOMB_DIRECT: ["DirectDamage", "DistanceDamage_BlastParamChase"],
|
||||
WAVE: "WaveDamage",
|
||||
SPECIAL_MAX_CHARGE: "ExhaleBlastParamMaxChargeDistanceDamage",
|
||||
SPECIAL_MIN_CHARGE: "ExhaleBlastParamMinChargeDistanceDamage",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { type MainWeaponId, mainWeaponIds } from "~/modules/in-game-lists";
|
||||
import type { DamageType } from "~/features/build-analyzer";
|
||||
import type objectDamages from "./core/object-dmg.json";
|
||||
import invariant from "tiny-invariant";
|
||||
import type { CombineWith } from "./calculator-types";
|
||||
import type { AnyWeapon, DamageType } from "../build-analyzer";
|
||||
import type objectDamages from "./core/object-dmg.json";
|
||||
|
||||
export const DAMAGE_RECEIVERS = [
|
||||
"Chariot", // Crab Tank
|
||||
@@ -20,152 +20,92 @@ export const DAMAGE_RECEIVERS = [
|
||||
"BulletUmbrellaCanopyWide", // Tenta Brella Canopy
|
||||
] as const;
|
||||
|
||||
export const objectDamageJsonKeyPriority: Record<
|
||||
keyof typeof objectDamages,
|
||||
Array<DamageType> | null
|
||||
> = {
|
||||
// added in chill season 2023
|
||||
Maneuver_SideStep_BombCore: null,
|
||||
// added in fresh season 2022
|
||||
BlowerExhale: null,
|
||||
Castle: ["BOMB_DIRECT", "SPECIAL_JUMP"],
|
||||
CoopEnemyRush: null,
|
||||
CoopEnemySakelienBomberBlast: null,
|
||||
CoopEnemySakerocketBlast: null,
|
||||
CoopEnemySakeSaucerFall: null,
|
||||
CoopEnemySakeTamaireBombDamage: null,
|
||||
GeyserOnline: null,
|
||||
RollerSplash_Wide: [
|
||||
"SPLASH_VERTICAL_MIN",
|
||||
"SPLASH_VERTICAL_MAX",
|
||||
"SPLASH_HORIZONTAL_MIN",
|
||||
"SPLASH_HORIZONTAL_MAX",
|
||||
],
|
||||
// original
|
||||
Blaster_BlasterMiddle: null,
|
||||
Blaster_BlasterShort: null,
|
||||
Blaster_KillOneShot: ["DIRECT"],
|
||||
Blaster: null,
|
||||
BlowerExhale_BombCore: ["SPECIAL_MAX_CHARGE", "SPECIAL_MIN_CHARGE"],
|
||||
BlowerInhale: null,
|
||||
BombFlower: null,
|
||||
Bomb_CurlingBullet: null,
|
||||
Bomb_DirectHit: ["BOMB_DIRECT"],
|
||||
Bomb_Fizzy: null,
|
||||
Bomb_Suction: null,
|
||||
Bomb_TorpedoBullet: null,
|
||||
Bomb_TorpedoSplashBurst: null,
|
||||
Bomb_Trap: null,
|
||||
Bomb: null,
|
||||
BrushCore: ["ROLL_OVER"],
|
||||
BrushSplash: ["SPLASH_MIN", "SPLASH_MAX"],
|
||||
CannonMissile: null,
|
||||
ChargerFull_Light: ["FULL_CHARGE"],
|
||||
ChargerFull_Long: ["FULL_CHARGE"],
|
||||
ChargerFull: ["FULL_CHARGE"],
|
||||
Charger_Light: ["MAX_CHARGE", "TAP_SHOT"],
|
||||
Charger_Long: ["MAX_CHARGE", "TAP_SHOT"],
|
||||
Charger: ["MAX_CHARGE", "TAP_SHOT"],
|
||||
Chariot_Body: ["SPECIAL_BUMP"],
|
||||
Chariot_Cannon: [
|
||||
"SPECIAL_BULLET_MIN",
|
||||
"SPECIAL_BULLET_MAX",
|
||||
"SPECIAL_CANNON",
|
||||
],
|
||||
Default: null,
|
||||
EnemyFlyingHohei_BombCore: null,
|
||||
GachihokoTimeUpBurst: null,
|
||||
Gachihoko_BombCore: null,
|
||||
Gachihoko_Bullet: null,
|
||||
Geyser: null,
|
||||
GoldenIkuraAttack: null,
|
||||
InkStormRain: null,
|
||||
InkStorm: ["SPECIAL_TICK"],
|
||||
Jetpack_BombCore: ["BOMB_DIRECT"],
|
||||
Jetpack_Bullet: ["BOMB_NORMAL"],
|
||||
Jetpack_Coop: null,
|
||||
Jetpack_Jet: null,
|
||||
Maneuver_Short: null,
|
||||
Maneuver: null,
|
||||
MicroLaser: ["SPECIAL_TICK"],
|
||||
MissionSalmonBuddy: null,
|
||||
MovePainter_Burst: null,
|
||||
MovePainter_Direct: null,
|
||||
MultiMissile_BombCore: ["BOMB_NORMAL"],
|
||||
MultiMissile_Bullet: null,
|
||||
NiceBall: ["SPECIAL_TICK"],
|
||||
ObjectEffect_Up: null,
|
||||
RollerCore: ["ROLL_OVER"],
|
||||
RollerSplash_Compact: [
|
||||
"SPLASH_VERTICAL_MIN",
|
||||
"SPLASH_VERTICAL_MAX",
|
||||
"SPLASH_HORIZONTAL_MIN",
|
||||
"SPLASH_HORIZONTAL_MAX",
|
||||
],
|
||||
RollerSplash_Heavy: [
|
||||
"SPLASH_VERTICAL_MIN",
|
||||
"SPLASH_VERTICAL_MAX",
|
||||
"SPLASH_HORIZONTAL_MIN",
|
||||
"SPLASH_HORIZONTAL_MAX",
|
||||
],
|
||||
RollerSplash_Hunter: [
|
||||
"SPLASH_VERTICAL_MIN",
|
||||
"SPLASH_VERTICAL_MAX",
|
||||
"SPLASH_HORIZONTAL_MIN",
|
||||
"SPLASH_HORIZONTAL_MAX",
|
||||
],
|
||||
RollerSplash: [
|
||||
"SPLASH_VERTICAL_MIN",
|
||||
"SPLASH_VERTICAL_MAX",
|
||||
"SPLASH_HORIZONTAL_MIN",
|
||||
"SPLASH_HORIZONTAL_MAX",
|
||||
],
|
||||
Saber_ChargeShot: ["SPLATANA_VERTICAL"],
|
||||
Saber_ChargeSlash: ["SPLATANA_VERTICAL_DIRECT"],
|
||||
Saber_Shot: ["SPLATANA_HORIZONTAL"],
|
||||
Saber_Slash: ["SPLATANA_HORIZONTAL_DIRECT"],
|
||||
Saber_ChargeSlash_Penetrate: null,
|
||||
SakerocketBullet: null,
|
||||
ShelterCanopy_Compact: null,
|
||||
ShelterCanopy_Wide: null,
|
||||
ShelterCanopy: null,
|
||||
ShelterShot_Compact: null,
|
||||
ShelterShot_Wide: null,
|
||||
ShelterShot: null,
|
||||
Shield: null,
|
||||
ShockSonar_Wave: ["WAVE"],
|
||||
Shooter_Blaze: ["NORMAL_MAX", "NORMAL_MIN"],
|
||||
Shooter_Expert: ["NORMAL_MAX", "NORMAL_MIN"],
|
||||
Shooter_First: ["NORMAL_MAX", "NORMAL_MIN"],
|
||||
Shooter_FlashRepeat: ["NORMAL_MAX", "NORMAL_MIN"],
|
||||
Shooter_Flash: ["NORMAL_MAX", "NORMAL_MIN"],
|
||||
Shooter_Gravity: ["NORMAL_MAX", "NORMAL_MIN"],
|
||||
Shooter_Heavy: ["NORMAL_MAX", "NORMAL_MIN"],
|
||||
Shooter_Long: ["NORMAL_MAX", "NORMAL_MIN"],
|
||||
Shooter_Precision: ["NORMAL_MAX", "NORMAL_MIN"],
|
||||
Shooter_Short: ["NORMAL_MAX", "NORMAL_MIN"],
|
||||
Shooter_TripleMiddle: ["NORMAL_MAX", "NORMAL_MIN"],
|
||||
Shooter_TripleQuick: ["NORMAL_MAX", "NORMAL_MIN"],
|
||||
Shooter: null,
|
||||
Skewer_Body: null,
|
||||
Skewer: ["BOMB_NORMAL"],
|
||||
Slosher_Bathtub: ["DIRECT", "DIRECT_MAX", "DIRECT_MIN", "DISTANCE", "SPLASH"],
|
||||
Slosher_Bear: ["DIRECT", "DIRECT_MAX", "DIRECT_MIN", "DISTANCE", "SPLASH"],
|
||||
Slosher_WashtubBombCore: ["DIRECT", "DIRECT_MAX", "DIRECT_MIN"],
|
||||
Slosher_Washtub: ["DISTANCE", "SPLASH"],
|
||||
Slosher: ["DIRECT", "DIRECT_MAX", "DIRECT_MIN", "DISTANCE", "SPLASH"],
|
||||
Spinner: ["NORMAL_MAX", "NORMAL_MIN", "NORMAL_MAX_FULL_CHARGE", "SPLASH"],
|
||||
Sprinkler: null,
|
||||
Stringer_Short: ["NORMAL_MAX", "NORMAL_MIN", "DISTANCE"],
|
||||
Stringer: ["NORMAL_MAX", "NORMAL_MIN", "DISTANCE"],
|
||||
SuperHook: ["BOMB_NORMAL"],
|
||||
SuperLanding: null,
|
||||
TripleTornado: null,
|
||||
UltraShot: ["BOMB_NORMAL", "BOMB_DIRECT"],
|
||||
UltraStamp_Swing: ["SPECIAL_SWING"],
|
||||
UltraStamp_Throw_BombCore: ["SPECIAL_THROW_DIRECT"],
|
||||
UltraStamp_Throw: ["SPECIAL_THROW"],
|
||||
};
|
||||
export const damagePriorities: Array<
|
||||
[
|
||||
AnyWeapon["type"],
|
||||
Array<AnyWeapon["id"]>,
|
||||
DamageType,
|
||||
keyof typeof objectDamages
|
||||
]
|
||||
> = [
|
||||
["MAIN", [210, 220, 260], "DIRECT", "Blaster_KillOneShot"],
|
||||
["MAIN", [210, 260], "DISTANCE", "Blaster_BlasterMiddle"],
|
||||
["MAIN", [220], "DISTANCE", "Blaster"],
|
||||
|
||||
["MAIN", [400], "NORMAL_MAX", "Shooter_Flash"], // xxx: TODO Squeezer autofire
|
||||
["MAIN", [400], "NORMAL_MIN", "Shooter_Flash"],
|
||||
|
||||
["MAIN", [1000], "SPLASH_VERTICAL_MIN", "RollerSplash_Compact"],
|
||||
["MAIN", [1000], "SPLASH_VERTICAL_MAX", "RollerSplash_Compact"],
|
||||
["MAIN", [1000], "SPLASH_HORIZONTAL_MIN", "RollerSplash_Compact"],
|
||||
["MAIN", [1000], "SPLASH_HORIZONTAL_MAX", "RollerSplash_Compact"],
|
||||
["MAIN", [1000, 1010, 1020, 1030, 1040], "ROLL_OVER", "RollerCore"],
|
||||
["MAIN", [1010], "SPLASH_VERTICAL_MIN", "RollerSplash"],
|
||||
["MAIN", [1010], "SPLASH_VERTICAL_MAX", "RollerSplash"],
|
||||
["MAIN", [1010], "SPLASH_HORIZONTAL_MIN", "RollerSplash"],
|
||||
["MAIN", [1010], "SPLASH_HORIZONTAL_MAX", "RollerSplash"],
|
||||
["MAIN", [1020], "SPLASH_VERTICAL_MIN", "RollerSplash_Heavy"],
|
||||
["MAIN", [1020], "SPLASH_VERTICAL_MAX", "RollerSplash_Heavy"],
|
||||
["MAIN", [1020], "SPLASH_HORIZONTAL_MIN", "RollerSplash_Heavy"],
|
||||
["MAIN", [1020], "SPLASH_HORIZONTAL_MAX", "RollerSplash_Heavy"],
|
||||
["MAIN", [1030], "SPLASH_VERTICAL_MIN", "RollerSplash_Hunter"],
|
||||
["MAIN", [1030], "SPLASH_VERTICAL_MAX", "RollerSplash_Hunter"],
|
||||
["MAIN", [1030], "SPLASH_HORIZONTAL_MIN", "RollerSplash_Hunter"],
|
||||
["MAIN", [1030], "SPLASH_HORIZONTAL_MAX", "RollerSplash_Hunter"],
|
||||
["MAIN", [1040], "SPLASH_VERTICAL_MIN", "RollerSplash_Wide"],
|
||||
["MAIN", [1040], "SPLASH_VERTICAL_MAX", "RollerSplash_Wide"],
|
||||
["MAIN", [1040], "SPLASH_HORIZONTAL_MIN", "RollerSplash_Wide"],
|
||||
["MAIN", [1040], "SPLASH_HORIZONTAL_MAX", "RollerSplash_Wide"],
|
||||
|
||||
["MAIN", [1100, 1110, 1120], "ROLL_OVER", "BrushCore"],
|
||||
["MAIN", [1100, 1110, 1120], "SPLASH_MIN", "BrushSplash"],
|
||||
["MAIN", [1100, 1110, 1120], "SPLASH_MAX", "BrushSplash"],
|
||||
|
||||
["MAIN", [2000, 2010, 2020, 2060], "FULL_CHARGE", "ChargerFull"],
|
||||
["MAIN", [2000, 2010, 2020, 2060], "MAX_CHARGE", "Charger"],
|
||||
["MAIN", [2000, 2010, 2020, 2060], "TAP_SHOT", "Charger"],
|
||||
["MAIN", [2030, 2040], "FULL_CHARGE", "ChargerFull_Long"],
|
||||
["MAIN", [2030, 2040], "MAX_CHARGE", "Charger_Long"],
|
||||
["MAIN", [2030, 2040], "TAP_SHOT", "Charger_Long"],
|
||||
["MAIN", [2050, 2070], "FULL_CHARGE", "ChargerFull_Light"],
|
||||
["MAIN", [2050, 2070], "MAX_CHARGE", "Charger_Light"],
|
||||
["MAIN", [2050, 2070], "TAP_SHOT", "Charger_Light"],
|
||||
|
||||
["MAIN", [3040], "DIRECT", "Slosher_WashtubBombCore"],
|
||||
["MAIN", [3040], "DISTANCE", "Slosher_Washtub"],
|
||||
|
||||
["MAIN", [6000], "NORMAL_MAX", "ShelterShot"], // TODO: could also list damage caused by Shield bump
|
||||
["MAIN", [6000], "NORMAL_MIN", "ShelterShot"],
|
||||
["MAIN", [6010], "NORMAL_MAX", "ShelterShot_Wide"],
|
||||
["MAIN", [6010], "NORMAL_MIN", "ShelterShot_Wide"],
|
||||
["MAIN", [6020], "NORMAL_MAX", "ShelterShot_Compact"],
|
||||
["MAIN", [6020], "NORMAL_MIN", "ShelterShot_Compact"],
|
||||
|
||||
["MAIN", [8000, 8010], "SPLATANA_VERTICAL", "Saber_ChargeShot"],
|
||||
["MAIN", [8000, 8010], "SPLATANA_VERTICAL_DIRECT", "Saber_ChargeSlash"],
|
||||
["MAIN", [8000, 8010], "SPLATANA_HORIZONTAL", "Saber_Shot"],
|
||||
["MAIN", [8000, 8010], "SPLATANA_HORIZONTAL_DIRECT", "Saber_Slash"],
|
||||
|
||||
["SUB", [0, 2, 7], "BOMB_NORMAL", "Bomb"], // TODO: could also consider "Bomb_DirectHit" it is almost the same but has different ratio for Big Bubbler core: 0.5 vs. 1.5
|
||||
["SUB", [6], "BOMB_DIRECT", "Bomb_CurlingBullet"],
|
||||
["SUB", [6], "BOMB_NORMAL", "Bomb"],
|
||||
["SUB", [13], "SPLASH", "Bomb_TorpedoSplashBurst"],
|
||||
["SUB", [13], "BOMB_DIRECT", "Bomb_TorpedoBullet"],
|
||||
|
||||
["SPECIAL", [4], "BOMB_NORMAL", "MultiMissile_Bullet"], // There is also "MultiMissile_BombCore" but it seems to contain same rates as MultiMissile_Bullet
|
||||
["SPECIAL", [8], "SPECIAL_MAX_CHARGE", "BlowerExhale_BombCore"],
|
||||
["SPECIAL", [8], "SPECIAL_MIN_CHARGE", "BlowerExhale_BombCore"],
|
||||
["SPECIAL", [10], "BOMB_DIRECT", "Jetpack_BombCore"],
|
||||
["SPECIAL", [10], "BOMB_NORMAL", "Jetpack_Bullet"],
|
||||
["SPECIAL", [11], "SPECIAL_THROW_DIRECT", "UltraStamp_Throw_BombCore"],
|
||||
["SPECIAL", [11], "SPECIAL_THROW", "UltraStamp_Throw"],
|
||||
["SPECIAL", [11], "SPECIAL_SWING", "UltraStamp_Swing"],
|
||||
["SPECIAL", [12], "SPECIAL_CANNON", "Chariot_Cannon"],
|
||||
["SPECIAL", [12], "SPECIAL_BULLET_MAX", "Chariot_Cannon"],
|
||||
["SPECIAL", [12], "SPECIAL_BULLET_MIN", "Chariot_Cannon"],
|
||||
["SPECIAL", [12], "SPECIAL_BUMP", "Chariot_Body"],
|
||||
["SPECIAL", [13], "BOMB_NORMAL", "Skewer"],
|
||||
];
|
||||
|
||||
export const damageTypesToCombine: Partial<
|
||||
Record<MainWeaponId, Array<CombineWith>>
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { suite } from "uvu";
|
||||
import * as assert from "uvu/assert";
|
||||
import type { MainWeaponId } from "~/modules/in-game-lists";
|
||||
import type { SubWeaponId, SpecialWeaponId } from "~/modules/in-game-lists";
|
||||
import {
|
||||
mainWeaponIds,
|
||||
type MainWeaponId,
|
||||
specialWeaponIds,
|
||||
exampleMainWeaponIdWithSpecialWeaponId,
|
||||
} from "~/modules/in-game-lists";
|
||||
import { calculateDamage } from "./objectDamage";
|
||||
import type { AnalyzedBuild } from "~/features/build-analyzer";
|
||||
import {
|
||||
buildStats,
|
||||
type DamageType,
|
||||
@@ -10,22 +17,35 @@ import {
|
||||
|
||||
function calculate({
|
||||
mainWeaponId = 10,
|
||||
subWeaponId,
|
||||
specialWeaponId,
|
||||
abilityPoints = new Map(),
|
||||
damageType = "NORMAL_MAX",
|
||||
preAnalyzed,
|
||||
}: {
|
||||
mainWeaponId?: MainWeaponId;
|
||||
subWeaponId?: SubWeaponId;
|
||||
specialWeaponId?: SpecialWeaponId;
|
||||
abilityPoints?: AbilityPoints;
|
||||
damageType?: DamageType;
|
||||
preAnalyzed?: AnalyzedBuild;
|
||||
}) {
|
||||
const analyzed = buildStats({
|
||||
weaponSplId: mainWeaponId,
|
||||
hasTacticooler: false,
|
||||
});
|
||||
const analyzed =
|
||||
preAnalyzed ??
|
||||
buildStats({
|
||||
weaponSplId: mainWeaponId,
|
||||
hasTacticooler: false,
|
||||
});
|
||||
|
||||
return calculateDamage({
|
||||
abilityPoints,
|
||||
analyzed,
|
||||
anyWeapon: { type: "MAIN", id: mainWeaponId },
|
||||
anyWeapon:
|
||||
typeof subWeaponId === "number"
|
||||
? { type: "SUB", id: subWeaponId }
|
||||
: typeof specialWeaponId === "number"
|
||||
? { type: "SPECIAL", id: specialWeaponId }
|
||||
: { type: "MAIN", id: mainWeaponId },
|
||||
damageType,
|
||||
isMultiShot: true,
|
||||
});
|
||||
@@ -33,6 +53,49 @@ function calculate({
|
||||
|
||||
const CalculateDamage = suite("calculateDamage()");
|
||||
|
||||
// the function throws if weapon resolves to more than one set of damage rates
|
||||
// so this test goes through all of them to make sure they all work
|
||||
CalculateDamage("Every weapon can calculate damage", () => {
|
||||
for (const mainWeaponId of mainWeaponIds) {
|
||||
const analyzed = buildStats({
|
||||
weaponSplId: mainWeaponId,
|
||||
hasTacticooler: false,
|
||||
});
|
||||
|
||||
for (const damage of analyzed.stats.damages) {
|
||||
calculate({ mainWeaponId, damageType: damage.type });
|
||||
}
|
||||
}
|
||||
|
||||
const analyzed = buildStats({
|
||||
weaponSplId: 0,
|
||||
hasTacticooler: false,
|
||||
});
|
||||
|
||||
for (const damage of analyzed.stats.subWeaponDefenseDamages) {
|
||||
calculate({
|
||||
subWeaponId: damage.subWeaponId,
|
||||
damageType: damage.type,
|
||||
preAnalyzed: analyzed,
|
||||
});
|
||||
}
|
||||
|
||||
for (const specialWeaponId of specialWeaponIds) {
|
||||
const analyzedWithSpecialWeapon = buildStats({
|
||||
weaponSplId: exampleMainWeaponIdWithSpecialWeaponId(specialWeaponId),
|
||||
hasTacticooler: false,
|
||||
});
|
||||
|
||||
for (const damage of analyzedWithSpecialWeapon.stats.specialWeaponDamages) {
|
||||
calculate({
|
||||
specialWeaponId,
|
||||
damageType: damage.type,
|
||||
preAnalyzed: analyzedWithSpecialWeapon,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
CalculateDamage("BRU increases Splash Wall hitpoints", () => {
|
||||
const withoutBRU = calculate({});
|
||||
const withBRU = calculate({
|
||||
@@ -81,29 +144,30 @@ const shotsToPopRM: Array<
|
||||
[220, "DIRECT", 5, 4],
|
||||
// .96 Gal
|
||||
[80, "NORMAL_MAX", 17, 15],
|
||||
// Luna Blaster
|
||||
[200, "DIRECT", 4, 4],
|
||||
// Splat Charger
|
||||
[2010, "FULL_CHARGE", 4, 3],
|
||||
// E-liter 4K
|
||||
[2030, "TAP_SHOT", 13, 12],
|
||||
// TODO: Explosher damage wrong
|
||||
// Explosher
|
||||
// [3040, "DIRECT", 4, 3],
|
||||
// Hydra Splatling
|
||||
[4020, "NORMAL_MAX", 32, 29],
|
||||
// Sloshing Machine
|
||||
[3020, "DIRECT_MAX", 6, 5],
|
||||
// Splat Dualies
|
||||
[5010, "NORMAL_MAX", 34, 31],
|
||||
// Tenta Brella
|
||||
[6010, "NORMAL_MAX", 4, 4],
|
||||
// // Tri-Stringer
|
||||
[7010, "NORMAL_MAX", 3, 3],
|
||||
// REEF-LUX
|
||||
[7020, "NORMAL_MIN", 8, 7],
|
||||
// Splatana Wiper
|
||||
[8010, "SPLATANA_HORIZONTAL", 11, 10],
|
||||
// Splatana Wiper
|
||||
[8010, "SPLATANA_HORIZONTAL_DIRECT", 9, 8],
|
||||
// Splatana Stamper
|
||||
[8000, "SPLATANA_VERTICAL_DIRECT", 3, 3],
|
||||
// // Tri-Stringer
|
||||
[7010, "NORMAL_MAX", 3, 3],
|
||||
// REEF-LUX
|
||||
[7020, "NORMAL_MIN", 8, 7],
|
||||
];
|
||||
|
||||
CalculateDamage(
|
||||
|
||||
@@ -15,7 +15,7 @@ import { objectHitPoints } from "./objectHitPoints";
|
||||
import {
|
||||
damageTypesToCombine,
|
||||
DAMAGE_RECEIVERS,
|
||||
objectDamageJsonKeyPriority,
|
||||
damagePriorities,
|
||||
} from "../calculator-constants";
|
||||
import type { CombineWith, DamageReceiver } from "../calculator-types";
|
||||
import type { AnyWeapon } from "~/features/build-analyzer";
|
||||
@@ -26,69 +26,80 @@ export function damageTypeToMultipliers({
|
||||
weapon,
|
||||
}: {
|
||||
type: DamageType;
|
||||
weapon:
|
||||
| {
|
||||
type: "MAIN";
|
||||
id: MainWeaponId;
|
||||
}
|
||||
| {
|
||||
type: "SUB";
|
||||
id: SubWeaponId;
|
||||
}
|
||||
| {
|
||||
type: "SPECIAL";
|
||||
id: SpecialWeaponId;
|
||||
};
|
||||
weapon: AnyWeapon;
|
||||
}) {
|
||||
for (const key of jsonKeysToCeck(type)) {
|
||||
const objectDamagesObj = objectDamages[key];
|
||||
const matchingKeys: Array<keyof typeof objectDamages> = [];
|
||||
|
||||
let ok = false;
|
||||
|
||||
if (weapon.type === "MAIN") {
|
||||
ok = (objectDamagesObj.mainWeaponIds as MainWeaponId[]).includes(
|
||||
for (const [key, objectDamagesObj] of Object.entries(objectDamages)) {
|
||||
if (
|
||||
weapon.type === "MAIN" &&
|
||||
(objectDamagesObj.mainWeaponIds as MainWeaponId[]).includes(weapon.id)
|
||||
) {
|
||||
matchingKeys.push(key as keyof typeof objectDamages);
|
||||
} else if (
|
||||
weapon.type === "SUB" &&
|
||||
(objectDamagesObj.subWeaponIds as SubWeaponId[]).includes(weapon.id)
|
||||
) {
|
||||
matchingKeys.push(key as keyof typeof objectDamages);
|
||||
} else if (
|
||||
weapon.type === "SPECIAL" &&
|
||||
(objectDamagesObj.specialWeaponIds as SpecialWeaponId[]).includes(
|
||||
weapon.id
|
||||
);
|
||||
} else if (weapon.type === "SUB") {
|
||||
ok = (objectDamagesObj.subWeaponIds as SubWeaponId[]).includes(weapon.id);
|
||||
} else if (weapon.type === "SPECIAL") {
|
||||
ok = (objectDamagesObj.specialWeaponIds as SpecialWeaponId[]).includes(
|
||||
weapon.id
|
||||
);
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
return objectDamagesObj.rates;
|
||||
)
|
||||
) {
|
||||
matchingKeys.push(key as keyof typeof objectDamages);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
if (matchingKeys.length === 0) return null;
|
||||
|
||||
const relevantKey = resolveRelevantKey({ keys: matchingKeys, type, weapon });
|
||||
|
||||
return objectDamages[relevantKey].rates;
|
||||
}
|
||||
const objectDamageJsonKeyPriorityEntries = Object.entries(
|
||||
objectDamageJsonKeyPriority
|
||||
);
|
||||
|
||||
// for example blaster belongs to both Blaster_KillOneShot
|
||||
// and Blaster categories so it needs to be specified
|
||||
// which damage type uses which
|
||||
function jsonKeysToCeck(type: DamageType) {
|
||||
const result: Array<keyof typeof objectDamages> = [];
|
||||
function resolveRelevantKey({
|
||||
keys,
|
||||
type,
|
||||
weapon,
|
||||
}: {
|
||||
keys: Array<keyof typeof objectDamages>;
|
||||
type: DamageType;
|
||||
weapon: AnyWeapon;
|
||||
}): keyof typeof objectDamages {
|
||||
if (keys.length === 1) return keys[0];
|
||||
|
||||
for (const [key, value] of objectDamageJsonKeyPriorityEntries) {
|
||||
if (value?.includes(type)) {
|
||||
result.push(key as keyof typeof objectDamages);
|
||||
const actualKeys = keys.filter((k) => k !== "Default");
|
||||
if (actualKeys.length === 1) return actualKeys[0];
|
||||
|
||||
for (const [weaponType, weaponIds, damageType, key] of damagePriorities) {
|
||||
// handle alt kits e.g. Splatteshot might have id 10 but Tentatek Splattershot has id 11
|
||||
// but in the context of this function they are one and the same
|
||||
const normalizedWeaponId =
|
||||
weapon.type === "MAIN" && weapon.id % 10 !== 0
|
||||
? ((weapon.id - 1) as MainWeaponId)
|
||||
: weapon.id;
|
||||
|
||||
if (weaponType !== weapon.type) continue;
|
||||
if (!weaponIds.includes(normalizedWeaponId)) continue;
|
||||
if (damageType !== type) continue;
|
||||
|
||||
if (!actualKeys.includes(key)) {
|
||||
throw new Error(
|
||||
`Invalid damagePriorities (no key in object-dmg.json for the weapon): ${JSON.stringify(
|
||||
[weaponType, weaponIds, damageType, key]
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
if (result.length) return result;
|
||||
|
||||
for (const [key, value] of objectDamageJsonKeyPriorityEntries) {
|
||||
if (!value) {
|
||||
result.push(key as keyof typeof objectDamages);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
throw new Error(
|
||||
`Could not resolve relevant key from ${actualKeys.join(", ")}; weapon: ${
|
||||
weapon.type
|
||||
}_${weapon.id}; damage type: ${type} - please update damagePriorities`
|
||||
);
|
||||
}
|
||||
|
||||
export function multipliersToRecordWithFallbacks(
|
||||
@@ -111,9 +122,7 @@ export function resolveAllUniqueDamageTypes({
|
||||
}) {
|
||||
const damageTypes =
|
||||
anyWeapon.type === "SUB"
|
||||
? analyzed.stats.subWeaponDefenseDamages
|
||||
.filter((damage) => damage.subWeaponId === anyWeapon.id)
|
||||
.map((d) => d.type)
|
||||
? []
|
||||
: anyWeapon.type === "SPECIAL"
|
||||
? analyzed.stats.specialWeaponDamages.map((d) => d.type)
|
||||
: analyzed.stats.damages.map((d) => d.type);
|
||||
|
||||
@@ -90,18 +90,20 @@ export default function ObjectDamagePage() {
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={clsx({
|
||||
invisible: !damagesToReceivers || allDamageTypes.length === 1,
|
||||
})}
|
||||
>
|
||||
<Label htmlFor="damage">{t("analyzer:labels.damageType")}</Label>
|
||||
<DamageTypesSelect
|
||||
handleChange={handleChange}
|
||||
damageType={damageType}
|
||||
allDamageTypes={allDamageTypes}
|
||||
/>
|
||||
</div>
|
||||
{allDamageTypes.length > 0 ? (
|
||||
<div
|
||||
className={clsx({
|
||||
invisible: !damagesToReceivers || allDamageTypes.length === 1,
|
||||
})}
|
||||
>
|
||||
<Label htmlFor="damage">{t("analyzer:labels.damageType")}</Label>
|
||||
<DamageTypesSelect
|
||||
handleChange={handleChange}
|
||||
damageType={damageType}
|
||||
allDamageTypes={allDamageTypes}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{multiShotCount ? (
|
||||
<div className="stack sm horizontal items-center label-no-spacing">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { navigate, seed } from "~/utils/playwright";
|
||||
import { topSearchPage, userPage, weaponBuildPage } from "~/utils/urls";
|
||||
import { topSearchPage, userPage } from "~/utils/urls";
|
||||
|
||||
test.describe("Top search", () => {
|
||||
test("views different x rank placements", async ({ page }) => {
|
||||
@@ -30,21 +30,4 @@ test.describe("Top search", () => {
|
||||
|
||||
await expect(page.getByText("Twig?")).toBeVisible();
|
||||
});
|
||||
|
||||
test("sorts top 500 builds first", async ({ page }) => {
|
||||
await seed(page);
|
||||
|
||||
await navigate({
|
||||
page,
|
||||
url: weaponBuildPage("splattershot"),
|
||||
});
|
||||
|
||||
await expect(
|
||||
page
|
||||
.getByTestId("build-card")
|
||||
.first()
|
||||
.locator(page.getByTestId("top500-crown"))
|
||||
.first()
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user