mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
start of port
This commit is contained in:
1 parent
3902c63a65
commit
f935adb70e
5 files changed
+260
-10
No files matched your search
@@ -135,10 +135,6 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
background-color: var(--gray-2);
|
background-color: var(--gray-2);
|
||||||
outline: var(--gray-5) solid var(--space-1);
|
outline: var(--gray-5) solid var(--space-1);
|
||||||
border-radius: var(--space-3);
|
border-radius: var(--space-3);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { TIER_ROMAN_NUMERALS } from "@blitzkit/core";
|
|||||||
import { literals } from "@blitzkit/i18n/src/literals";
|
import { literals } from "@blitzkit/i18n/src/literals";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { characteristicsGroups } from "../../config/characteristicsGroups";
|
import { characteristicsGroups } from "../../config/characteristicsGroups";
|
||||||
|
import { toys } from "../../config/toys";
|
||||||
import { useCharacteristicRenderer } from "../../hooks/useCharacteristicRenderer";
|
import { useCharacteristicRenderer } from "../../hooks/useCharacteristicRenderer";
|
||||||
import { DuelModule, useDuel } from "../../hooks/useDuel";
|
import { DuelModule, useDuel } from "../../hooks/useDuel";
|
||||||
import { useRankings } from "../../hooks/useRankings";
|
import { useRankings } from "../../hooks/useRankings";
|
||||||
@@ -14,6 +15,7 @@ import type {
|
|||||||
CharacteristicReturnType,
|
CharacteristicReturnType,
|
||||||
CharacteristicsGroup,
|
CharacteristicsGroup,
|
||||||
ComputedCharacteristics,
|
ComputedCharacteristics,
|
||||||
|
ToyName,
|
||||||
} from "../../types/characteristics";
|
} from "../../types/characteristics";
|
||||||
import { Heading } from "../Heading";
|
import { Heading } from "../Heading";
|
||||||
import { SegmentedControl } from "../SegmentedControl";
|
import { SegmentedControl } from "../SegmentedControl";
|
||||||
@@ -107,7 +109,9 @@ function Group({ group, characteristics, rankings }: GroupProps) {
|
|||||||
>
|
>
|
||||||
{group.order.map((item) => (
|
{group.order.map((item) => (
|
||||||
<>
|
<>
|
||||||
{"toy" in item && <Toy {...item} />}
|
{"toy" in item && (
|
||||||
|
<ToyAssigner characteristics={characteristics} {...item} />
|
||||||
|
)}
|
||||||
{"label" in item && <Label {...item} />}
|
{"label" in item && <Label {...item} />}
|
||||||
{"name" in item && characteristics.has(item.name) && (
|
{"name" in item && characteristics.has(item.name) && (
|
||||||
<Item
|
<Item
|
||||||
@@ -257,15 +261,22 @@ function Bar({ value }: BarProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ToyProps {
|
interface ToyAssignerProps {
|
||||||
toy: string;
|
toy: ToyName;
|
||||||
|
characteristics: ComputedCharacteristics;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Toy({ toy }: ToyProps) {
|
function ToyAssigner({ toy, characteristics }: ToyAssignerProps) {
|
||||||
|
const Toy = toys[toy];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.toy}>
|
<div className={styles.toy}>
|
||||||
<div className={styles.sandbox}>
|
<div className={styles.sandbox}>
|
||||||
<Text lowContrast>{toy} toy</Text>
|
{Toy ? (
|
||||||
|
<Toy characteristics={characteristics} />
|
||||||
|
) : (
|
||||||
|
<Text lowContrast>{toy}</Text>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,229 @@
|
|||||||
|
import { isExplosive } from "@blitzkit/core";
|
||||||
|
import { useRef, useState } from "react";
|
||||||
|
import { degToRad } from "three/src/math/MathUtils.js";
|
||||||
|
import type { ToyProps } from "../../config/toys";
|
||||||
|
import { DuelModule, useDuel } from "../../hooks/useDuel";
|
||||||
|
import { useStrings } from "../../hooks/useStrings";
|
||||||
|
|
||||||
|
export function RicochetToy({ characteristics }: ToyProps) {
|
||||||
|
const strings = useStrings();
|
||||||
|
|
||||||
|
const shell = useDuel("protagonist", DuelModule.Shell);
|
||||||
|
|
||||||
|
const container = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const normalization = degToRad(
|
||||||
|
(characteristics.get("normalization") as number) ?? 0,
|
||||||
|
);
|
||||||
|
const ricochet = degToRad((characteristics.get("ricochet") as number) ?? 90);
|
||||||
|
const explodes = isExplosive(shell.type);
|
||||||
|
|
||||||
|
const [rawAngle, setAngle] = useState(degToRad(-25));
|
||||||
|
|
||||||
|
const angle = explodes ? degToRad(-70) : rawAngle;
|
||||||
|
const doesRicochet = Math.abs(angle) >= ricochet;
|
||||||
|
const effectiveAngle = doesRicochet
|
||||||
|
? -angle + Math.PI
|
||||||
|
: Math.sign(angle) * Math.max(0, Math.abs(angle) - normalization);
|
||||||
|
|
||||||
|
const armorPercentage = Math.min(999, 100 / Math.cos(effectiveAngle));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
onPointerMove={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (!container.current || explodes) return;
|
||||||
|
|
||||||
|
const rect = container.current.getBoundingClientRect();
|
||||||
|
const x = event.clientX - rect.left - rect.width / 3;
|
||||||
|
const y = event.clientY - rect.top - rect.height / 2;
|
||||||
|
const angle = clamp(Math.atan2(-y, x), -Math.PI / 2, Math.PI / 2);
|
||||||
|
|
||||||
|
setAngle(angle);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
width="33%"
|
||||||
|
height="100%"
|
||||||
|
position="absolute"
|
||||||
|
top="0"
|
||||||
|
left="0"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(to right, transparent, ${Var("gray-a4")})`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
style={{
|
||||||
|
opacity: 2 ** -3,
|
||||||
|
backgroundImage: `url(/assets/images/tankopedia/visualizers/ricochet/armor-hash.png)`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
left="33%"
|
||||||
|
top="0"
|
||||||
|
width="67%"
|
||||||
|
height="100%"
|
||||||
|
position="absolute"
|
||||||
|
style={{
|
||||||
|
background: `conic-gradient(
|
||||||
|
at 0 50%,
|
||||||
|
|
||||||
|
${Var("ruby-a2")},
|
||||||
|
${Var("ruby-a1")} ${Math.PI / 2 - ricochet}rad,
|
||||||
|
|
||||||
|
${Var("jade-a2")} ${Math.PI / 2 - ricochet}rad,
|
||||||
|
${Var("jade-a4")} ${Math.PI / 2 - normalization}rad,
|
||||||
|
|
||||||
|
${Var("cyan-a5")} ${Math.PI / 2 - normalization}rad,
|
||||||
|
${Var("cyan-a3")} ${Math.PI / 2 + normalization}rad,
|
||||||
|
|
||||||
|
${Var("jade-a2")} ${Math.PI / 2 + normalization}rad,
|
||||||
|
${Var("jade-a4")} ${Math.PI / 2 + ricochet}rad,
|
||||||
|
|
||||||
|
${Var("ruby-a1")} ${Math.PI / 2 + ricochet}rad,
|
||||||
|
${Var("ruby-a2")} 180deg
|
||||||
|
`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
left="33%"
|
||||||
|
top="50%"
|
||||||
|
height="18rem"
|
||||||
|
style={{
|
||||||
|
transformOrigin: "top center",
|
||||||
|
transform: `translateX(-50%) rotate(${-Math.PI / 2 - angle}rad)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
width="1px"
|
||||||
|
height="100%"
|
||||||
|
bottom="100%"
|
||||||
|
style={{
|
||||||
|
transform: "translateX(-50%)",
|
||||||
|
backgroundColor: Var("gray-a8"),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
width="3px"
|
||||||
|
height="100%"
|
||||||
|
style={{
|
||||||
|
transform: "translateX(-50%)",
|
||||||
|
background: `linear-gradient(${Var("gray-a11")}, ${Var("gray-a4")})`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<img
|
||||||
|
src={alias("api", `/icons/shells/${shell.icon}.webp`)}
|
||||||
|
style={{
|
||||||
|
objectFit: "contain",
|
||||||
|
transform: "translate(-50%, -50%) rotate(-45deg)",
|
||||||
|
position: "absolute",
|
||||||
|
width: "2rem",
|
||||||
|
height: "2rem",
|
||||||
|
top: "6rem",
|
||||||
|
left: 0,
|
||||||
|
filter: "drop-shadow(0px 0px 0.25rem black)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
left="33%"
|
||||||
|
top="50%"
|
||||||
|
height="16rem"
|
||||||
|
style={{
|
||||||
|
transformOrigin: "top center",
|
||||||
|
transform: `translateX(-50%) rotate(${-Math.PI / 2 - effectiveAngle}rad)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
width="3px"
|
||||||
|
height="100%"
|
||||||
|
top="-100%"
|
||||||
|
style={{
|
||||||
|
transform: "translateX(-50%)",
|
||||||
|
background: `linear-gradient(${Var("gray-a4")}, ${Var("gray-a11")})`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<VisualizerCornerStat
|
||||||
|
label={strings.website.tools.tankopedia.visualizers.ricochet.angle}
|
||||||
|
value={literals(strings.common.units.deg, {
|
||||||
|
value: radToDeg(Math.abs(angle)).toFixed(0),
|
||||||
|
})}
|
||||||
|
side="top-left"
|
||||||
|
/>
|
||||||
|
<VisualizerCornerStat
|
||||||
|
label={
|
||||||
|
strings.website.tools.tankopedia.visualizers.ricochet.normalization
|
||||||
|
}
|
||||||
|
value={
|
||||||
|
doesRicochet
|
||||||
|
? "-"
|
||||||
|
: literals(strings.common.units.deg, {
|
||||||
|
value: radToDeg(
|
||||||
|
Math.min(normalization, Math.abs(angle)),
|
||||||
|
).toFixed(0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
side="top-right"
|
||||||
|
/>
|
||||||
|
<VisualizerCornerStat
|
||||||
|
label={
|
||||||
|
strings.website.tools.tankopedia.visualizers.ricochet.effective_angle
|
||||||
|
}
|
||||||
|
value={
|
||||||
|
doesRicochet
|
||||||
|
? "-"
|
||||||
|
: literals(strings.common.units.deg, {
|
||||||
|
value: radToDeg(Math.abs(effectiveAngle)).toFixed(0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
side="bottom-left"
|
||||||
|
/>
|
||||||
|
<VisualizerCornerStat
|
||||||
|
label={strings.website.tools.tankopedia.visualizers.ricochet.armor}
|
||||||
|
value={
|
||||||
|
doesRicochet
|
||||||
|
? "-"
|
||||||
|
: literals(strings.common.units.percentage, {
|
||||||
|
value: armorPercentage.toFixed(0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
side="bottom-right"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{explodes && (
|
||||||
|
<Flex
|
||||||
|
p="3"
|
||||||
|
justify="center"
|
||||||
|
align="center"
|
||||||
|
height="100%"
|
||||||
|
width="100%"
|
||||||
|
position="absolute"
|
||||||
|
top="0"
|
||||||
|
left="0"
|
||||||
|
style={{
|
||||||
|
background: `radial-gradient(${Var("black-a7")}, ${Var("black-a11")})`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text color="gray" align="center">
|
||||||
|
{strings.website.tools.tankopedia.visualizers.ricochet.no_ricochet}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
|
import { RicochetToy } from "../components/TankopediaToys";
|
||||||
|
import type { ComputedCharacteristics } from "../types/characteristics";
|
||||||
|
|
||||||
|
export interface ToyProps {
|
||||||
|
characteristics: ComputedCharacteristics;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const toys: Record<string, (props: ToyProps) => ReactNode> = {
|
||||||
|
ricochet: RicochetToy,
|
||||||
|
};
|
||||||
@@ -11,6 +11,7 @@ import type {
|
|||||||
} from "@blitzkit/protos";
|
} from "@blitzkit/protos";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import type { characteristics } from "../config/characteristics";
|
import type { characteristics } from "../config/characteristics";
|
||||||
|
import type { toys } from "../config/toys";
|
||||||
import type { DuelSide } from "../hooks/useEquipment";
|
import type { DuelSide } from "../hooks/useEquipment";
|
||||||
import type { TankEnvironment } from "../stores/tankopedia";
|
import type { TankEnvironment } from "../stores/tankopedia";
|
||||||
import type { TankState } from "../tankopedia/tankState";
|
import type { TankState } from "../tankopedia/tankState";
|
||||||
@@ -99,9 +100,11 @@ export interface ToyRenderConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface LabelRenderConfig {
|
export interface LabelRenderConfig {
|
||||||
label: string;
|
label: ToyName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ToyName = keyof typeof toys;
|
||||||
|
|
||||||
export type CharacteristicsGroup = {
|
export type CharacteristicsGroup = {
|
||||||
name: string;
|
name: string;
|
||||||
order: (CharacteristicRenderConfig | LabelRenderConfig | ToyRenderConfig)[];
|
order: (CharacteristicRenderConfig | LabelRenderConfig | ToyRenderConfig)[];
|
||||||
|
|||||||
Reference in new issue
Block a user