diff --git a/app/modules/analyzer/objectDamage.test.ts b/app/modules/analyzer/objectDamage.test.ts index d920b95eb..ecac63904 100644 --- a/app/modules/analyzer/objectDamage.test.ts +++ b/app/modules/analyzer/objectDamage.test.ts @@ -23,6 +23,7 @@ function calculate({ analyzed, mainWeaponId, damageType, + isMultiShot: true, }); } @@ -92,6 +93,8 @@ const shotsToPopRM: Array< [8010, "SPLATANA_HORIZONTAL_DIRECT", 9, 8], // Splatana Stamper [8000, "SPLATANA_VERTICAL_DIRECT", 3, 3], + // REEF-LUX + [7020, "NORMAL_MIN", 8, 7], ]; CalculateDamage( diff --git a/app/modules/analyzer/objectDamage.ts b/app/modules/analyzer/objectDamage.ts index af36e2790..7a097bce4 100644 --- a/app/modules/analyzer/objectDamage.ts +++ b/app/modules/analyzer/objectDamage.ts @@ -106,26 +106,37 @@ export function calculateDamage({ mainWeaponId, abilityPoints, damageType, + isMultiShot, }: { analyzed: AnalyzedBuild; mainWeaponId: MainWeaponId; abilityPoints: AbilityPoints; damageType: DamageType; + isMultiShot: boolean; }) { - const filteredDamages = analyzed.stats.damages.filter( - (d) => - d.type === damageType || - // Splatana direct seems to use two damage sources - // The way Splatana damage works is a bit confusing: - // Vertical Direct = Vertical + Vertical Direct damage vs. Objects only - // Horizontal Direct = Horizontal + Horizontal Direct damage vs. both objects and players both - // so that's why Horizontal Direct damage has these baked in while - // with Vertical Direct we add them here in not so clean manner - (damageType === "SPLATANA_VERTICAL_DIRECT" && - d.type === "SPLATANA_VERTICAL") || - (damageType === "SPLATANA_HORIZONTAL_DIRECT" && - d.type === "SPLATANA_HORIZONTAL") - ); + const filteredDamages = analyzed.stats.damages + .filter( + (d) => + d.type === damageType || + // Splatana direct seems to use two damage sources + // The way Splatana damage works is a bit confusing: + // Vertical Direct = Vertical + Vertical Direct damage vs. Objects only + // Horizontal Direct = Horizontal + Horizontal Direct damage vs. both objects and players both + // so that's why Horizontal Direct damage has these baked in while + // with Vertical Direct we add them here in not so clean manner + (damageType === "SPLATANA_VERTICAL_DIRECT" && + d.type === "SPLATANA_VERTICAL") || + (damageType === "SPLATANA_HORIZONTAL_DIRECT" && + d.type === "SPLATANA_HORIZONTAL") + ) + .map((damage) => { + if (!isMultiShot || !damage.multiShots) return damage; + + return { + ...damage, + value: damage.value * damage.multiShots, + }; + }); const hitPoints = objectHitPoints(abilityPoints); const multipliers = Object.fromEntries( diff --git a/app/modules/analyzer/stats.ts b/app/modules/analyzer/stats.ts index 3ab29979e..8ec7fb0f2 100644 --- a/app/modules/analyzer/stats.ts +++ b/app/modules/analyzer/stats.ts @@ -404,6 +404,7 @@ function damages(args: StatFunctionInput): AnalyzedBuild["stats"]["damages"] { type, multiShots: multiShot[args.weaponSplId], }), + multiShots: multiShot[args.weaponSplId], }); } diff --git a/app/modules/analyzer/types.ts b/app/modules/analyzer/types.ts index 799dbc48b..816686435 100644 --- a/app/modules/analyzer/types.ts +++ b/app/modules/analyzer/types.ts @@ -182,6 +182,7 @@ export interface Damage { type: DamageType; distance?: number; shotsToSplat?: number; + multiShots?: number; } export interface AnalyzedBuild { diff --git a/app/modules/analyzer/useObjectDamage.ts b/app/modules/analyzer/useObjectDamage.ts index 4d76b0d14..4efc09c71 100644 --- a/app/modules/analyzer/useObjectDamage.ts +++ b/app/modules/analyzer/useObjectDamage.ts @@ -9,13 +9,14 @@ import { possibleApValues, validatedWeaponIdFromSearchParams } from "./utils"; const ABILITY_POINTS_SP_KEY = "ap"; const DAMAGE_TYPE_SP_KEY = "dmg"; +const MULTI_SHOT_SP_KEY = "multi"; export function useObjectDamage() { const [searchParams, setSearchParams] = useSearchParams(); const mainWeaponId = validatedWeaponIdFromSearchParams(searchParams); const abilityPoints = validatedAbilityPointsFromSearchParams(searchParams); - + const isMultiShot = validatedMultiShotFromSearchParams(searchParams); const analyzed = buildStats({ weaponSplId: mainWeaponId, }); @@ -29,16 +30,19 @@ export function useObjectDamage() { newMainWeaponId = mainWeaponId, newAbilityPoints = abilityPoints, newDamageType = damageType, + newIsMultiShot = isMultiShot, }: { newMainWeaponId?: MainWeaponId; newAbilityPoints?: number; newDamageType?: DamageType; + newIsMultiShot?: boolean; }) => { setSearchParams( { weapon: String(newMainWeaponId), [ABILITY_POINTS_SP_KEY]: String(newAbilityPoints), [DAMAGE_TYPE_SP_KEY]: newDamageType ?? "", + [MULTI_SHOT_SP_KEY]: String(newIsMultiShot), }, { replace: true, state: { scroll: false } } ); @@ -47,6 +51,9 @@ export function useObjectDamage() { return { mainWeaponId, subWeaponId: analyzed.weapon.subWeaponSplId, + isMultiShot, + multiShotCount: analyzed.stats.damages.find((d) => d.type === damageType) + ?.multiShots, handleChange, damagesToReceivers: damageType ? calculateDamage({ @@ -57,6 +64,7 @@ export function useObjectDamage() { analyzed, mainWeaponId, damageType, + isMultiShot, }) : null, abilityPoints: String(abilityPoints), @@ -75,6 +83,10 @@ function validatedAbilityPointsFromSearchParams(searchParams: URLSearchParams) { ); } +function validatedMultiShotFromSearchParams(searchParams: URLSearchParams) { + return searchParams.get(MULTI_SHOT_SP_KEY) === "false" ? false : true; +} + export const damageTypePriorityList = [ "DIRECT_MAX", "DIRECT", diff --git a/app/routes/object-damage-calculator.tsx b/app/routes/object-damage-calculator.tsx index fcd389f4c..2736e473d 100644 --- a/app/routes/object-damage-calculator.tsx +++ b/app/routes/object-damage-calculator.tsx @@ -33,6 +33,7 @@ import { Ability } from "~/components/Ability"; import { damageTypeTranslationString } from "~/utils/i18next"; import { useSetTitle } from "~/hooks/useSetTitle"; import type { ShouldReloadFunction } from "@remix-run/react"; +import { Toggle } from "~/components/Toggle"; export const CURRENT_PATCH = "2.0"; @@ -61,36 +62,54 @@ export default function ObjectDamagePage() { abilityPoints, damageType, allDamageTypes, + multiShotCount, + isMultiShot, } = useObjectDamage(); return (
-
- - - opt && - handleChange({ - newMainWeaponId: Number(opt.value) as MainWeaponId, - }) - } - className="w-full-important" - clearsInputOnFocus - /> -
-
- - +
+
+ + + opt && + handleChange({ + newMainWeaponId: Number(opt.value) as MainWeaponId, + }) + } + className="w-full-important" + clearsInputOnFocus + /> +
+
+ + +
+ {multiShotCount ? ( +
+ + + handleChange({ newIsMultiShot: checked }) + } + /> +
+ ) : null}
{damagesToReceivers ? (