mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-09 15:13:53 -05:00
Object damages with calculation
This commit is contained in:
parent
89e525307f
commit
b448c19b71
|
|
@ -1,4 +1,4 @@
|
|||
import type { DamageType } from "./types";
|
||||
import type { DamageReceiver, DamageType } from "./types";
|
||||
import objectDamages from "./object-dmg.json";
|
||||
import type {
|
||||
MainWeaponId,
|
||||
|
|
@ -78,11 +78,13 @@ export function damageTypeToMultipliers({
|
|||
return null;
|
||||
}
|
||||
|
||||
export function fallbackRates(
|
||||
export function multipliersToRecordWithFallbacks(
|
||||
multipliers: ReturnType<typeof damageTypeToMultipliers>
|
||||
) {
|
||||
return DAMAGE_RECEIVERS.map((receiver) => ({
|
||||
target: receiver,
|
||||
rate: multipliers?.find((m) => m.target === receiver)?.rate ?? 1,
|
||||
}));
|
||||
return Object.fromEntries(
|
||||
DAMAGE_RECEIVERS.map((receiver) => [
|
||||
receiver,
|
||||
multipliers?.find((m) => m.target === receiver)?.rate ?? 1,
|
||||
])
|
||||
) as Record<DamageReceiver, number>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import { useSearchParams } from "@remix-run/react";
|
||||
import { type MainWeaponId } from "../in-game-lists";
|
||||
import { damageTypeToWeaponType } from "./constants";
|
||||
import { damageTypeToMultipliers, fallbackRates } from "./damageMultipliers";
|
||||
import {
|
||||
damageTypeToMultipliers,
|
||||
multipliersToRecordWithFallbacks,
|
||||
} from "./damageMultipliers";
|
||||
import { objectHitPoints } from "./objectHitPoints";
|
||||
import { buildStats } from "./stats";
|
||||
import { validatedWeaponIdFromSearchParams } from "./utils";
|
||||
|
|
@ -40,7 +43,7 @@ export function useObjectDamage() {
|
|||
|
||||
return [
|
||||
damage.type,
|
||||
fallbackRates(
|
||||
multipliersToRecordWithFallbacks(
|
||||
damageTypeToMultipliers({
|
||||
type: damage.type,
|
||||
weapon: { type: weaponType, id: weaponId },
|
||||
|
|
@ -52,6 +55,7 @@ export function useObjectDamage() {
|
|||
|
||||
return {
|
||||
mainWeaponId,
|
||||
subWeaponId: analyzed.weapon.subWeaponSplId,
|
||||
handleChange,
|
||||
multipliers,
|
||||
damages: analyzed.stats.damages,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
import { WeaponCombobox } from "~/components/Combobox";
|
||||
import { Image } from "~/components/Image";
|
||||
import { Main } from "~/components/Main";
|
||||
import {
|
||||
DAMAGE_RECEIVERS,
|
||||
useObjectDamage,
|
||||
type HitPoints,
|
||||
} from "~/modules/analyzer";
|
||||
import { DAMAGE_RECEIVERS, useObjectDamage } from "~/modules/analyzer";
|
||||
import {
|
||||
type MainWeaponId,
|
||||
BIG_BUBBLER_ID,
|
||||
|
|
@ -27,26 +23,32 @@ 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 React from "react";
|
||||
import { roundToTwoDecimalPlaces } from "~/utils/number";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const links: LinksFunction = () => {
|
||||
return [{ rel: "stylesheet", href: styles }];
|
||||
};
|
||||
|
||||
export const handle: SendouRouteHandle = {
|
||||
i18n: ["weapons"],
|
||||
i18n: ["weapons", "analyzer"],
|
||||
};
|
||||
|
||||
export default function ObjectDamagePage() {
|
||||
const { mainWeaponId, handleChange, multipliers, damages, hitPoints } =
|
||||
useObjectDamage();
|
||||
const {
|
||||
mainWeaponId,
|
||||
subWeaponId,
|
||||
handleChange,
|
||||
multipliers,
|
||||
damages,
|
||||
hitPoints,
|
||||
} = useObjectDamage();
|
||||
|
||||
if (process.env.NODE_ENV !== "development") {
|
||||
return <Main>WIP :)</Main>;
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(multipliers, null, 2));
|
||||
console.log(JSON.stringify(damages, null, 2));
|
||||
|
||||
return (
|
||||
<Main className="stack lg">
|
||||
<WeaponCombobox
|
||||
|
|
@ -61,12 +63,12 @@ export default function ObjectDamagePage() {
|
|||
className="w-full-important"
|
||||
clearsInputOnFocus
|
||||
/>
|
||||
<div
|
||||
className="object-damage__grid"
|
||||
style={{ "--columns-count": "2" } as any}
|
||||
>
|
||||
<DamageReceiversHeader hitPoints={hitPoints} />
|
||||
</div>
|
||||
<DamageReceiversGrid
|
||||
subWeaponId={subWeaponId}
|
||||
hitPoints={hitPoints}
|
||||
damages={damages}
|
||||
multipliers={multipliers}
|
||||
/>
|
||||
</Main>
|
||||
);
|
||||
}
|
||||
|
|
@ -88,21 +90,75 @@ const damageReceiverImages: Record<DamageReceiver, string> = {
|
|||
BulletUmbrellaCanopyWide: mainWeaponImageUrl(6010),
|
||||
BulletUmbrellaCanopyCompact: mainWeaponImageUrl(6020),
|
||||
};
|
||||
function DamageReceiversHeader({ hitPoints }: { hitPoints: HitPoints }) {
|
||||
|
||||
function DamageReceiversGrid({
|
||||
subWeaponId,
|
||||
hitPoints,
|
||||
damages,
|
||||
multipliers,
|
||||
}: Pick<
|
||||
ReturnType<typeof useObjectDamage>,
|
||||
"hitPoints" | "damages" | "multipliers" | "subWeaponId"
|
||||
>) {
|
||||
const { t } = useTranslation(["weapons", "analyzer"]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{DAMAGE_RECEIVERS.map((receiver, i) => (
|
||||
<>
|
||||
<Image
|
||||
key={i}
|
||||
alt=""
|
||||
path={damageReceiverImages[receiver]}
|
||||
width={40}
|
||||
height={40}
|
||||
/>
|
||||
<div>{hitPoints[receiver]}</div>
|
||||
</>
|
||||
<div
|
||||
className="object-damage__grid"
|
||||
style={{ "--columns-count": String(2 + damages.length) } as any}
|
||||
>
|
||||
<div />
|
||||
<div />
|
||||
{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"}`)}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
{DAMAGE_RECEIVERS.map((receiver, i) => {
|
||||
const damageReceiverHp = hitPoints[receiver];
|
||||
|
||||
return (
|
||||
<React.Fragment key={receiver}>
|
||||
<Image
|
||||
key={i}
|
||||
alt=""
|
||||
path={damageReceiverImages[receiver]}
|
||||
width={40}
|
||||
height={40}
|
||||
/>
|
||||
<div className="object-damage__hp">{damageReceiverHp}hp</div>
|
||||
{damages.map((damage) => {
|
||||
const multiplier = multipliers[damage.type]![receiver];
|
||||
const damagePerHit = roundToTwoDecimalPlaces(
|
||||
damage.value * multiplier
|
||||
);
|
||||
|
||||
const hitsToDestroy = Math.ceil(damageReceiverHp / damagePerHit);
|
||||
|
||||
return (
|
||||
<div key={damage.id} className="object-damage__table-card">
|
||||
<div className="object-damage__table-card__results">
|
||||
<abbr className="object-damage__abbr" title="Damage">
|
||||
DMG
|
||||
</abbr>
|
||||
<div>{damagePerHit}</div>
|
||||
<abbr
|
||||
className="object-damage__abbr"
|
||||
title="Hits to destroy"
|
||||
>
|
||||
HTD
|
||||
</abbr>
|
||||
<div>{hitsToDestroy}</div>
|
||||
</div>
|
||||
<div className="object-damage__multiplier">×{multiplier}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,59 @@
|
|||
.object-damage__grid {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
gap: var(--s-3);
|
||||
grid-template-columns: repeat(var(--columns-count), 1fr);
|
||||
column-gap: var(--s-6);
|
||||
|
||||
/* xxx: make dynamic */
|
||||
grid-template-columns: max-content max-content 1fr 1fr 1fr 1fr 1fr;
|
||||
place-items: center;
|
||||
row-gap: var(--s-3);
|
||||
}
|
||||
|
||||
.object-damage__hp {
|
||||
font-size: var(--fonts-xs);
|
||||
font-weight: var(--semi-bold);
|
||||
}
|
||||
|
||||
.object-damage__table-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
border-radius: var(--rounded);
|
||||
background-color: var(--bg-darker);
|
||||
font-size: var(--fonts-xs);
|
||||
font-weight: var(--semi-bold);
|
||||
padding-block: var(--s-2);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.object-damage__table-card {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: var(--s-2);
|
||||
border-radius: var(--rounded);
|
||||
background-color: var(--bg-lighter);
|
||||
font-size: var(--fonts-xs);
|
||||
font-weight: var(--semi-bold);
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.object-damage__table-card__results {
|
||||
display: grid;
|
||||
column-gap: var(--s-2);
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.object-damage__abbr {
|
||||
color: var(--text-lighter);
|
||||
font-weight: var(--bold);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.object-damage__multiplier {
|
||||
color: var(--theme);
|
||||
font-size: var(--fonts-xxs);
|
||||
font-weight: var(--bold);
|
||||
letter-spacing: 0.5px;
|
||||
margin-block-start: var(--s-2);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user