EffectsSelector with LDE dropdown

This commit is contained in:
Kalle
2022-09-16 20:19:43 +03:00
parent 4184eda385
commit e57f1d63c1
7 changed files with 148 additions and 31 deletions

View File

@@ -0,0 +1,2 @@
export const MAX_LDE_INTENSITY = 21;
export const MAX_AP = 57;

View File

@@ -7,3 +7,7 @@ export type {
} from "./types";
export { useAnalyzeBuild } from "./useAnalyzeBuild";
export { MAX_LDE_INTENSITY } from "./constants";
export { lastDitchEffortIntensityToAp } from "./specialEffects";

View File

@@ -1,8 +1,24 @@
import { MAX_AP } from "./constants";
import type { AbilityPoints } from "./types";
const MAX_AP = 57;
export const SPECIAL_EFFECTS = [
{
type: "DR",
values: [
{
type: "SSU",
ap: 10,
},
{
type: "RSU",
ap: 10,
},
{
type: "RES",
ap: 10,
},
],
},
{
type: "OG",
values: [
@@ -53,23 +69,6 @@ export const SPECIAL_EFFECTS = [
},
],
},
{
type: "DR",
values: [
{
type: "SSU",
ap: 10,
},
{
type: "RSU",
ap: 10,
},
{
type: "RES",
ap: 10,
},
],
},
{
type: "TACTICOOLER",
values: [
@@ -107,8 +106,12 @@ export const SPECIAL_EFFECTS = [
},
] as const;
export function lastDitchEffortIntensityToAp(intensity: number) {
return Math.floor((24 / 21) * intensity);
}
function lastDitchEffortValues(intensity: number) {
const ap = Math.floor((24 / 21) * intensity);
const ap = lastDitchEffortIntensityToAp(intensity);
return [
{

View File

@@ -9,6 +9,7 @@ import {
isAbility,
} from "../in-game-lists";
import type { AbilityType, AbilityWithUnknown } from "../in-game-lists/types";
import { MAX_LDE_INTENSITY } from "./constants";
import { applySpecialEffects, SPECIAL_EFFECTS } from "./specialEffects";
import { buildStats } from "./stats";
import type { SpecialEffectType } from "./types";
@@ -68,6 +69,7 @@ export function useAnalyzeBuild() {
handleChange,
analyzed,
abilityPoints,
ldeIntensity,
};
}
@@ -142,7 +144,6 @@ function validateAbility(
throw new Error("Invalid ability");
}
const MAX_LDE_INTENSITY = 21;
function validatedLdeIntensityFromSearchParams(searchParams: URLSearchParams) {
const ldeIntensity = searchParams.get("lde")
? Number(searchParams.get("lde"))
@@ -171,6 +172,7 @@ function validatedEffectsFromSearchParams({
const effects = searchParams.getAll("effect");
const effectsNoDuplicates = [...new Set(effects)];
const abilities = build.flat();
for (const effect of effectsNoDuplicates) {
const effectObj = SPECIAL_EFFECTS.find((e) => e.type === effect);
@@ -178,12 +180,18 @@ function validatedEffectsFromSearchParams({
// e.g. even if OG effect is active in state
// it can't be on unless build has OG
if (isAbility(effect) && !build.flat().includes(effect)) {
if (isAbility(effect) && !abilities.includes(effect)) {
continue;
}
result.push(effect as SpecialEffectType);
}
// lde is a special case in that it's always
// considered active when in the build
if (abilities.includes("LDE") && !result.includes("LDE")) {
result.push("LDE");
}
return result;
}

View File

@@ -1,4 +1,5 @@
import { type MetaFunction, type LinksFunction } from "@remix-run/node";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { AbilitiesSelector } from "~/components/AbilitiesSelector";
import { Ability } from "~/components/Ability";
@@ -7,10 +8,17 @@ import { Image } from "~/components/Image";
import { Main } from "~/components/Main";
import { useSetTitle } from "~/hooks/useSetTitle";
import type { AnalyzedBuild, Stat } from "~/modules/analyzer";
import { MAX_LDE_INTENSITY } from "~/modules/analyzer";
import { useAnalyzeBuild } from "~/modules/analyzer";
import {
lastDitchEffortIntensityToAp,
SPECIAL_EFFECTS,
} from "~/modules/analyzer/specialEffects";
import type { AbilityPoints } from "~/modules/analyzer/types";
import type { BuildAbilitiesTupleWithUnknown } from "~/modules/in-game-lists";
import {
abilities,
isAbility,
type MainWeaponId,
type SubWeaponId,
} from "~/modules/in-game-lists";
@@ -37,8 +45,14 @@ export const handle = {
export default function BuildAnalyzerPage() {
const { t } = useTranslation(["analyzer", "common"]);
useSetTitle(t("common:pages.buildAnalyzer"));
const { build, mainWeaponId, handleChange, analyzed, abilityPoints } =
useAnalyzeBuild();
const {
build,
mainWeaponId,
handleChange,
analyzed,
abilityPoints,
ldeIntensity,
} = useAnalyzeBuild();
// xxx: remove before prod
if (process.env.NODE_ENV === "production") return <Main>Coming soon :)</Main>;
@@ -64,11 +78,20 @@ export default function BuildAnalyzerPage() {
</div>
<WeaponInfoBadges analyzed={analyzed} />
</div>
<AbilitiesSelector
selectedAbilities={build}
onChange={(newBuild) => handleChange({ newBuild })}
/>
<AbilityPointsDetails abilityPoints={abilityPoints} />
<div className="stack md items-center">
<AbilitiesSelector
selectedAbilities={build}
onChange={(newBuild) => handleChange({ newBuild })}
/>
<EffectsSelector
build={build}
ldeIntensity={ldeIntensity}
handleLdeIntensityChange={(newLdeIntensity) =>
handleChange({ newLdeIntensity })
}
/>
<AbilityPointsDetails abilityPoints={abilityPoints} />
</div>
<div className="analyzer__patch">
{t("analyzer:patch")} {CURRENT_PATCH}
</div>
@@ -233,6 +256,71 @@ function WeaponInfoBadges({ analyzed }: { analyzed: AnalyzedBuild }) {
);
}
function EffectsSelector({
build,
ldeIntensity,
handleLdeIntensityChange,
}: {
build: BuildAbilitiesTupleWithUnknown;
ldeIntensity: number;
handleLdeIntensityChange: (newLdeIntensity: number) => void;
}) {
const { t } = useTranslation(["weapons", "analyzer"]);
const effectsToShow = SPECIAL_EFFECTS.filter(
(effect) => !isAbility(effect.type) || build.flat().includes(effect.type)
).reverse(); // reverse to show Tacticooler first as it always shows
return (
<div className="analyzer__effects-selector">
{effectsToShow.map((effect) => {
return (
<React.Fragment key={effect.type}>
<div>
{isAbility(effect.type) ? (
<Ability ability={effect.type} size="SUB" />
) : (
<Image
path={specialWeaponImageUrl(15)}
alt={t("weapons:SPECIAL_15")}
height={32}
width={32}
/>
)}
</div>
<div>
{effect.type === "LDE" ? (
<select
value={ldeIntensity}
onChange={(e) =>
handleLdeIntensityChange(Number(e.target.value))
}
className="analyzer__lde-intensity-select"
>
{new Array(MAX_LDE_INTENSITY + 1).fill(null).map((_, i) => {
const percentage = ((i / MAX_LDE_INTENSITY) * 100)
.toFixed(2)
.replace(".00", "");
return (
<option key={i} value={i}>
{percentage}% (+{lastDitchEffortIntensityToAp(i)}{" "}
{t("analyzer:abilityPoints.short")})
</option>
);
})}
</select>
) : (
<div />
)}
</div>
</React.Fragment>
);
})}
</div>
);
}
function AbilityPointsDetails({
abilityPoints,
}: {

View File

@@ -34,19 +34,30 @@
padding-inline: var(--s-1-5);
}
.analyzer__effects-selector {
display: grid;
gap: var(--s-2);
grid-template-columns: 1fr 2.5fr;
place-items: center;
}
.analyzer__lde-intensity-select {
font-size: var(--fonts-xxs);
}
.analyzer__ap-summary {
width: 100%;
background-color: var(--bg-lighter);
border-radius: var(--rounded);
font-size: var(--fonts-xxs);
font-weight: var(--semi-bold);
width: 100%;
padding-block: var(--s-1);
padding-inline: var(--s-2);
}
.analyzer__ap-text {
font-size: var(--fonts-xxs);
color: var(--text-lighter);
font-size: var(--fonts-xxs);
font-weight: var(--semi-bold);
}

View File

@@ -54,5 +54,6 @@
"build": "Build",
"patch": "Patch:",
"abilityPoints": "Ability points",
"abilityPoints.short": "AP",
"consumptionExplanation": "This chart shows the amount of actions left to perform with main weapon after using sub 0-{{maxSubsToUse}} times. Max amount of consecutive subs to use with full ink tank is {{maxSubsToUse}}."
}