mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-04-25 07:32:19 -05:00
Object damage filter by damage type
This commit is contained in:
parent
dfd16cef2f
commit
d487af683a
|
|
@ -6,6 +6,8 @@ export type {
|
|||
AnalyzedBuild,
|
||||
SpecialEffectType,
|
||||
HitPoints,
|
||||
DamageReceiver,
|
||||
DamageType,
|
||||
} from "./types";
|
||||
|
||||
export { useAnalyzeBuild } from "./useAnalyzeBuild";
|
||||
|
|
|
|||
|
|
@ -100,14 +100,20 @@ export function calculateDamage({
|
|||
analyzed,
|
||||
mainWeaponId,
|
||||
abilityPoints,
|
||||
damageType,
|
||||
}: {
|
||||
analyzed: AnalyzedBuild;
|
||||
mainWeaponId: MainWeaponId;
|
||||
abilityPoints: AbilityPoints;
|
||||
damageType: DamageType;
|
||||
}) {
|
||||
const filteredDamages = analyzed.stats.damages.filter(
|
||||
(d) => d.type === damageType
|
||||
);
|
||||
|
||||
const hitPoints = objectHitPoints(abilityPoints);
|
||||
const multipliers = Object.fromEntries(
|
||||
analyzed.stats.damages.map((damage) => {
|
||||
filteredDamages.map((damage) => {
|
||||
const weaponType = damageTypeToWeaponType[damage.type];
|
||||
const weaponId: any =
|
||||
weaponType === "MAIN"
|
||||
|
|
@ -134,7 +140,7 @@ export function calculateDamage({
|
|||
return {
|
||||
receiver,
|
||||
hitPoints: damageReceiverHp,
|
||||
damages: analyzed.stats.damages.map((damage) => {
|
||||
damages: filteredDamages.map((damage) => {
|
||||
const multiplier = multipliers[damage.type]![receiver];
|
||||
const damagePerHit = roundToNDecimalPlaces(damage.value * multiplier);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
import { useSearchParams } from "@remix-run/react";
|
||||
import invariant from "tiny-invariant";
|
||||
import { type MainWeaponId } from "../in-game-lists";
|
||||
import { calculateDamage } from "./objectDamage";
|
||||
import { buildStats } from "./stats";
|
||||
import type { AnalyzedBuild, DamageType } from "./types";
|
||||
import { possibleApValues, validatedWeaponIdFromSearchParams } from "./utils";
|
||||
|
||||
const ABILITY_POINTS_SP_KEY = "ap";
|
||||
const DAMAGE_TYPE_SP_KEY = "dmg";
|
||||
|
||||
export function useObjectDamage() {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
|
@ -12,26 +15,34 @@ export function useObjectDamage() {
|
|||
const mainWeaponId = validatedWeaponIdFromSearchParams(searchParams);
|
||||
const abilityPoints = validatedAbilityPointsFromSearchParams(searchParams);
|
||||
|
||||
const analyzed = buildStats({
|
||||
weaponSplId: mainWeaponId,
|
||||
});
|
||||
|
||||
const damageType = validatedDamageTypeFromSearchParams({
|
||||
searchParams,
|
||||
analyzed,
|
||||
});
|
||||
|
||||
const handleChange = ({
|
||||
newMainWeaponId = mainWeaponId,
|
||||
abilityPoints = 0,
|
||||
newAbilityPoints = abilityPoints,
|
||||
newDamageType = damageType,
|
||||
}: {
|
||||
newMainWeaponId?: MainWeaponId;
|
||||
abilityPoints?: number;
|
||||
newAbilityPoints?: number;
|
||||
newDamageType?: DamageType;
|
||||
}) => {
|
||||
setSearchParams(
|
||||
{
|
||||
weapon: String(newMainWeaponId),
|
||||
[ABILITY_POINTS_SP_KEY]: String(abilityPoints),
|
||||
[ABILITY_POINTS_SP_KEY]: String(newAbilityPoints),
|
||||
[DAMAGE_TYPE_SP_KEY]: newDamageType,
|
||||
},
|
||||
{ replace: true, state: { scroll: false } }
|
||||
);
|
||||
};
|
||||
|
||||
const analyzed = buildStats({
|
||||
weaponSplId: mainWeaponId,
|
||||
});
|
||||
|
||||
return {
|
||||
mainWeaponId,
|
||||
subWeaponId: analyzed.weapon.subWeaponSplId,
|
||||
|
|
@ -43,17 +54,52 @@ export function useObjectDamage() {
|
|||
]),
|
||||
analyzed,
|
||||
mainWeaponId,
|
||||
damageType,
|
||||
}),
|
||||
abilityPoints: String(abilityPoints),
|
||||
damageType,
|
||||
allDamageTypes: Array.from(
|
||||
new Set(analyzed.stats.damages.map((d) => d.type))
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export function validatedAbilityPointsFromSearchParams(
|
||||
searchParams: URLSearchParams
|
||||
) {
|
||||
function validatedAbilityPointsFromSearchParams(searchParams: URLSearchParams) {
|
||||
const abilityPoints = Number(searchParams.get(ABILITY_POINTS_SP_KEY));
|
||||
|
||||
return (
|
||||
possibleApValues().find((possibleAp) => possibleAp === abilityPoints) ?? 0
|
||||
);
|
||||
}
|
||||
|
||||
export const damageTypePriorityList = [
|
||||
"DIRECT",
|
||||
"FULL_CHARGE",
|
||||
"MAX_CHARGE",
|
||||
"NORMAL_MAX",
|
||||
"NORMAL_MIN",
|
||||
"TAP_SHOT",
|
||||
"DISTANCE",
|
||||
"BOMB_DIRECT",
|
||||
"BOMB_NORMAL",
|
||||
] as const;
|
||||
function validatedDamageTypeFromSearchParams({
|
||||
searchParams,
|
||||
analyzed,
|
||||
}: {
|
||||
searchParams: URLSearchParams;
|
||||
analyzed: AnalyzedBuild;
|
||||
}) {
|
||||
const damageType = searchParams.get(DAMAGE_TYPE_SP_KEY);
|
||||
|
||||
const found = analyzed.stats.damages.find((d) => d.type === damageType);
|
||||
|
||||
if (found) return found.type;
|
||||
|
||||
const fallbackFound = damageTypePriorityList.find((type) =>
|
||||
analyzed.stats.damages.some((d) => d.type === type)
|
||||
);
|
||||
invariant(fallbackFound);
|
||||
|
||||
return fallbackFound;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import {
|
|||
type SubWeaponId,
|
||||
} from "~/modules/in-game-lists";
|
||||
import styles from "~/styles/analyzer.css";
|
||||
import { damageTypeTranslationString } from "~/utils/i18next";
|
||||
import { type SendouRouteHandle } from "~/utils/remix";
|
||||
import { makeTitle } from "~/utils/strings";
|
||||
import { specialWeaponImageUrl, subWeaponImageUrl } from "~/utils/urls";
|
||||
|
|
@ -826,9 +827,10 @@ function DamageTable({
|
|||
? `${val.value}+${val.value}+${val.value}`
|
||||
: val.value;
|
||||
|
||||
const typeRowName = val.type.startsWith("BOMB_")
|
||||
? t(`weapons:SUB_${subWeaponId}`)
|
||||
: t(`analyzer:damage.${val.type as "NORMAL_MIN"}`);
|
||||
const typeRowName = damageTypeTranslationString({
|
||||
damageType: val.type,
|
||||
subWeaponId,
|
||||
});
|
||||
|
||||
return (
|
||||
<tr key={val.id}>
|
||||
|
|
|
|||
|
|
@ -22,12 +22,13 @@ import {
|
|||
import styles from "~/styles/object-damage.css";
|
||||
import type { LinksFunction } from "@remix-run/node";
|
||||
import type { SendouRouteHandle } from "~/utils/remix";
|
||||
import type { DamageReceiver } from "~/modules/analyzer/types";
|
||||
import type { DamageReceiver, DamageType } from "~/modules/analyzer";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import clsx from "clsx";
|
||||
import { Label } from "~/components/Label";
|
||||
import { Ability } from "~/components/Ability";
|
||||
import { damageTypeTranslationString } from "~/utils/i18next";
|
||||
|
||||
export const links: LinksFunction = () => {
|
||||
return [{ rel: "stylesheet", href: styles }];
|
||||
|
|
@ -45,6 +46,8 @@ export default function ObjectDamagePage() {
|
|||
handleChange,
|
||||
damagesToReceivers,
|
||||
abilityPoints,
|
||||
damageType,
|
||||
allDamageTypes,
|
||||
} = useObjectDamage();
|
||||
|
||||
if (process.env.NODE_ENV !== "development") {
|
||||
|
|
@ -72,9 +75,12 @@ export default function ObjectDamagePage() {
|
|||
</div>
|
||||
<div>
|
||||
<Label htmlFor="damage">Damage type</Label>
|
||||
<select id="damage">
|
||||
<option value="0">0</option>
|
||||
</select>
|
||||
<DamageTypesSelect
|
||||
handleChange={handleChange}
|
||||
subWeaponId={subWeaponId}
|
||||
damageType={damageType}
|
||||
allDamageTypes={allDamageTypes}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="ap" labelClassName="object-damage__ap-label">
|
||||
|
|
@ -85,7 +91,7 @@ export default function ObjectDamagePage() {
|
|||
id="ap"
|
||||
value={abilityPoints}
|
||||
onChange={(e) =>
|
||||
handleChange({ abilityPoints: Number(e.target.value) })
|
||||
handleChange({ newAbilityPoints: Number(e.target.value) })
|
||||
}
|
||||
>
|
||||
{possibleApValues().map((ap) => (
|
||||
|
|
@ -105,6 +111,41 @@ export default function ObjectDamagePage() {
|
|||
);
|
||||
}
|
||||
|
||||
function DamageTypesSelect({
|
||||
allDamageTypes,
|
||||
handleChange,
|
||||
subWeaponId,
|
||||
damageType,
|
||||
}: Pick<
|
||||
ReturnType<typeof useObjectDamage>,
|
||||
"handleChange" | "subWeaponId" | "damageType" | "allDamageTypes"
|
||||
>) {
|
||||
const { t } = useTranslation(["analyzer"]);
|
||||
|
||||
return (
|
||||
<select
|
||||
id="damage"
|
||||
value={damageType}
|
||||
onChange={(e) =>
|
||||
handleChange({ newDamageType: e.target.value as DamageType })
|
||||
}
|
||||
>
|
||||
{allDamageTypes.map((damageType) => {
|
||||
return (
|
||||
<option key={damageType} value={damageType}>
|
||||
{t(
|
||||
damageTypeTranslationString({
|
||||
damageType,
|
||||
subWeaponId,
|
||||
})
|
||||
)}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
|
||||
const damageReceiverImages: Record<DamageReceiver, string> = {
|
||||
Bomb_TorpedoBullet: subWeaponImageUrl(TORPEDO_ID),
|
||||
Chariot: specialWeaponImageUrl(CRAB_TANK_ID),
|
||||
|
|
@ -143,9 +184,12 @@ function DamageReceiversGrid({
|
|||
<div />
|
||||
{damagesToReceivers[0]?.damages.map((damage) => (
|
||||
<div key={damage.id} className="object-damage__table-header">
|
||||
{damage.type.startsWith("BOMB_")
|
||||
? t(`weapons:SUB_${subWeaponId}`)
|
||||
: t(`analyzer:damage.${damage.type as "NORMAL_MIN"}`)}
|
||||
{t(
|
||||
damageTypeTranslationString({
|
||||
damageType: damage.type,
|
||||
subWeaponId: subWeaponId,
|
||||
})
|
||||
)}
|
||||
<div
|
||||
className={clsx("object-damage__distance", {
|
||||
invisible: !damage.distance,
|
||||
|
|
|
|||
14
app/utils/i18next.ts
Normal file
14
app/utils/i18next.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import type { DamageType } from "~/modules/analyzer";
|
||||
import type { SubWeaponId } from "~/modules/in-game-lists";
|
||||
|
||||
// TODO: type this correctly
|
||||
export const damageTypeTranslationString = ({
|
||||
damageType,
|
||||
subWeaponId,
|
||||
}: {
|
||||
damageType: DamageType;
|
||||
subWeaponId: SubWeaponId;
|
||||
}): any =>
|
||||
damageType.startsWith("BOMB_")
|
||||
? `weapons:SUB_${subWeaponId}`
|
||||
: `analyzer:damage.${damageType as "NORMAL_MIN"}`;
|
||||
Loading…
Reference in New Issue
Block a user