Object damage filter by damage type

This commit is contained in:
Kalle 2022-10-23 12:50:25 +03:00
parent dfd16cef2f
commit d487af683a
6 changed files with 137 additions and 23 deletions

View File

@ -6,6 +6,8 @@ export type {
AnalyzedBuild,
SpecialEffectType,
HitPoints,
DamageReceiver,
DamageType,
} from "./types";
export { useAnalyzeBuild } from "./useAnalyzeBuild";

View File

@ -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);

View File

@ -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;
}

View File

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

View File

@ -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
View 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"}`;