mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-14 07:06:14 -05:00
Obj DMG Calc: Support calculating multishot
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -404,6 +404,7 @@ function damages(args: StatFunctionInput): AnalyzedBuild["stats"]["damages"] {
|
||||
type,
|
||||
multiShots: multiShot[args.weaponSplId],
|
||||
}),
|
||||
multiShots: multiShot[args.weaponSplId],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -182,6 +182,7 @@ export interface Damage {
|
||||
type: DamageType;
|
||||
distance?: number;
|
||||
shotsToSplat?: number;
|
||||
multiShots?: number;
|
||||
}
|
||||
|
||||
export interface AnalyzedBuild {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -370,7 +370,7 @@ dialog::backdrop {
|
||||
}
|
||||
|
||||
.toggle.checked {
|
||||
background-color: var(--theme);
|
||||
background-color: var(--theme-vibrant);
|
||||
}
|
||||
|
||||
.toggle:active {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -193,3 +193,7 @@
|
||||
.outline-theme:focus {
|
||||
outline: 2px solid var(--theme);
|
||||
}
|
||||
|
||||
.label-no-spacing {
|
||||
--label-margin: 0;
|
||||
}
|
||||
|
||||
@@ -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%);
|
||||
|
||||
Reference in New Issue
Block a user