Obj DMG Calc: Support calculating multishot

This commit is contained in:
Kalle
2022-12-10 16:17:43 +02:00
parent 602e545831
commit 3caecef855
10 changed files with 99 additions and 47 deletions

View File

@@ -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(

View File

@@ -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(

View File

@@ -404,6 +404,7 @@ function damages(args: StatFunctionInput): AnalyzedBuild["stats"]["damages"] {
type,
multiShots: multiShot[args.weaponSplId],
}),
multiShots: multiShot[args.weaponSplId],
});
}

View File

@@ -182,6 +182,7 @@ export interface Damage {
type: DamageType;
distance?: number;
shotsToSplat?: number;
multiShots?: number;
}
export interface AnalyzedBuild {

View File

@@ -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",

View File

@@ -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 (
<Main className="stack lg">
<div className="object-damage__controls">
<div>
<Label htmlFor="weapon">{t("analyzer:labels.weapon")}</Label>
<WeaponCombobox
id="weapon"
inputName="weapon"
initialWeaponId={mainWeaponId}
onChange={(opt) =>
opt &&
handleChange({
newMainWeaponId: Number(opt.value) as MainWeaponId,
})
}
className="w-full-important"
clearsInputOnFocus
/>
</div>
<div className={clsx({ invisible: !damagesToReceivers })}>
<Label htmlFor="damage">{t("analyzer:labels.damageType")}</Label>
<DamageTypesSelect
handleChange={handleChange}
subWeaponId={subWeaponId}
damageType={damageType}
allDamageTypes={allDamageTypes}
/>
<div className="object-damage__selects">
<div>
<Label htmlFor="weapon">{t("analyzer:labels.weapon")}</Label>
<WeaponCombobox
id="weapon"
inputName="weapon"
initialWeaponId={mainWeaponId}
onChange={(opt) =>
opt &&
handleChange({
newMainWeaponId: Number(opt.value) as MainWeaponId,
})
}
className="w-full-important"
clearsInputOnFocus
/>
</div>
<div className={clsx({ invisible: !damagesToReceivers })}>
<Label htmlFor="damage">{t("analyzer:labels.damageType")}</Label>
<DamageTypesSelect
handleChange={handleChange}
subWeaponId={subWeaponId}
damageType={damageType}
allDamageTypes={allDamageTypes}
/>
</div>
</div>
{multiShotCount ? (
<div className="stack sm horizontal items-center label-no-spacing">
<label className="plain" htmlFor="multi">
×{multiShotCount}
</label>
<Toggle
name="multi"
checked={isMultiShot}
setChecked={(checked) =>
handleChange({ newIsMultiShot: checked })
}
/>
</div>
) : null}
</div>
{damagesToReceivers ? (
<DamageReceiversGrid

View File

@@ -370,7 +370,7 @@ dialog::backdrop {
}
.toggle.checked {
background-color: var(--theme);
background-color: var(--theme-vibrant);
}
.toggle:active {

View File

@@ -1,15 +1,15 @@
.object-damage__controls {
display: flex;
align-items: flex-start;
justify-content: space-between;
}
.object-damage__selects {
display: flex;
flex-wrap: wrap;
gap: var(--s-5);
}
@media screen and (min-width: 640px) {
.object-damage__controls {
justify-content: center;
}
}
.object-damage__ability {
position: absolute;
}

View File

@@ -193,3 +193,7 @@
.outline-theme:focus {
outline: 2px solid var(--theme);
}
.label-no-spacing {
--label-margin: 0;
}

View File

@@ -33,6 +33,7 @@ html {
--theme: #f73e8b;
--theme-transparent: #f3a0c386;
--theme-very-transparent: #f3a0c341;
--theme-vibrant: #f73e8b;
--theme-secondary: rgb(63 58 255);
--backdrop-filter: blur(10px) brightness(95%);
--rounded: 16px;
@@ -115,7 +116,7 @@ html.dark {
--theme-informative-green: #a2ffad;
--theme: #ffc6de;
--theme-very-transparent: #ffc6de36;
--theme-vibrant: hsl(255deg 78% 65%);
--theme-vibrant: #f391ba;
--theme-transparent: #ffc6de52;
--theme-secondary: rgb(239 229 83);
--backdrop-filter: blur(10px) brightness(75%);