feat: consumables

This commit is contained in:
TrèsAbhi committed 2024-04-06 19:37:57 -05:00
1 parent db1e4af20c
commit 94558393d9
4 files changed
+247 -94

No files matched your search

+121 -17
View File
@@ -4,6 +4,7 @@ import {
CaretLeftIcon, CaretLeftIcon,
CaretRightIcon, CaretRightIcon,
CaretSortIcon, CaretSortIcon,
ComponentPlaceholderIcon,
GroupIcon, GroupIcon,
PlusIcon, PlusIcon,
TrashIcon, TrashIcon,
@@ -22,11 +23,14 @@ import {
import { times } from 'lodash'; import { times } from 'lodash';
import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import { use, useCallback, useEffect, useMemo, useState } from 'react'; import { use, useCallback, useEffect, useMemo, useState } from 'react';
import { ConsumablesManager } from '../../../components/ConsumablesManager';
import { EquipmentManager } from '../../../components/EquipmentManager'; import { EquipmentManager } from '../../../components/EquipmentManager';
import PageWrapper from '../../../components/PageWrapper'; import PageWrapper from '../../../components/PageWrapper';
import { ProvisionsManager } from '../../../components/ProvisionsManager'; import { ProvisionsManager } from '../../../components/ProvisionsManager';
import { asset } from '../../../core/blitzkrieg/asset'; import { asset } from '../../../core/blitzkrieg/asset';
import { availableProvisions } from '../../../core/blitzkrieg/availableProvisions'; import { availableProvisions } from '../../../core/blitzkrieg/availableProvisions';
import { checkConsumableProvisionInclusivity } from '../../../core/blitzkrieg/checkConsumableProvisionInclusivity';
import { consumableDefinitions } from '../../../core/blitzkrieg/consumableDefinitions';
import { equipmentDefinitions } from '../../../core/blitzkrieg/equipmentDefinitions'; import { equipmentDefinitions } from '../../../core/blitzkrieg/equipmentDefinitions';
import { modelDefinitions } from '../../../core/blitzkrieg/modelDefinitions'; import { modelDefinitions } from '../../../core/blitzkrieg/modelDefinitions';
import { provisionDefinitions } from '../../../core/blitzkrieg/provisionDefinitions'; import { provisionDefinitions } from '../../../core/blitzkrieg/provisionDefinitions';
@@ -52,6 +56,7 @@ export default function Page() {
const router = useRouter(); const router = useRouter();
const awaitedTankDefinitions = use(tankDefinitions); const awaitedTankDefinitions = use(tankDefinitions);
const awaitedSkillDefinitions = use(skillDefinitions); const awaitedSkillDefinitions = use(skillDefinitions);
const awaitedConsumableDefinitions = use(consumableDefinitions);
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const members = useCompareTemporary((state) => state.members); const members = useCompareTemporary((state) => state.members);
const [addTankDialogOpen, setAddTankDialogOpen] = useState(false); const [addTankDialogOpen, setAddTankDialogOpen] = useState(false);
@@ -442,7 +447,17 @@ export default function Page() {
<Table.Cell /> <Table.Cell />
{members.map( {members.map(
({ equipmentMatrix, tank, key, provisions, gun }, index) => { (
{
equipmentMatrix,
tank,
key,
provisions,
gun,
consumables,
},
index,
) => {
const equipmentPreset = const equipmentPreset =
awaitedEquipmentDefinitions.presets[tank.equipment]; awaitedEquipmentDefinitions.presets[tank.equipment];
const provisionsList = availableProvisions( const provisionsList = availableProvisions(
@@ -450,6 +465,15 @@ export default function Page() {
gun, gun,
awaitedProvisionDefinitions, awaitedProvisionDefinitions,
); );
const consumablesList = Object.values(
awaitedConsumableDefinitions,
).filter((consumable) =>
checkConsumableProvisionInclusivity(
consumable,
tank,
gun,
),
);
return ( return (
<Table.Cell key={key}> <Table.Cell key={key}>
@@ -462,6 +486,84 @@ export default function Page() {
height: '100%', height: '100%',
}} }}
> >
<Popover.Root>
<Popover.Trigger>
<Button
variant="ghost"
radius="large"
style={{
height: '100%',
width: 12,
position: 'relative',
}}
>
<Flex direction="column" justify="center">
{provisions.map((provision, index) => (
<img
key={provision}
src={asset(
`/icons/provisions/${provision}.webp`,
)}
style={{
left: '50%',
/**
* max = 100% - 16px - 4px
* min = 4px
* diff = max - min
* = 100% - 16px - 4px - 4px
* = 100% - 24px
* coefficient = index / length - 1
* position = min + coefficient * diff
*/
top: `calc(4px + ${
provisions.length === 1
? 0.5
: index / (provisions.length - 1)
} * (100% - 24px))`,
transform: 'translateX(-50%)',
position: 'absolute',
width: 16,
height: 16,
objectFit: 'contain',
}}
/>
))}
{provisions.length === 0 && <GroupIcon />}
</Flex>
</Button>
</Popover.Trigger>
<Popover.Content>
<ProvisionsManager
provisions={provisionsList.map(
(provision) => provision.id,
)}
selected={provisions}
disabled={tank.provisions === provisions.length}
onChange={(provisions) => {
mutateCompareTemporary((draft) => {
draft.members[index].provisions =
provisions;
});
}}
/>
<Flex justify="end" mt="4">
<Button
variant="ghost"
onClick={() => {
mutateCompareTemporary((draft) => {
draft.members[index].provisions = [];
});
}}
>
Clear
</Button>
</Flex>
</Popover.Content>
</Popover.Root>
<Popover.Root> <Popover.Root>
<Popover.Trigger> <Popover.Trigger>
<Button variant="ghost" radius="large"> <Button variant="ghost" radius="large">
@@ -575,11 +677,11 @@ export default function Page() {
}} }}
> >
<Flex direction="column" justify="center"> <Flex direction="column" justify="center">
{provisions.map((provision, index) => ( {consumables.map((consumable, index) => (
<img <img
key={provision} key={consumable}
src={asset( src={asset(
`/icons/provisions/${provision}.webp`, `/icons/consumables/${consumable}.webp`,
)} )}
style={{ style={{
left: '50%', left: '50%',
@@ -593,9 +695,9 @@ export default function Page() {
* position = min + coefficient * diff * position = min + coefficient * diff
*/ */
top: `calc(4px + ${ top: `calc(4px + ${
provisions.length === 1 consumables.length === 1
? 0.5 ? 0.5
: index / (provisions.length - 1) : index / (consumables.length - 1)
} * (100% - 24px))`, } * (100% - 24px))`,
transform: 'translateX(-50%)', transform: 'translateX(-50%)',
position: 'absolute', position: 'absolute',
@@ -606,24 +708,26 @@ export default function Page() {
/> />
))} ))}
{provisions.length === 0 && <GroupIcon />} {consumables.length === 0 && (
<ComponentPlaceholderIcon />
)}
</Flex> </Flex>
</Button> </Button>
</Popover.Trigger> </Popover.Trigger>
<Popover.Content> <Popover.Content>
<ProvisionsManager <ConsumablesManager
provisions={provisionsList.map( consumables={consumablesList}
(provision) => provision.id, selected={consumables}
)} onChange={(consumables) => {
selected={provisions}
disabled={tank.provisions === provisions.length}
onChange={(provisions) => {
mutateCompareTemporary((draft) => { mutateCompareTemporary((draft) => {
draft.members[index].provisions = draft.members[index].consumables =
provisions; consumables;
}); });
}} }}
disabled={
tank.consumables === consumables.length
}
/> />
<Flex justify="end" mt="4"> <Flex justify="end" mt="4">
@@ -631,7 +735,7 @@ export default function Page() {
variant="ghost" variant="ghost"
onClick={() => { onClick={() => {
mutateCompareTemporary((draft) => { mutateCompareTemporary((draft) => {
draft.members[index].provisions = []; draft.members[index].consumables = [];
}); });
}} }}
> >
@@ -8,7 +8,7 @@ import {
Text, Text,
} from '@radix-ui/themes'; } from '@radix-ui/themes';
import { use } from 'react'; import { use } from 'react';
import { ConsumableButton } from '../../../../../../../components/ModuleButtons/ConsumableButton'; import { ConsumablesManager } from '../../../../../../../components/ConsumablesManager';
import { checkConsumableProvisionInclusivity } from '../../../../../../../core/blitzkrieg/checkConsumableProvisionInclusivity'; import { checkConsumableProvisionInclusivity } from '../../../../../../../core/blitzkrieg/checkConsumableProvisionInclusivity';
import { consumableDefinitions } from '../../../../../../../core/blitzkrieg/consumableDefinitions'; import { consumableDefinitions } from '../../../../../../../core/blitzkrieg/consumableDefinitions';
import { useEquipment } from '../../../../../../../hooks/useEquipment'; import { useEquipment } from '../../../../../../../hooks/useEquipment';
@@ -68,48 +68,20 @@ export function Consumables() {
</Button> </Button>
</Flex> </Flex>
<Flex wrap="wrap"> <ConsumablesManager
{consumablesList.map((consumable, index) => { consumables={consumablesList}
const selected = consumables.includes(consumable.id); selected={consumables}
cooldownBooster={cooldownBooster}
return ( disabled={protagonist.tank.consumables === consumables.length}
<ConsumableButton hasConsumableDeliverySystem={hasConsumableDeliverySystem}
special={consumable.duration === undefined} hasHighEndConsumables={hasHighEndConsumables}
duration={ timers
consumable.duration === undefined onChange={(consumables) => {
? undefined mutateDuel((draft) => {
: consumable.duration * (hasHighEndConsumables ? 0.7 : 1) draft.protagonist!.consumables = consumables;
} });
cooldown={ }}
consumable.cooldown * />
(hasConsumableDeliverySystem ? 0.85 : 1) *
(1 - cooldownBooster * 0.1)
}
key={consumable.id}
first={index === 0}
last={index === consumablesList.length - 1}
rowChild
disabled={
protagonist.tank.consumables === consumables.length && !selected
}
consumable={consumable.id}
selected={selected}
onClick={() => {
mutateDuel((draft) => {
if (selected) {
draft.protagonist!.consumables =
draft.protagonist!.consumables.filter(
(id) => id !== consumable.id,
);
} else {
draft.protagonist!.consumables.push(consumable.id);
}
});
}}
/>
);
})}
</Flex>
</ConfigurationChildWrapper> </ConfigurationChildWrapper>
); );
} }
+73
View File
@@ -0,0 +1,73 @@
import { Flex } from '@radix-ui/themes';
import { ConsumableEntry } from '../core/blitzkrieg/consumableDefinitions';
import { ConsumableButton } from './ModuleButtons/ConsumableButton';
interface ConsumablesManagerProps {
consumables: ConsumableEntry[];
selected: number[];
onChange?: (consumables: number[]) => void;
disabled?: boolean;
timers?: boolean;
hasHighEndConsumables?: boolean;
hasConsumableDeliverySystem?: boolean;
cooldownBooster?: number;
}
export function ConsumablesManager({
consumables,
selected,
onChange,
disabled,
timers,
hasConsumableDeliverySystem,
cooldownBooster,
hasHighEndConsumables,
}: ConsumablesManagerProps) {
return (
<Flex wrap="wrap">
{consumables.map((consumable, index) => {
const isSelected = selected.some((id) => id === consumable.id);
return (
<ConsumableButton
special={consumable.duration === undefined}
duration={
timers
? consumable.duration === undefined
? undefined
: consumable.duration * (hasHighEndConsumables ? 0.7 : 1)
: undefined
}
cooldown={
timers
? consumable.cooldown *
(hasConsumableDeliverySystem ? 0.85 : 1) *
(1 - cooldownBooster! * 0.1)
: undefined
}
key={consumable.id}
first={index === 0}
last={index === consumables.length - 1}
rowChild
disabled={disabled && !isSelected}
consumable={consumable.id}
selected={isSelected}
onClick={() => {
if (!onChange) return;
const draft = [...selected];
if (draft.some((id) => id === consumable.id)) {
draft.splice(draft.indexOf(consumable.id), 1);
} else {
draft.push(consumable.id);
}
onChange(draft);
}}
/>
);
})}
</Flex>
);
}
@@ -8,7 +8,7 @@ import { TankComponentButtonProps } from './TankComponentButton';
interface ConsumableButtonProps extends TankComponentButtonProps { interface ConsumableButtonProps extends TankComponentButtonProps {
consumable: number; consumable: number;
duration?: number; duration?: number;
cooldown: number; cooldown?: number;
} }
export function ConsumableButton({ export function ConsumableButton({
@@ -17,7 +17,7 @@ export function ConsumableButton({
duration, duration,
...props ...props
}: ConsumableButtonProps) { }: ConsumableButtonProps) {
const cooldownDelta = useDelta(cooldown); const cooldownDelta = useDelta(cooldown ?? 0);
const durationDelta = useDelta(duration ?? 0); const durationDelta = useDelta(duration ?? 0);
return ( return (
@@ -25,38 +25,42 @@ export function ConsumableButton({
icon={asset(`icons/consumables/${consumable}.webp`)} icon={asset(`icons/consumables/${consumable}.webp`)}
{...props} {...props}
> >
<Flex direction="column" style={{ transform: 'translateX(-10px)' }}> {(cooldown || duration) && (
{duration && ( <Flex direction="column" style={{ transform: 'translateX(-10px)' }}>
<Text {duration && (
color={ <Text
durationDelta < 0 color={
? 'green' durationDelta < 0
: durationDelta > 0 ? 'green'
? 'tomato' : durationDelta > 0
: undefined ? 'tomato'
} : undefined
> }
<Flex align="center" gap="1" style={{ marginBottom: -4 }}> >
<ClockIcon width={12} height={12} /> <Flex align="center" gap="1" style={{ marginBottom: -4 }}>
{Math.round(duration)}s <ClockIcon width={12} height={12} />
</Flex> {Math.round(duration)}s
</Text> </Flex>
)} </Text>
<Text )}
color={ {cooldown && (
cooldownDelta < 0 <Text
? 'green' color={
: cooldownDelta > 0 cooldownDelta < 0
? 'tomato' ? 'green'
: undefined : cooldownDelta > 0
} ? 'tomato'
> : undefined
<Flex align="center" gap="1"> }
<ReloadIcon width={12} height={12} /> >
{Math.round(cooldown)}s <Flex align="center" gap="1">
</Flex> <ReloadIcon width={12} height={12} />
</Text> {Math.round(cooldown)}s
</Flex> </Flex>
</Text>
)}
</Flex>
)}
</GenericTankComponentButton> </GenericTankComponentButton>
); );
} }