mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
feat: consumable buttons
This commit is contained in:
1 parent
a6c3ac36ef
commit
ad1ad80026
7 files changed
+120
-26
No files matched your search
Vendored
+1
@@ -46,6 +46,7 @@
|
|||||||
"glsl",
|
"glsl",
|
||||||
"GLTF",
|
"GLTF",
|
||||||
"immer",
|
"immer",
|
||||||
|
"Inclusivity",
|
||||||
"JOINTINDEX",
|
"JOINTINDEX",
|
||||||
"JOINTWEIGHT",
|
"JOINTWEIGHT",
|
||||||
"kranvagn",
|
"kranvagn",
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import { readYAML } from '../../src/core/blitz/readYAML';
|
|||||||
import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL';
|
import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL';
|
||||||
import { toUniqueId } from '../../src/core/blitz/toUniqueId';
|
import { toUniqueId } from '../../src/core/blitz/toUniqueId';
|
||||||
import { commitAssets } from '../../src/core/blitzkrieg/commitAssets';
|
import { commitAssets } from '../../src/core/blitzkrieg/commitAssets';
|
||||||
import { ConsumableDefinitions } from '../../src/core/blitzkrieg/consumablesDefinitions';
|
import {
|
||||||
|
ConsumableDefinitionFilterCategory,
|
||||||
|
ConsumableDefinitions,
|
||||||
|
} from '../../src/core/blitzkrieg/consumablesDefinitions';
|
||||||
import {
|
import {
|
||||||
EquipmentDefinitions,
|
EquipmentDefinitions,
|
||||||
EquipmentPreset,
|
EquipmentPreset,
|
||||||
@@ -666,7 +669,9 @@ export async function buildDefinitions(production: boolean) {
|
|||||||
} else if ('extendedTags' in excludeRaw) {
|
} else if ('extendedTags' in excludeRaw) {
|
||||||
consumableDefinitions[consumable.id].exclude!.push({
|
consumableDefinitions[consumable.id].exclude!.push({
|
||||||
type: 'category',
|
type: 'category',
|
||||||
categories: excludeRaw.extendedTags.split(' '),
|
categories: excludeRaw.extendedTags.split(
|
||||||
|
' ',
|
||||||
|
) as ConsumableDefinitionFilterCategory[],
|
||||||
});
|
});
|
||||||
} else throw new SyntaxError('Unhandled exclude type');
|
} else throw new SyntaxError('Unhandled exclude type');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Flex, Heading } from '@radix-ui/themes';
|
import { Flex, Heading } from '@radix-ui/themes';
|
||||||
import { use } from 'react';
|
import { use } from 'react';
|
||||||
import { ModuleButton } from '../../../../../components/ModuleButton';
|
import { ModuleButton } from '../../../../../components/ModuleButton';
|
||||||
|
import { checkConsumableInclusivity } from '../../../../../core/blitzkrieg/checkConsumableInclusivity';
|
||||||
|
import { consumableDefinitions } from '../../../../../core/blitzkrieg/consumablesDefinitions';
|
||||||
import { equipmentDefinitions } from '../../../../../core/blitzkrieg/equipmentDefinitions';
|
import { equipmentDefinitions } from '../../../../../core/blitzkrieg/equipmentDefinitions';
|
||||||
import { TIER_ROMAN_NUMERALS } from '../../../../../core/blitzkrieg/tankDefinitions';
|
import { TIER_ROMAN_NUMERALS } from '../../../../../core/blitzkrieg/tankDefinitions';
|
||||||
import { mutateDuel, useDuel } from '../../../../../stores/duel';
|
import { mutateDuel, useDuel } from '../../../../../stores/duel';
|
||||||
@@ -12,11 +14,13 @@ import {
|
|||||||
export function Configure() {
|
export function Configure() {
|
||||||
const protagonist = useDuel((state) => state.protagonist!);
|
const protagonist = useDuel((state) => state.protagonist!);
|
||||||
const awaitedEquipmentDefinitions = use(equipmentDefinitions);
|
const awaitedEquipmentDefinitions = use(equipmentDefinitions);
|
||||||
|
const awaitedConsumableDefinitions = use(consumableDefinitions);
|
||||||
const equipmentRows =
|
const equipmentRows =
|
||||||
awaitedEquipmentDefinitions.presets[protagonist.tank.equipment];
|
awaitedEquipmentDefinitions.presets[protagonist.tank.equipment];
|
||||||
const equipmentMatrix = useTankopediaTemporary(
|
const equipmentMatrix = useTankopediaTemporary(
|
||||||
(state) => state.equipmentMatrix,
|
(state) => state.equipmentMatrix,
|
||||||
);
|
);
|
||||||
|
const consumables = useTankopediaTemporary((state) => state.consumables);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex gap="4" direction="column">
|
<Flex gap="4" direction="column">
|
||||||
@@ -118,6 +122,35 @@ export function Configure() {
|
|||||||
|
|
||||||
<Flex gap="2" direction="column">
|
<Flex gap="2" direction="column">
|
||||||
<Heading size="4">Consumables</Heading>
|
<Heading size="4">Consumables</Heading>
|
||||||
|
|
||||||
|
{Object.values(awaitedConsumableDefinitions)
|
||||||
|
.filter((consumable) =>
|
||||||
|
checkConsumableInclusivity(
|
||||||
|
consumable,
|
||||||
|
protagonist.tank,
|
||||||
|
protagonist.gun,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.map((consumable) => (
|
||||||
|
<ModuleButton
|
||||||
|
first
|
||||||
|
last
|
||||||
|
type="consumable"
|
||||||
|
consumable={consumable.id}
|
||||||
|
selected={consumables.includes(consumable.id)}
|
||||||
|
onClick={() => {
|
||||||
|
mutateTankopediaTemporary((draft) => {
|
||||||
|
if (draft.consumables.includes(consumable.id)) {
|
||||||
|
draft.consumables = draft.consumables.filter(
|
||||||
|
(id) => id !== consumable.id,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
draft.consumables.push(consumable.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Button, Text } from '@radix-ui/themes';
|
import { Button, Text } from '@radix-ui/themes';
|
||||||
import { ComponentProps } from 'react';
|
import { CSSProperties, ComponentProps } from 'react';
|
||||||
import { asset } from '../core/blitzkrieg/asset';
|
import { asset } from '../core/blitzkrieg/asset';
|
||||||
|
|
||||||
type ModuleButtonProps = Omit<ComponentProps<typeof Button>, 'type'> & {
|
type ModuleButtonProps = Omit<ComponentProps<typeof Button>, 'type'> & {
|
||||||
@@ -21,6 +21,10 @@ type ModuleButtonProps = Omit<ComponentProps<typeof Button>, 'type'> & {
|
|||||||
type: 'equipment';
|
type: 'equipment';
|
||||||
equipment: number;
|
equipment: number;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: 'consumable';
|
||||||
|
consumable: number;
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export function ModuleButton({
|
export function ModuleButton({
|
||||||
@@ -30,10 +34,26 @@ export function ModuleButton({
|
|||||||
last = false,
|
last = false,
|
||||||
rowChild,
|
rowChild,
|
||||||
discriminator,
|
discriminator,
|
||||||
|
disabled,
|
||||||
...props
|
...props
|
||||||
}: ModuleButtonProps) {
|
}: ModuleButtonProps) {
|
||||||
|
const imageStyles: CSSProperties = {
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
position: 'absolute',
|
||||||
|
opacity: disabled ? 0.5 : 1,
|
||||||
|
transform: 'scale(0.75)',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (props.type === 'module') {
|
||||||
|
imageStyles.top = '50%';
|
||||||
|
imageStyles.left = '50%';
|
||||||
|
imageStyles.transform = 'translate(calc(-50% + 2px), calc(-50% + 2px))';
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
disabled={disabled}
|
||||||
radius="medium"
|
radius="medium"
|
||||||
color={selected ? undefined : 'gray'}
|
color={selected ? undefined : 'gray'}
|
||||||
variant={selected ? 'surface' : 'soft'}
|
variant={selected ? 'surface' : 'soft'}
|
||||||
@@ -57,22 +77,11 @@ export function ModuleButton({
|
|||||||
? `icons/modules/${props.module}.webp`
|
? `icons/modules/${props.module}.webp`
|
||||||
: props.type === 'shell'
|
: props.type === 'shell'
|
||||||
? `icons/shells/${props.shell}.webp`
|
? `icons/shells/${props.shell}.webp`
|
||||||
: `icons/equipment/${props.equipment}.webp`,
|
: props.type === 'consumable'
|
||||||
|
? `icons/consumables/${props.consumable}.webp`
|
||||||
|
: `icons/equipment/${props.equipment}.webp`,
|
||||||
)}
|
)}
|
||||||
style={{
|
style={imageStyles}
|
||||||
width: 32,
|
|
||||||
height: 32,
|
|
||||||
position: 'absolute',
|
|
||||||
...(props.type === 'module'
|
|
||||||
? {
|
|
||||||
top: '50%',
|
|
||||||
left: '50%',
|
|
||||||
transform: 'translate(calc(-50% + 2px), calc(-50% + 2px))',
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
transform: 'scale(0.75)',
|
|
||||||
}),
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
{discriminator !== undefined && (
|
{discriminator !== undefined && (
|
||||||
<Text
|
<Text
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { ConsumableEntry } from './consumablesDefinitions';
|
||||||
|
import { GunDefinition, TankDefinition } from './tankDefinitions';
|
||||||
|
|
||||||
|
export function checkConsumableInclusivity(
|
||||||
|
consumable: ConsumableEntry,
|
||||||
|
tank: TankDefinition,
|
||||||
|
gun: GunDefinition,
|
||||||
|
) {
|
||||||
|
const included = consumable.include.every((rule) => {
|
||||||
|
switch (rule.type) {
|
||||||
|
case 'tier':
|
||||||
|
return rule.min <= tank.tier && tank.tier <= rule.max;
|
||||||
|
|
||||||
|
case 'ids':
|
||||||
|
return rule.ids.includes(tank.id);
|
||||||
|
|
||||||
|
case 'category':
|
||||||
|
throw new SyntaxError('Category filtering found in include rule');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const excluded = consumable.exclude?.some((rule) => {
|
||||||
|
switch (rule.type) {
|
||||||
|
case 'tier':
|
||||||
|
return rule.min <= tank.tier && tank.tier <= rule.max;
|
||||||
|
|
||||||
|
case 'ids':
|
||||||
|
return rule.ids.includes(tank.id);
|
||||||
|
|
||||||
|
case 'category':
|
||||||
|
return rule.categories.some((category) => {
|
||||||
|
switch (category) {
|
||||||
|
case 'clip':
|
||||||
|
return gun.type !== 'regular';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return included && !excluded;
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import { asset } from './asset';
|
import { asset } from './asset';
|
||||||
|
|
||||||
|
export type ConsumableDefinitionFilterCategory = 'clip';
|
||||||
|
|
||||||
type ConsumableDefinitionFilter =
|
type ConsumableDefinitionFilter =
|
||||||
| {
|
| {
|
||||||
type: 'tier';
|
type: 'tier';
|
||||||
@@ -12,17 +14,19 @@ type ConsumableDefinitionFilter =
|
|||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'category';
|
type: 'category';
|
||||||
categories: string[];
|
categories: ConsumableDefinitionFilterCategory[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface ConsumableEntry {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
effects?: Record<string, number>;
|
||||||
|
include: ConsumableDefinitionFilter[];
|
||||||
|
exclude?: ConsumableDefinitionFilter[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface ConsumableDefinitions {
|
export interface ConsumableDefinitions {
|
||||||
[key: string]: {
|
[key: string]: ConsumableEntry;
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
effects?: Record<string, number>;
|
|
||||||
include: ConsumableDefinitionFilter[];
|
|
||||||
exclude?: ConsumableDefinitionFilter[];
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const consumableDefinitions = fetch(
|
export const consumableDefinitions = fetch(
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ interface TankopediaTemporary {
|
|||||||
};
|
};
|
||||||
equipmentMatrix: EquipmentMatrix;
|
equipmentMatrix: EquipmentMatrix;
|
||||||
mode: TankopediaMode;
|
mode: TankopediaMode;
|
||||||
|
consumables: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useTankopediaPersistent = create<TankopediaPersistent>()(
|
export const useTankopediaPersistent = create<TankopediaPersistent>()(
|
||||||
@@ -104,6 +105,7 @@ export const useTankopediaTemporary = create<TankopediaTemporary>()(
|
|||||||
[0, 1, 0],
|
[0, 1, 0],
|
||||||
],
|
],
|
||||||
mode: 'model',
|
mode: 'model',
|
||||||
|
consumables: [],
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user