mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-28 21:19:52 +01:00
Compare commits
2
Commits
1f767af0b5
...
af85fb74cb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af85fb74cb | ||
|
|
bd42cb44e4 |
No files matched your search
@@ -21,20 +21,20 @@
|
|||||||
padding: var(--space-2) var(--space-3);
|
padding: var(--space-2) var(--space-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-list-child="true"] {
|
&[data-list-child="true"]:not(:first-child),
|
||||||
&:not(:first-child) {
|
*:not(:first-child) > &[data-parent-list-child="true"] {
|
||||||
border-top-left-radius: 0;
|
border-top-left-radius: 0;
|
||||||
border-bottom-left-radius: 0;
|
border-bottom-left-radius: 0;
|
||||||
|
|
||||||
border-left-width: var(--space-0);
|
border-left-width: var(--space-0);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(:last-child) {
|
&[data-list-child="true"]:not(:last-child),
|
||||||
border-top-right-radius: 0;
|
*:not(:last-child) > &[data-parent-list-child="true"] {
|
||||||
border-bottom-right-radius: 0;
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
|
||||||
border-right-width: var(--space-0);
|
border-right-width: var(--space-0);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
svg,
|
svg,
|
||||||
@@ -74,6 +74,6 @@
|
|||||||
|
|
||||||
&[data-variant="surface"],
|
&[data-variant="surface"],
|
||||||
&[data-variant="outline"] {
|
&[data-variant="outline"] {
|
||||||
border: var(--space-1) solid var(--accent-6);
|
outline: var(--space-1) solid var(--accent-6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,8 +8,10 @@ export interface ButtonProps extends ComponentProps<"button"> {
|
|||||||
radius?: `${1 | 2 | 3}` | "max";
|
radius?: `${1 | 2 | 3}` | "max";
|
||||||
variant?: "solid" | "surface" | "soft" | "inverted" | "ghost" | "outline";
|
variant?: "solid" | "surface" | "soft" | "inverted" | "ghost" | "outline";
|
||||||
size?: "regular" | "minor";
|
size?: "regular" | "minor";
|
||||||
listChild?: boolean;
|
|
||||||
highContrast?: boolean; // TODO: add high contrast
|
highContrast?: boolean; // TODO: add high contrast
|
||||||
|
|
||||||
|
listChild?: boolean;
|
||||||
|
parentListChild?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LIGHT_TEXT_COLORS = new Set<Color>([
|
export const LIGHT_TEXT_COLORS = new Set<Color>([
|
||||||
@@ -34,7 +36,9 @@ export function Button({
|
|||||||
style,
|
style,
|
||||||
radius = "max",
|
radius = "max",
|
||||||
size = "regular",
|
size = "regular",
|
||||||
|
|
||||||
listChild,
|
listChild,
|
||||||
|
parentListChild,
|
||||||
...props
|
...props
|
||||||
}: ButtonProps) {
|
}: ButtonProps) {
|
||||||
return (
|
return (
|
||||||
@@ -46,6 +50,7 @@ export function Button({
|
|||||||
data-radius={radius}
|
data-radius={radius}
|
||||||
data-size={size}
|
data-size={size}
|
||||||
data-list-child={listChild}
|
data-list-child={listChild}
|
||||||
|
data-parent-list-child={parentListChild}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,4 +2,10 @@
|
|||||||
padding: var(--space-3);
|
padding: var(--space-3);
|
||||||
|
|
||||||
aspect-ratio: 1 / 1;
|
aspect-ratio: 1 / 1;
|
||||||
|
|
||||||
|
img,
|
||||||
|
svg {
|
||||||
|
width: 0.75em;
|
||||||
|
height: 0.75em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,7 @@ export function TankopediaCharacteristics({
|
|||||||
<div className={styles.characteristics}>
|
<div className={styles.characteristics}>
|
||||||
{characteristicsGroups.map((group) => {
|
{characteristicsGroups.map((group) => {
|
||||||
const items = group.order.filter(
|
const items = group.order.filter(
|
||||||
(item) => "name" in item && item.name in characteristics,
|
(item) => "name" in item && characteristics.has(item.name),
|
||||||
).length;
|
).length;
|
||||||
const shouldRender = items > 0;
|
const shouldRender = items > 0;
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ function Group({ group, characteristics }: GroupProps) {
|
|||||||
const strings = useStrings();
|
const strings = useStrings();
|
||||||
const toys = group.order.filter((item) => "toy" in item).length;
|
const toys = group.order.filter((item) => "toy" in item).length;
|
||||||
const items = group.order.filter(
|
const items = group.order.filter(
|
||||||
(item) => "name" in item && characteristics[item.name] !== null,
|
(item) => "name" in item && characteristics.has(item.name),
|
||||||
).length;
|
).length;
|
||||||
const groupSpan = `calc(${toys} * var(--group-span) + ${items} + 2)`;
|
const groupSpan = `calc(${toys} * var(--group-span) + ${items} + 2)`;
|
||||||
const contentSpan = `calc(${toys} * var(--group-span) + ${items})`;
|
const contentSpan = `calc(${toys} * var(--group-span) + ${items})`;
|
||||||
@@ -88,8 +88,8 @@ function Group({ group, characteristics }: GroupProps) {
|
|||||||
{group.order.map((item) => (
|
{group.order.map((item) => (
|
||||||
<>
|
<>
|
||||||
{"toy" in item && <Toy {...item} />}
|
{"toy" in item && <Toy {...item} />}
|
||||||
{"name" in item && characteristics[item.name] !== null && (
|
{"name" in item && characteristics.get(item.name) && (
|
||||||
<Item config={item} value={characteristics[item.name]!} />
|
<Item config={item} value={characteristics.get(item.name)!} />
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { alias } from "@blitzkit/core";
|
||||||
import type { Consumable, EquipmentSlot } from "@blitzkit/protos";
|
import type { Consumable, EquipmentSlot } from "@blitzkit/protos";
|
||||||
import { ClockIcon, ResetIcon, TrashIcon } from "@radix-ui/react-icons";
|
import { ClockIcon, ResetIcon, TrashIcon } from "@radix-ui/react-icons";
|
||||||
import { chunk } from "lodash-es";
|
import { chunk } from "lodash-es";
|
||||||
@@ -196,7 +197,7 @@ function ConsumableEntry({ id, consumable }: ConsumableProps) {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img src={`/media/consumables/${id}.webp`} />
|
<img src={alias("api", `/icons/consumables/${consumable.id}.webp`)} />
|
||||||
<div className={styles.info}>
|
<div className={styles.info}>
|
||||||
<Text lowContrast size="minor">
|
<Text lowContrast size="minor">
|
||||||
<div className={styles.entry}>
|
<div className={styles.entry}>
|
||||||
@@ -336,12 +337,12 @@ function EquipmentOption({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip tooltip={unwrap(specificEquipment.name)}>
|
<Tooltip tooltip={unwrap(specificEquipment.name)}>
|
||||||
<Button
|
<IconButton
|
||||||
color={isSelected ? undefined : "gray"}
|
color={isSelected ? undefined : "gray"}
|
||||||
variant={isSelected ? "surface" : "soft"}
|
variant={isSelected ? "surface" : "soft"}
|
||||||
data-selected={isSelected}
|
data-selected={isSelected}
|
||||||
radius="1"
|
radius="1"
|
||||||
listChild
|
parentListChild
|
||||||
className={styles.slot}
|
className={styles.slot}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
Tankopedia.mutate((draft) => {
|
Tankopedia.mutate((draft) => {
|
||||||
@@ -353,8 +354,12 @@ function EquipmentOption({
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img src={`/media/equipment/${id}.webp`} />
|
<img
|
||||||
</Button>
|
className={styles.icon}
|
||||||
|
alt={unwrap(specificEquipment.name)}
|
||||||
|
src={alias("api", `/icons/equipment/${id}.webp`)}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,12 +26,12 @@ export const characteristics = {
|
|||||||
|
|
||||||
damage: {
|
damage: {
|
||||||
type: CharacteristicType.Number,
|
type: CharacteristicType.Number,
|
||||||
compute({ characteristic, gun, shell, equalizer, assault_distance }) {
|
compute({ characteristic, gun, shell, environment }) {
|
||||||
let coefficient = 1;
|
let coefficient = 1;
|
||||||
|
|
||||||
if (gun.assault_ranges && gun.assault_ranges.types.includes(shell.type)) {
|
if (gun.assault_ranges && gun.assault_ranges.types.includes(shell.type)) {
|
||||||
const match = gun.assault_ranges.ranges.find(
|
const match = gun.assault_ranges.ranges.find(
|
||||||
({ distance }) => distance >= assault_distance,
|
({ distance }) => distance >= environment.assault_distance,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (match !== undefined) {
|
if (match !== undefined) {
|
||||||
@@ -39,7 +39,7 @@ export const characteristics = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
coefficient *= equalizer.damage;
|
// coefficient *= equalizer.damage;
|
||||||
|
|
||||||
return coefficient * shell.armor_damage;
|
return coefficient * shell.armor_damage;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { api } from "../api/dynamic";
|
||||||
|
import { characteristics } from "../config/characteristics";
|
||||||
|
import { Tankopedia, type TankEnvironment } from "../stores/tankopedia";
|
||||||
|
import type { TankState } from "../tankopedia/tankState";
|
||||||
|
import type {
|
||||||
|
CharacteristicContext,
|
||||||
|
CharacteristicName,
|
||||||
|
ComputedCharacteristics,
|
||||||
|
} from "../types/characteristics";
|
||||||
|
import type { DuelSide } from "./useEquipment";
|
||||||
|
|
||||||
|
const tanks = await api.tanks();
|
||||||
|
|
||||||
|
export function useCharacteristics(side: DuelSide) {
|
||||||
|
const state = Tankopedia.use((state) => state[side]);
|
||||||
|
const environment = Tankopedia.use((state) => state.environment);
|
||||||
|
|
||||||
|
return computeCharacteristics(state, environment);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function computeCharacteristics(
|
||||||
|
state: TankState,
|
||||||
|
environment: TankEnvironment,
|
||||||
|
) {
|
||||||
|
const computed: ComputedCharacteristics = new Map();
|
||||||
|
|
||||||
|
const tank = tanks.tanks[state.tank];
|
||||||
|
const engine = tank.engines.find((engine) => engine.id === state.engine)!;
|
||||||
|
const track = tank.tracks.find((track) => track.id === state.track)!;
|
||||||
|
const turret = tank.turrets.find((turret) => turret.id === state.turret)!;
|
||||||
|
const gun = turret.guns.find((gun) => gun.id === state.gun)!;
|
||||||
|
const shell = gun.shells.find((shell) => shell.id === state.shell)!;
|
||||||
|
|
||||||
|
function characteristic(name: CharacteristicName) {
|
||||||
|
if (!computed.has(name)) {
|
||||||
|
throw new Error(`Characteristic ${name} not computed yet`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return computed.get(name)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
const context = {
|
||||||
|
state,
|
||||||
|
environment,
|
||||||
|
|
||||||
|
tank,
|
||||||
|
engine,
|
||||||
|
track,
|
||||||
|
turret,
|
||||||
|
gun,
|
||||||
|
shell,
|
||||||
|
|
||||||
|
characteristic,
|
||||||
|
} satisfies CharacteristicContext;
|
||||||
|
|
||||||
|
for (const name in characteristics) {
|
||||||
|
const characteristic = characteristics[name as CharacteristicName];
|
||||||
|
let shouldRender = true;
|
||||||
|
|
||||||
|
if ("should_render" in characteristic) {
|
||||||
|
shouldRender = characteristic.should_render(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldRender) continue;
|
||||||
|
|
||||||
|
const value = characteristic.compute(context);
|
||||||
|
|
||||||
|
computed.set(name as CharacteristicName, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return computed;
|
||||||
|
}
|
||||||
@@ -43,14 +43,14 @@ const { accent, locale } = Astro.props;
|
|||||||
color: var(--gray-12);
|
color: var(--gray-12);
|
||||||
font-family: var(--font-family-regular);
|
font-family: var(--font-family-regular);
|
||||||
|
|
||||||
.app {
|
main {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
background-color: var(--gray-1);
|
background-color: var(--gray-1);
|
||||||
|
|
||||||
padding: var(--space-4);
|
/* padding: var(--space-4); */
|
||||||
gap: var(--space-6);
|
gap: var(--space-6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,9 +75,9 @@ const { accent, locale } = Astro.props;
|
|||||||
<body>
|
<body>
|
||||||
<slot name="pre-app" />
|
<slot name="pre-app" />
|
||||||
|
|
||||||
<div class="app">
|
<main>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</main>
|
||||||
|
|
||||||
<!-- <Footer {locale} /> -->
|
<!-- <Footer {locale} /> -->
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import { defaultEqualizer } from "../../../../../config/equalizer";
|
|||||||
import { withErrorWrapper } from "../../../../../hocs/withErrorWrapper";
|
import { withErrorWrapper } from "../../../../../hocs/withErrorWrapper";
|
||||||
import { withLocale } from "../../../../../hocs/withLocale";
|
import { withLocale } from "../../../../../hocs/withLocale";
|
||||||
import { useAwait } from "../../../../../hooks/useAwait";
|
import { useAwait } from "../../../../../hooks/useAwait";
|
||||||
|
import { useCharacteristics } from "../../../../../hooks/useCharacteristics";
|
||||||
import { Tankopedia } from "../../../../../stores/tankopedia";
|
import { Tankopedia } from "../../../../../stores/tankopedia";
|
||||||
import { computeCharacteristics } from "../../../../../tankopedia/computeCharacteristics";
|
|
||||||
import type { MaybeSkeletonComponentProps } from "../../../../../types/maybeSkeletonComponentProps";
|
import type { MaybeSkeletonComponentProps } from "../../../../../types/maybeSkeletonComponentProps";
|
||||||
import styles from "./_index.module.css";
|
import styles from "./_index.module.css";
|
||||||
|
|
||||||
@@ -26,14 +26,10 @@ export const Page = withErrorWrapper(
|
|||||||
Tankopedia.useInitialization(protagonistTank);
|
Tankopedia.useInitialization(protagonistTank);
|
||||||
|
|
||||||
const protagonist = Tankopedia.use((state) => state.protagonist);
|
const protagonist = Tankopedia.use((state) => state.protagonist);
|
||||||
// const protagonistEquipment = useEquipment("protagonist", protagonistTank.tank!);
|
const equalize = Tankopedia.use((state) => state.environment.equalize);
|
||||||
|
|
||||||
const characteristics = useMemo(
|
const characteristics = useCharacteristics("protagonist");
|
||||||
() => computeCharacteristics(),
|
|
||||||
[protagonist],
|
|
||||||
);
|
|
||||||
|
|
||||||
const equalize = Tankopedia.use((state) => state.equalize);
|
|
||||||
const thicknessRange = useMemo(() => {
|
const thicknessRange = useMemo(() => {
|
||||||
const entries = Object.values(tanks.tanks);
|
const entries = Object.values(tanks.tanks);
|
||||||
const filtered = entries.filter(
|
const filtered = entries.filter(
|
||||||
|
|||||||
@@ -75,14 +75,18 @@ export enum TankopediaRelativeAgainst {
|
|||||||
All,
|
All,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TankEnvironment {
|
||||||
|
equalize: boolean;
|
||||||
|
assault_distance: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface Tankopedia {
|
interface Tankopedia {
|
||||||
disturbed: boolean;
|
disturbed: boolean;
|
||||||
revealed: boolean;
|
revealed: boolean;
|
||||||
|
|
||||||
protagonist: TankState;
|
protagonist: TankState;
|
||||||
antagonist: TankState;
|
antagonist: TankState;
|
||||||
|
environment: TankEnvironment;
|
||||||
equalize: boolean;
|
|
||||||
|
|
||||||
shot?: Shot;
|
shot?: Shot;
|
||||||
skills: Record<string, number>;
|
skills: Record<string, number>;
|
||||||
@@ -121,7 +125,7 @@ export const Tankopedia = new Soapstone<Tankopedia, [TankDefinition]>(
|
|||||||
antagonist: createTankState(tank),
|
antagonist: createTankState(tank),
|
||||||
model: models.models[tank.id],
|
model: models.models[tank.id],
|
||||||
|
|
||||||
equalize: false,
|
environment: { equalize: false, assault_distance: 0 },
|
||||||
|
|
||||||
relativeAgainst: TankopediaRelativeAgainst.Class,
|
relativeAgainst: TankopediaRelativeAgainst.Class,
|
||||||
editStatic: false,
|
editStatic: false,
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import type { ComputedCharacteristics } from "../types/characteristics";
|
|
||||||
|
|
||||||
export function computeCharacteristics() {
|
|
||||||
const characteristics: Partial<ComputedCharacteristics> = {};
|
|
||||||
|
|
||||||
return characteristics as ComputedCharacteristics;
|
|
||||||
}
|
|
||||||
@@ -27,7 +27,10 @@ export function createTankState(tank: TankDefinition) {
|
|||||||
shell: gun.shells[0].id,
|
shell: gun.shells[0].id,
|
||||||
track: tank.tracks.at(-1)!.id,
|
track: tank.tracks.at(-1)!.id,
|
||||||
|
|
||||||
assault_distance: (gun.assault_ranges?.ranges[0].distance ?? 0) / 2,
|
environment: {
|
||||||
|
assault_distance: (gun.assault_ranges?.ranges[0].distance ?? 0) / 2,
|
||||||
|
},
|
||||||
|
|
||||||
speed: 0,
|
speed: 0,
|
||||||
|
|
||||||
equipment: {},
|
equipment: {},
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ export interface TankState {
|
|||||||
shell: number;
|
shell: number;
|
||||||
track: number;
|
track: number;
|
||||||
|
|
||||||
assault_distance: number;
|
|
||||||
speed: number;
|
speed: number;
|
||||||
|
|
||||||
equipment: Record<number, number>;
|
equipment: Record<number, number>;
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
import type { Strings } from "@blitzkit/i18n";
|
import type { Strings } from "@blitzkit/i18n";
|
||||||
import type {
|
import type {
|
||||||
Equalizer,
|
EngineDefinition,
|
||||||
GunDefinition,
|
GunDefinition,
|
||||||
ShellDefinition,
|
ShellDefinition,
|
||||||
|
TankDefinition,
|
||||||
|
TrackDefinition,
|
||||||
|
TurretDefinition,
|
||||||
} from "@blitzkit/protos";
|
} from "@blitzkit/protos";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { characteristics } from "../config/characteristics";
|
import { characteristics } from "../config/characteristics";
|
||||||
|
import type { TankEnvironment } from "../stores/tankopedia";
|
||||||
|
import type { TankState } from "../tankopedia/tankState";
|
||||||
|
|
||||||
export type Characteristic = {
|
export type Characteristic = {
|
||||||
should_render?(context: CharacteristicContext): boolean;
|
should_render?(context: CharacteristicContext): boolean;
|
||||||
@@ -20,14 +25,17 @@ export type Characteristic = {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
interface CharacteristicContext {
|
export interface CharacteristicContext {
|
||||||
|
state: TankState;
|
||||||
|
environment: TankEnvironment;
|
||||||
|
|
||||||
|
tank: TankDefinition;
|
||||||
|
engine: EngineDefinition;
|
||||||
|
track: TrackDefinition;
|
||||||
|
turret: TurretDefinition;
|
||||||
gun: GunDefinition;
|
gun: GunDefinition;
|
||||||
shell: ShellDefinition;
|
shell: ShellDefinition;
|
||||||
|
|
||||||
equalizer: Equalizer;
|
|
||||||
|
|
||||||
assault_distance: number;
|
|
||||||
|
|
||||||
characteristic(
|
characteristic(
|
||||||
name: CharacteristicName,
|
name: CharacteristicName,
|
||||||
): ReturnType<Characteristic["compute"]>;
|
): ReturnType<Characteristic["compute"]>;
|
||||||
@@ -41,8 +49,9 @@ export enum CharacteristicType {
|
|||||||
export type CharacteristicName = keyof typeof characteristics;
|
export type CharacteristicName = keyof typeof characteristics;
|
||||||
export type CharacteristicReturnType = ReturnType<Characteristic["compute"]>;
|
export type CharacteristicReturnType = ReturnType<Characteristic["compute"]>;
|
||||||
|
|
||||||
export type ComputedCharacteristics = Partial<
|
export type ComputedCharacteristics = Map<
|
||||||
Record<CharacteristicName, CharacteristicReturnType>
|
CharacteristicName,
|
||||||
|
CharacteristicReturnType
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export interface CharacteristicRenderConfig {
|
export interface CharacteristicRenderConfig {
|
||||||
|
|||||||
Reference in new issue
Block a user