feat: migrated duel store

This commit is contained in:
TrèsAbhi committed 2024-07-09 20:35:36 -05:00
1 parent eaaea114a0
commit 332c1787f4
32 files changed
+223 -191

No files matched your search

@@ -12,13 +12,14 @@ import { ConsumablesManager } from '../../../../../../../components/ConsumablesM
import { checkConsumableProvisionInclusivity } from '../../../../../../../core/blitzkit/checkConsumableProvisionInclusivity';
import { consumableDefinitions } from '../../../../../../../core/blitzkit/consumableDefinitions';
import { useEquipment } from '../../../../../../../hooks/useEquipment';
import { mutateDuel, useDuel } from '../../../../../../../stores/duel';
import * as Duel from '../../../../../../../stores/duel';
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
export function Consumables() {
const protagonist = useDuel((state) => state.protagonist!);
const mutateDuel = Duel.useMutation();
const protagonist = Duel.use((state) => state.protagonist!);
const awaitedConsumableDefinitions = use(consumableDefinitions);
const consumables = useDuel((state) => state.protagonist!.consumables);
const consumables = Duel.use((state) => state.protagonist!.consumables);
const consumablesList = Object.values(awaitedConsumableDefinitions).filter(
(consumable) =>
checkConsumableProvisionInclusivity(
@@ -27,7 +28,7 @@ export function Consumables() {
protagonist.gun,
),
);
const cooldownBooster = useDuel(
const cooldownBooster = Duel.use(
(state) => state.protagonist!.cooldownBooster,
);
const hasConsumableDeliverySystem = useEquipment(118);
@@ -2,15 +2,16 @@ import { Button, Flex, Heading } from '@radix-ui/themes';
import { use } from 'react';
import { EquipmentManager } from '../../../../../../../components/EquipmentManager';
import { equipmentDefinitions } from '../../../../../../../core/blitzkit/equipmentDefinitions';
import { mutateDuel, useDuel } from '../../../../../../../stores/duel';
import * as Duel from '../../../../../../../stores/duel';
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
export function Equipment() {
const protagonist = useDuel((state) => state.protagonist!);
const mutateDuel = Duel.useMutation();
const protagonist = Duel.use((state) => state.protagonist!);
const awaitedEquipmentDefinitions = use(equipmentDefinitions);
const equipmentPreset =
awaitedEquipmentDefinitions.presets[protagonist.tank.equipment];
const equipmentMatrix = useDuel(
const equipmentMatrix = Duel.use(
(state) => state.protagonist!.equipmentMatrix,
);
@@ -3,18 +3,19 @@ import { clamp, debounce } from 'lodash';
import { useEffect, useMemo, useRef, useState } from 'react';
import { GenericTankComponentButton } from '../../../../../../../components/ModuleButtons/GenericTankComponentButton';
import { asset } from '../../../../../../../core/blitzkit/asset';
import { mutateDuel, useDuel } from '../../../../../../../stores/duel';
import * as Duel from '../../../../../../../stores/duel';
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
export function Miscellaneous() {
const camouflage = useDuel((state) => state.protagonist!.camouflage);
const cooldownBooster = useDuel(
const mutateDuel = Duel.useMutation();
const camouflage = Duel.use((state) => state.protagonist!.camouflage);
const cooldownBooster = Duel.use(
(state) => state.protagonist!.cooldownBooster,
);
const crewMastery = useDuel((state) => state.protagonist!.crewMastery);
const crewMastery = Duel.use((state) => state.protagonist!.crewMastery);
const crewInput = useRef<HTMLInputElement>(null);
const [crewMasteryDraft, setCrewMasteryDraft] = useState(crewMastery);
const { tank } = useDuel((state) => state.protagonist!);
const { tank } = Duel.use((state) => state.protagonist!);
const debouncedApplyDraft = useMemo(
() =>
@@ -19,7 +19,7 @@ import {
} from '../../../../../../../core/blitzkit/tankDefinitions';
import { TIER_ROMAN_NUMERALS } from '../../../../../../../core/blitzkit/tankDefinitions/constants';
import { tankIcon } from '../../../../../../../core/blitzkit/tankIcon';
import { mutateDuel, useDuel } from '../../../../../../../stores/duel';
import * as Duel from '../../../../../../../stores/duel';
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
function ModuleButton({
@@ -116,17 +116,18 @@ function ModuleButton({
}
export function Modules() {
const mutateDuel = Duel.useMutation();
const awaitedTankDefinitions = use(tankDefinitions);
const tank = useDuel((state) => state.protagonist!.tank);
const tank = Duel.use((state) => state.protagonist!.tank);
const hasUpgrades =
tank.turrets.length > 1 ||
tank.turrets[0].guns.length > 1 ||
tank.engines.length > 1 ||
tank.tracks.length > 1;
const turret = useDuel((state) => state.protagonist!.turret);
const gun = useDuel((state) => state.protagonist!.gun);
const engine = useDuel((state) => state.protagonist!.engine);
const track = useDuel((state) => state.protagonist!.track);
const turret = Duel.use((state) => state.protagonist!.turret);
const gun = Duel.use((state) => state.protagonist!.gun);
const engine = Duel.use((state) => state.protagonist!.engine);
const track = Duel.use((state) => state.protagonist!.track);
const turret0 = tank.turrets[0];
const gun0 = turret0.guns[0];
const engine0 = tank.engines[0];
@@ -3,12 +3,13 @@ import { use } from 'react';
import { ProvisionsManager } from '../../../../../../../components/ProvisionsManager';
import { availableProvisions } from '../../../../../../../core/blitzkit/availableProvisions';
import { provisionDefinitions } from '../../../../../../../core/blitzkit/provisionDefinitions';
import { mutateDuel, useDuel } from '../../../../../../../stores/duel';
import * as Duel from '../../../../../../../stores/duel';
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
export function Provisions() {
const { tank, gun } = useDuel((state) => state.protagonist!);
const provisions = useDuel((state) => state.protagonist!.provisions);
const mutateDuel = Duel.useMutation();
const { tank, gun } = Duel.use((state) => state.protagonist!);
const provisions = Duel.use((state) => state.protagonist!.provisions);
const awaitedProvisionDefinitions = use(provisionDefinitions);
const provisionsList = availableProvisions(
tank,
@@ -28,12 +28,13 @@ import {
} from '../../../../../../core/blitzkit/tankDefinitions/constants';
import { useAdExempt } from '../../../../../../hooks/useAdExempt';
import { useEquipment } from '../../../../../../hooks/useEquipment';
import { mutateDuel, useDuel } from '../../../../../../stores/duel';
import * as Duel from '../../../../../../stores/duel';
import * as TankopediaEphemeral from '../../../../../../stores/tankopediaEphemeral';
import { Info } from './components/Info';
import { InfoWithDelta } from './components/InfoWithDelta';
export function Characteristics() {
const mutateDuel = Duel.useMutation();
const exempt = useAdExempt();
const awaitedEquipmentDefinitions = use(equipmentDefinitions);
const awaitedModelDefinitions = use(modelDefinitions);
@@ -41,10 +42,10 @@ export function Characteristics() {
const crewSkills = TankopediaEphemeral.use((state) => state.skills);
const penetrationDistanceInput = useRef<HTMLInputElement>(null);
const hasImprovedVentilation = useEquipment(102);
const crewMastery = useDuel((state) => state.protagonist!.crewMastery);
const crewMastery = Duel.use((state) => state.protagonist!.crewMastery);
const [penetrationDistance, setPenetrationDistance] = useState(250);
const provisions = useDuel((state) => state.protagonist!.provisions);
const provisions = Duel.use((state) => state.protagonist!.provisions);
const provisionCrewBonus =
provisions.reduce(
(total, provision) =>
@@ -55,12 +56,12 @@ export function Characteristics() {
) + (hasImprovedVentilation ? 0.08 : 0);
const commanderMastery = crewMastery + provisionCrewBonus;
const crewMemberMastery = commanderMastery * 1.1;
const consumables = useDuel((state) => state.protagonist!.consumables);
const camouflage = useDuel((state) => state.protagonist!.camouflage);
const equipmentMatrix = useDuel(
const consumables = Duel.use((state) => state.protagonist!.consumables);
const camouflage = Duel.use((state) => state.protagonist!.camouflage);
const equipmentMatrix = Duel.use(
(state) => state.protagonist!.equipmentMatrix,
);
const { tank, turret, gun, engine, track, shell } = useDuel(
const { tank, turret, gun, engine, track, shell } = Duel.use(
(state) => state.protagonist!,
);
const stockEngine = tank.engines[0];
@@ -8,7 +8,7 @@ import { hasEquipment } from '../../../../../core/blitzkit/hasEquipment';
import { modelDefinitions } from '../../../../../core/blitzkit/modelDefinitions';
import { Pose, poseEvent } from '../../../../../core/blitzkit/pose';
import { useAwait } from '../../../../../hooks/useAwait';
import { useDuel } from '../../../../../stores/duel';
import * as Duel from '../../../../../stores/duel';
import * as TankopediaEphemeral from '../../../../../stores/tankopediaEphemeral';
const poseDistances: Record<Pose, number> = {
@@ -18,13 +18,14 @@ const poseDistances: Record<Pose, number> = {
};
export function Controls() {
const duelStore = Duel.useStore();
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
const camera = useThree((state) => state.camera);
const canvas = useThree((state) => state.gl.domElement);
const orbitControls = useRef<OrbitControlsClass>(null);
const awaitedModelDefinitions = useAwait(modelDefinitions);
const protagonist = useDuel((state) => state.protagonist!);
const antagonist = useDuel((state) => state.antagonist!);
const protagonist = Duel.use((state) => state.protagonist!);
const antagonist = Duel.use((state) => state.antagonist!);
const protagonistModelDefinition =
awaitedModelDefinitions[protagonist.tank.id];
const protagonistTrackModelDefinition =
@@ -71,7 +72,7 @@ export function Controls() {
);
async function handlePoseEvent(event: Pose) {
const duel = useDuel.getState();
const duel = duelStore.getState();
const hasImprovedVerticalStabilizer = await hasEquipment(
122,
duel.protagonist!.tank.equipment,
@@ -1,9 +1,9 @@
import { Heading, Text } from '@radix-ui/themes';
import PageWrapper from '../../../../../components/PageWrapper';
import { useDuel } from '../../../../../stores/duel';
import * as Duel from '../../../../../stores/duel';
export function HistorySection() {
const tank = useDuel((state) => state.protagonist!.tank);
const tank = Duel.use((state) => state.protagonist!.tank);
if (!tank.description) return null;
@@ -1,6 +1,6 @@
import { Environment } from '@react-three/drei';
import { useModel } from '../../../../../hooks/useModel';
import { useDuel } from '../../../../../stores/duel';
import * as Duel from '../../../../../stores/duel';
import * as TankopediaPersistent from '../../../../../stores/tankopediaPersistent';
export const ENVIRONMENTS = [
@@ -12,7 +12,7 @@ export const ENVIRONMENTS = [
] as const;
export function Lighting() {
const protagonist = useDuel((state) => state.protagonist!);
const protagonist = Duel.use((state) => state.protagonist!);
const { hasPbr } = useModel(protagonist.tank.id);
const showEnvironment = TankopediaPersistent.use(
(state) => state.model.visual.showEnvironment,
@@ -7,13 +7,13 @@ import { useFullScreen } from '../../../../../../hooks/useFullScreen';
import { useWideFormat } from '../../../../../../hooks/useWideFormat';
import strings from '../../../../../../lang/en-US.json';
import { theme } from '../../../../../../stitches.config';
import { useDuel } from '../../../../../../stores/duel';
import * as Duel from '../../../../../../stores/duel';
import { TankSandbox } from './TankSandbox';
import { Options } from './components/Options';
export function HeroSection() {
const protagonist = useDuel((state) => state.protagonist!.tank);
const antagonist = useDuel((state) => state.antagonist!.tank);
const protagonist = Duel.use((state) => state.protagonist!.tank);
const antagonist = Duel.use((state) => state.antagonist!.tank);
const wideFormat = useWideFormat(880);
const compareTanks =
protagonist.id === antagonist.id
@@ -7,7 +7,7 @@ import { modelDefinitions } from '../../../../../../core/blitzkit/modelDefinitio
import { modelTransformEvent } from '../../../../../../core/blitzkit/modelTransform';
import { Pose, poseEvent } from '../../../../../../core/blitzkit/pose';
import { useEquipment } from '../../../../../../hooks/useEquipment';
import { mutateDuel, useDuel } from '../../../../../../stores/duel';
import * as Duel from '../../../../../../stores/duel';
import * as TankopediaEphemeral from '../../../../../../stores/tankopediaEphemeral';
import { Controls } from '../Control';
import { Lighting } from '../Lighting';
@@ -20,7 +20,8 @@ export function TankSandbox() {
const canvas = useRef<HTMLCanvasElement>(null);
const hasImprovedVerticalStabilizer = useEquipment(122);
const awaitedModelDefinitions = use(modelDefinitions);
const protagonist = useDuel((state) => state.protagonist!);
const protagonist = Duel.use((state) => state.protagonist!);
const mutateDuel = Duel.useMutation();
const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id];
const turretModelDefinition =
tankModelDefinition.turrets[protagonist.turret.id];
@@ -7,7 +7,7 @@ import { asset } from '../../../../../../core/blitzkit/asset';
import { tankDefinitions } from '../../../../../../core/blitzkit/tankDefinitions';
import { TIER_ROMAN_NUMERALS } from '../../../../../../core/blitzkit/tankDefinitions/constants';
import { useWideFormat } from '../../../../../../hooks/useWideFormat';
import { useDuel } from '../../../../../../stores/duel';
import * as Duel from '../../../../../../stores/duel';
export function TreeArrow({
style,
@@ -53,7 +53,7 @@ export function TreeArrow({
export function TechTreeSection() {
const wideFormat = useWideFormat(720);
const awaitedTankDefinitions = use(tankDefinitions);
const tank = useDuel((state) => state.protagonist!.tank);
const tank = Duel.use((state) => state.protagonist!.tank);
if (tank.treeType !== 'researchable') return null;
@@ -32,7 +32,7 @@ import {
import { useEquipment } from '../../../../../../../hooks/useEquipment';
import { useFullScreen } from '../../../../../../../hooks/useFullScreen';
import * as App from '../../../../../../../stores/app';
import { mutateDuel, useDuel } from '../../../../../../../stores/duel';
import * as Duel from '../../../../../../../stores/duel';
import * as TankopediaEphemeral from '../../../../../../../stores/tankopediaEphemeral';
import * as TankopediaPersistent from '../../../../../../../stores/tankopediaPersistent';
import { TankSearch } from '../../../../components/TankSearch';
@@ -66,13 +66,14 @@ export function Options() {
(state) => state.model.visual.environment,
);
const developerMode = App.use((state) => state.developerMode);
const antagonistGun = useDuel((state) => state.antagonist!.gun);
const antagonistShell = useDuel((state) => state.antagonist!.shell);
const antagonistGun = Duel.use((state) => state.antagonist!.gun);
const antagonistShell = Duel.use((state) => state.antagonist!.shell);
const [antagonistSelectorOpen, setAntagonistSelectorOpen] = useState(false);
const antagonistTank = useDuel((state) => state.antagonist!.tank);
const antagonistTurret = useDuel((state) => state.antagonist!.turret);
const antagonistTank = Duel.use((state) => state.antagonist!.tank);
const antagonistTurret = Duel.use((state) => state.antagonist!.turret);
const hasCalibratedShells = useEquipment(103, true);
const [tab, setTab] = useState('search');
const mutateDuel = Duel.useMutation();
const hasEnhancedArmor = useEquipment(110);
return (
@@ -21,12 +21,14 @@ import {
import { normalizeAngleRad } from '../../../../../../../core/math/normalizeAngleRad';
import { useAwait } from '../../../../../../../hooks/useAwait';
import { useModel } from '../../../../../../../hooks/useModel';
import { mutateDuel, useDuel } from '../../../../../../../stores/duel';
import * as Duel from '../../../../../../../stores/duel';
import * as TankopediaEphemeral from '../../../../../../../stores/tankopediaEphemeral';
export const TankModel = memo(() => {
const mutateDuel = Duel.useMutation();
const duelStore = Duel.useStore();
const awaitedModelDefinitions = useAwait(modelDefinitions);
const protagonist = useDuel((draft) => draft.protagonist!);
const protagonist = Duel.use((draft) => draft.protagonist!);
const canvas = useThree((state) => state.gl.domElement);
const hullContainer = useRef<Group>(null);
const turretContainer = useRef<Group>(null);
@@ -216,7 +218,7 @@ export const TankModel = memo(() => {
}
}
async function handlePointerMove(event: PointerEvent) {
const duel = useDuel.getState();
const duel = duelStore.getState();
const hasImprovedVerticalStabilizer = await hasEquipment(
122,
duel.protagonist!.tank.equipment,
@@ -296,7 +298,7 @@ export const TankModel = memo(() => {
window.addEventListener('pointerup', handlePointerUp);
}
async function handlePointerMove(event: PointerEvent) {
const duel = useDuel.getState();
const duel = duelStore.getState();
const hasImprovedVerticalStabilizer = await hasEquipment(
122,
duel.protagonist!.tank.equipment,
@@ -10,11 +10,12 @@ import {
} from '../../../../../../core/blitzkit/modelTransform';
import { useEquipment } from '../../../../../../hooks/useEquipment';
import { useFullScreen } from '../../../../../../hooks/useFullScreen';
import { mutateDuel, useDuel } from '../../../../../../stores/duel';
import * as Duel from '../../../../../../stores/duel';
import * as styles from './index.css';
export function RotationInputs() {
const protagonist = useDuel((state) => state.protagonist!);
const mutateDuel = Duel.useMutation();
const protagonist = Duel.use((state) => state.protagonist!);
const awaitedModelDefinitions = use(modelDefinitions);
const yawInput = useRef<HTMLInputElement>(null);
const pitchInput = useRef<HTMLInputElement>(null);
@@ -1,21 +1,24 @@
import { CaretRightIcon, UpdateIcon } from '@radix-ui/react-icons';
import { Button, Dialog, Flex, Heading } from '@radix-ui/themes';
import Link from 'next/link';
import { useState } from 'react';
import { use, useState } from 'react';
import { TREE_TYPE_ICONS } from '../../../../../components/Tanks/components/Item/constants';
import { assignDuelMember } from '../../../../../core/blitzkit/assignDuelMember';
import { provisionDefinitions } from '../../../../../core/blitzkit/provisionDefinitions';
import { pushTankopediaPath } from '../../../../../core/blitzkit/pushTankopediaPath';
import { useDuel } from '../../../../../stores/duel';
import { tankToDuelMember } from '../../../../../core/blitzkit/tankToDuelMember';
import * as Duel from '../../../../../stores/duel';
import { TankSearch } from '../../components/TankSearch';
export function Title() {
const protagonist = useDuel((state) => state.protagonist!);
const antagonist = useDuel((state) => state.antagonist!);
const awaitedProvisionDefinitions = use(provisionDefinitions);
const protagonist = Duel.use((state) => state.protagonist!);
const antagonist = Duel.use((state) => state.antagonist!);
const compareTanks =
protagonist.tank.id === antagonist.tank.id
? [protagonist.tank.id]
: [protagonist.tank.id, antagonist.tank.id];
const [changeTankDialogOpen, setChangeTankDialogOpen] = useState(false);
const mutateDuel = Duel.useMutation();
return (
<>
@@ -76,9 +79,15 @@ export function Title() {
<TankSearch
compact
onSelect={(tank) => {
assignDuelMember('protagonist', tank.id);
setChangeTankDialogOpen(false);
pushTankopediaPath(tank.id);
mutateDuel((draft) => {
draft.protagonist = tankToDuelMember(
tank,
awaitedProvisionDefinitions,
);
});
}}
/>
</Flex>
@@ -2,10 +2,10 @@ import { Flex, Heading, Link, Text } from '@radix-ui/themes';
import { use } from 'react';
import PageWrapper from '../../../../../components/PageWrapper';
import { videoDefinitions } from '../../../../../core/blitzkit/videos';
import { useDuel } from '../../../../../stores/duel';
import * as Duel from '../../../../../stores/duel';
export function VideoSection() {
const tank = useDuel((state) => state.protagonist!.tank);
const tank = Duel.use((state) => state.protagonist!.tank);
const awaitedVideoDefinitions = use(videoDefinitions);
const videos = awaitedVideoDefinitions[tank.id]?.videos ?? [];
+15 -21
View File
@@ -3,9 +3,8 @@
import { useEffect } from 'react';
import { AdMidSectionResponsive } from '../../../../components/AdMidSectionResponsive';
import PageWrapper from '../../../../components/PageWrapper';
import { assignDuelMember } from '../../../../core/blitzkit/assignDuelMember';
import { useAdExempt } from '../../../../hooks/useAdExempt';
import { mutateDuel, useDuel } from '../../../../stores/duel';
import * as Duel from '../../../../stores/duel';
import * as TankopediaEphemeral from '../../../../stores/tankopediaEphemeral';
import { HistorySection } from './components/HistorySection';
import { CharacteristicsSection } from './components/Model/CharacteristicsSection';
@@ -14,15 +13,14 @@ import { TechTreeSection } from './components/Model/TechTreeSection';
import { ShotDisplaySection } from './components/ShotDisplaySection';
import { VideoSection } from './components/VideoSection';
// TODO: remove this param if not needed for duel assignment
export default function Page({ params }: { params: { id: string } }) {
const initialId = parseInt(params.id);
const assigned = useDuel((state) => state.assigned);
const exempt = useAdExempt();
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const mutateDuel = Duel.useMutation();
const duelStore = Duel.useStore();
useEffect(() => {
assignDuelMember('both', initialId);
function handleKeyDown(event: KeyboardEvent) {
if (document.activeElement instanceof HTMLInputElement) return;
@@ -59,10 +57,10 @@ export default function Page({ params }: { params: { id: string } }) {
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [initialId, assigned]);
}, []);
useEffect(() => {
const unsubscribe = useDuel.subscribe(
const unsubscribe = duelStore.subscribe(
(state) => state.protagonist?.tank.id,
() => {
mutateTankopediaEphemeral((draft) => {
@@ -76,19 +74,15 @@ export default function Page({ params }: { params: { id: string } }) {
return (
<PageWrapper p="0" noMaxWidth color="purple" size={1600}>
{assigned && (
<>
<HeroSection />
<ShotDisplaySection />
{!exempt && <AdMidSectionResponsive />}
<CharacteristicsSection />
{!exempt && <AdMidSectionResponsive />}
<TechTreeSection />
<VideoSection />
{!exempt && <AdMidSectionResponsive />}
<HistorySection />
</>
)}
<HeroSection />
<ShotDisplaySection />
{!exempt && <AdMidSectionResponsive />}
<CharacteristicsSection />
{!exempt && <AdMidSectionResponsive />}
<TechTreeSection />
<VideoSection />
{!exempt && <AdMidSectionResponsive />}
<HistorySection />
</PageWrapper>
);
}
+8 -5
View File
@@ -1,13 +1,16 @@
import * as Duel from '../../../stores/duel';
import * as TankopediaEphemeral from '../../../stores/tankopediaEphemeral';
import * as TankopediaPersistent from '../../../stores/tankopediaPersistent';
import * as TankopediaSort from '../../../stores/tankopediaSort';
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<TankopediaPersistent.Provider>
<TankopediaEphemeral.Provider>
<TankopediaSort.Provider>{children}</TankopediaSort.Provider>
</TankopediaEphemeral.Provider>
</TankopediaPersistent.Provider>
<Duel.Provider>
<TankopediaPersistent.Provider>
<TankopediaEphemeral.Provider>
<TankopediaSort.Provider>{children}</TankopediaSort.Provider>
</TankopediaEphemeral.Provider>
</TankopediaPersistent.Provider>
</Duel.Provider>
);
}
@@ -10,7 +10,7 @@ import { nameToArmorId } from '../../../core/blitzkit/nameToArmorId';
import { resolveArmor } from '../../../core/blitzkit/resolveThickness';
import { useArmor } from '../../../hooks/useArmor';
import { useModelDefinitions } from '../../../hooks/useModelDefinitions';
import { useDuel } from '../../../stores/duel';
import * as Duel from '../../../stores/duel';
import * as TankopediaPersistent from '../../../stores/tankopediaPersistent';
import { CoreArmorSceneComponent } from './CoreArmorSceneComponent';
import { ShotDisplay } from './ShotDisplay';
@@ -22,7 +22,7 @@ export const CoreArmorScene = memo(() => {
const gunContainer = useRef<Group>(null);
const tankopediaPersistentStore = TankopediaPersistent.useStore();
const initialTankopediaState = tankopediaPersistentStore.getState();
const protagonist = useDuel((draft) => draft.protagonist!);
const protagonist = Duel.use((draft) => draft.protagonist);
useEffect(() => {
if (!modelDefinitions) return;
@@ -110,10 +110,10 @@ export const CoreArmorScene = memo(() => {
return unsubscribe;
});
const tank = useDuel((state) => state.protagonist!.tank);
const track = useDuel((state) => state.protagonist!.track);
const turret = useDuel((state) => state.protagonist!.turret);
const gun = useDuel((state) => state.protagonist!.gun);
const tank = Duel.use((state) => state.protagonist!.tank);
const track = Duel.use((state) => state.protagonist!.track);
const turret = Duel.use((state) => state.protagonist!.turret);
const gun = Duel.use((state) => state.protagonist!.gun);
const armorGltf = useArmor(tank.id);
const armorNodes = Object.values(armorGltf.nodes);
const tankModelDefinition = modelDefinitions[tank.id];
@@ -9,7 +9,7 @@ import { resolvePenetrationCoefficient } from '../../../../core/blitz/resolvePen
import { hasEquipment } from '../../../../core/blitzkit/hasEquipment';
import { jsxTree } from '../../../../core/blitzkit/jsxTree';
import { ShellDefinition } from '../../../../core/blitzkit/tankDefinitions';
import { EquipmentMatrix, useDuel } from '../../../../stores/duel';
import * as Duel from '../../../../stores/duel';
import * as TankopediaPersistent from '../../../../stores/tankopediaPersistent';
import fragmentShader from './shaders/fragment.glsl';
import vertexShader from './shaders/vertex.glsl';
@@ -29,6 +29,7 @@ export function CoreArmorSceneComponent({
thickness,
}: CoreArmorSceneComponentProps) {
const tankopediaPersistentStore = TankopediaPersistent.useStore();
const duelStore = Duel.useStore();
const material = new ShaderMaterial({
fragmentShader,
vertexShader,
@@ -70,7 +71,7 @@ export function CoreArmorSceneComponent({
material.uniforms.damage.value = shell.damage.armor;
material.uniforms.explosionRadius.value = shell.explosionRadius;
const duel = useDuel.getState();
const duel = duelStore.getState();
await handleProtagonistEquipmentChange(duel.protagonist!.equipmentMatrix);
await handleAntagonistEquipmentChange(duel.antagonist!.equipmentMatrix);
}
@@ -83,9 +84,9 @@ export function CoreArmorSceneComponent({
material.wireframe = visual.wireframe;
}
async function handleProtagonistEquipmentChange(
equipment: EquipmentMatrix,
equipment: Duel.EquipmentMatrix,
) {
const duel = useDuel.getState();
const duel = duelStore.getState();
const hasEnhancedArmor = await hasEquipment(
110,
duel.protagonist!.tank.equipment,
@@ -95,8 +96,10 @@ export function CoreArmorSceneComponent({
? thickness * 1.03
: thickness;
}
async function handleAntagonistEquipmentChange(equipment: EquipmentMatrix) {
const duel = useDuel.getState();
async function handleAntagonistEquipmentChange(
equipment: Duel.EquipmentMatrix,
) {
const duel = duelStore.getState();
const shell = duel.antagonist!.shell;
const penetration = resolveNearPenetration(shell.penetration);
const hasCalibratedShells = await hasEquipment(
@@ -110,26 +113,29 @@ export function CoreArmorSceneComponent({
resolvePenetrationCoefficient(hasCalibratedShells, shell.type);
}
handleShellChange(useDuel.getState().antagonist!.shell);
handleShellChange(duelStore.getState().antagonist!.shell);
handleVisualChange(tankopediaPersistentStore.getState().model.visual);
handleProtagonistEquipmentChange(
useDuel.getState().protagonist!.equipmentMatrix,
duelStore.getState().protagonist!.equipmentMatrix,
);
handleAntagonistEquipmentChange(
useDuel.getState().antagonist!.equipmentMatrix,
duelStore.getState().antagonist!.equipmentMatrix,
);
const unsubscribes = [
useDuel.subscribe((state) => state.antagonist!.shell, handleShellChange),
duelStore.subscribe(
(state) => state.antagonist!.shell,
handleShellChange,
),
tankopediaPersistentStore.subscribe(
(state) => state.model.visual,
handleVisualChange,
),
useDuel.subscribe(
duelStore.subscribe(
(state) => state.protagonist!.equipmentMatrix,
handleProtagonistEquipmentChange,
),
useDuel.subscribe(
duelStore.subscribe(
(state) => state.antagonist!.equipmentMatrix,
handleAntagonistEquipmentChange,
),
@@ -11,7 +11,7 @@ import { resolveArmor } from '../../../core/blitzkit/resolveThickness';
import { useArmor } from '../../../hooks/useArmor';
import { useModel } from '../../../hooks/useModel';
import { useModelDefinitions } from '../../../hooks/useModelDefinitions';
import { useDuel } from '../../../stores/duel';
import * as Duel from '../../../stores/duel';
import * as TankopediaPersistent from '../../../stores/tankopediaPersistent';
import { SpacedArmorSceneComponent } from './SpacedArmorSceneComponent';
@@ -32,7 +32,7 @@ export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
const gunContainer = useRef<Group>(null);
const tankopediaPersistentStore = TankopediaPersistent.useStore();
const initialTankopediaState = tankopediaPersistentStore.getState();
const protagonist = useDuel((draft) => draft.protagonist!);
const protagonist = Duel.use((draft) => draft.protagonist);
useEffect(() => {
if (!modelDefinitions) return;
@@ -120,10 +120,10 @@ export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
return unsubscribe;
});
const tank = useDuel((state) => state.protagonist!.tank);
const track = useDuel((state) => state.protagonist!.track);
const turret = useDuel((state) => state.protagonist!.turret);
const gun = useDuel((state) => state.protagonist!.gun);
const tank = Duel.use((state) => state.protagonist!.tank);
const track = Duel.use((state) => state.protagonist!.track);
const turret = Duel.use((state) => state.protagonist!.turret);
const gun = Duel.use((state) => state.protagonist!.gun);
const armorGltf = useArmor(tank.id);
const { gltf: modelGltf } = useModel(tank.id);
const armorNodes = Object.values(armorGltf.nodes);
@@ -12,7 +12,7 @@ import { resolvePenetrationCoefficient } from '../../../../../../core/blitz/reso
import { hasEquipment } from '../../../../../../core/blitzkit/hasEquipment';
import { jsxTree } from '../../../../../../core/blitzkit/jsxTree';
import { ShellDefinition } from '../../../../../../core/blitzkit/tankDefinitions';
import { EquipmentMatrix, useDuel } from '../../../../../../stores/duel';
import * as Duel from '../../../../../../stores/duel';
import { ArmorType } from '../../../SpacedArmorScene';
import fragmentShader from './shaders/fragment.glsl';
import vertexShader from './shaders/vertex.glsl';
@@ -30,6 +30,7 @@ export function SpacedArmorSubExternal({
variant,
clip,
}: SpacedArmorSubExternalProps) {
const duelStore = Duel.useStore();
const material = new ShaderMaterial({
fragmentShader,
vertexShader,
@@ -53,9 +54,9 @@ export function SpacedArmorSubExternal({
);
}
async function handleProtagonistEquipmentChange(
equipment: EquipmentMatrix,
equipment: Duel.EquipmentMatrix,
) {
const duel = useDuel.getState();
const duel = duelStore.getState();
const hasEnhancedArmor = await hasEquipment(
110,
duel.protagonist!.tank.equipment,
@@ -65,8 +66,10 @@ export function SpacedArmorSubExternal({
? thickness * 1.03
: thickness;
}
async function handleAntagonistEquipmentChange(equipment: EquipmentMatrix) {
const duel = useDuel.getState();
async function handleAntagonistEquipmentChange(
equipment: Duel.EquipmentMatrix,
) {
const duel = duelStore.getState();
const shell = duel.antagonist!.shell;
const penetration = resolveNearPenetration(shell.penetration);
const hasCalibratedShells = await hasEquipment(
@@ -80,21 +83,24 @@ export function SpacedArmorSubExternal({
resolvePenetrationCoefficient(hasCalibratedShells, shell.type);
}
handleShellChange(useDuel.getState().antagonist!.shell);
handleShellChange(duelStore.getState().antagonist!.shell);
handleProtagonistEquipmentChange(
useDuel.getState().protagonist!.equipmentMatrix,
duelStore.getState().protagonist!.equipmentMatrix,
);
handleAntagonistEquipmentChange(
useDuel.getState().antagonist!.equipmentMatrix,
duelStore.getState().antagonist!.equipmentMatrix,
);
const unsubscribes = [
useDuel.subscribe((state) => state.antagonist!.shell, handleShellChange),
useDuel.subscribe(
duelStore.subscribe(
(state) => state.antagonist!.shell,
handleShellChange,
),
duelStore.subscribe(
(state) => state.protagonist!.equipmentMatrix,
handleProtagonistEquipmentChange,
),
useDuel.subscribe(
duelStore.subscribe(
(state) => state.antagonist!.equipmentMatrix,
handleAntagonistEquipmentChange,
),
@@ -13,7 +13,7 @@ import { resolvePenetrationCoefficient } from '../../../../../../core/blitz/reso
import { hasEquipment } from '../../../../../../core/blitzkit/hasEquipment';
import { jsxTree } from '../../../../../../core/blitzkit/jsxTree';
import { ShellDefinition } from '../../../../../../core/blitzkit/tankDefinitions';
import { EquipmentMatrix, useDuel } from '../../../../../../stores/duel';
import * as Duel from '../../../../../../stores/duel';
import { ArmorType } from '../../../SpacedArmorScene';
import fragmentShader from './shaders/fragment.glsl';
import vertexShader from './shaders/vertex.glsl';
@@ -32,6 +32,7 @@ export function SpacedArmorSubSpaced({
node,
thickness,
}: SpacedArmorSubSpacedProps) {
const duelStore = Duel.useStore();
const material = new ShaderMaterial({
fragmentShader,
vertexShader,
@@ -63,9 +64,9 @@ export function SpacedArmorSubSpaced({
);
}
async function handleProtagonistEquipmentChange(
equipment: EquipmentMatrix,
equipment: Duel.EquipmentMatrix,
) {
const duel = useDuel.getState();
const duel = duelStore.getState();
const hasEnhancedArmor = await hasEquipment(
110,
duel.protagonist!.tank.equipment,
@@ -75,9 +76,11 @@ export function SpacedArmorSubSpaced({
? thickness * 1.03
: thickness;
}
async function handleAntagonistEquipmentChange(equipment: EquipmentMatrix) {
const duel = useDuel.getState();
const shell = useDuel.getState().antagonist!.shell;
async function handleAntagonistEquipmentChange(
equipment: Duel.EquipmentMatrix,
) {
const duel = duelStore.getState();
const shell = duelStore.getState().antagonist!.shell;
const penetration = resolveNearPenetration(shell.penetration);
const hasCalibratedShells = await hasEquipment(
103,
@@ -90,21 +93,24 @@ export function SpacedArmorSubSpaced({
resolvePenetrationCoefficient(hasCalibratedShells, shell.type);
}
handleShellChange(useDuel.getState().antagonist!.shell);
handleShellChange(duelStore.getState().antagonist!.shell);
handleProtagonistEquipmentChange(
useDuel.getState().protagonist!.equipmentMatrix,
duelStore.getState().protagonist!.equipmentMatrix,
);
handleAntagonistEquipmentChange(
useDuel.getState().antagonist!.equipmentMatrix,
duelStore.getState().antagonist!.equipmentMatrix,
);
const unsubscribes = [
useDuel.subscribe((state) => state.antagonist!.shell, handleShellChange),
useDuel.subscribe(
duelStore.subscribe(
(state) => state.antagonist!.shell,
handleShellChange,
),
duelStore.subscribe(
(state) => state.protagonist!.equipmentMatrix,
handleProtagonistEquipmentChange,
),
useDuel.subscribe(
duelStore.subscribe(
(state) => state.antagonist!.equipmentMatrix,
handleAntagonistEquipmentChange,
),
@@ -16,7 +16,7 @@ import { resolvePenetrationCoefficient } from '../../../../core/blitz/resolvePen
import { hasEquipment } from '../../../../core/blitzkit/hasEquipment';
import { jsxTree } from '../../../../core/blitzkit/jsxTree';
import { ShellType } from '../../../../core/blitzkit/tankDefinitions';
import { useDuel } from '../../../../stores/duel';
import * as Duel from '../../../../stores/duel';
import * as TankopediaEphemeral from '../../../../stores/tankopediaEphemeral';
import { ArmorType } from '../SpacedArmorScene';
import { SpacedArmorSubExternal } from './components/SpacedArmorSubExternal';
@@ -74,6 +74,7 @@ export function SpacedArmorSceneComponent({
}: SpacedArmorSceneComponentProps) {
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
const camera = useThree((state) => state.camera);
const duelStore = Duel.useStore();
async function shoot(
point: Vector3,
@@ -81,7 +82,7 @@ export function SpacedArmorSceneComponent({
allowRicochet: boolean,
remainingPenetrationInput?: number,
) {
const { antagonist, protagonist } = useDuel.getState();
const { antagonist, protagonist } = duelStore.getState();
const shell = antagonist!.shell;
const cameraNormal = camera.position.clone().sub(point).normalize();
const shot: TankopediaEphemeral.Shot = {
-26
View File
@@ -1,26 +0,0 @@
import { mutateDuel } from '../../stores/duel';
import { provisionDefinitions } from './provisionDefinitions';
import { tankDefinitions } from './tankDefinitions';
import { tankToDuelMember } from './tankToDuelMember';
export async function assignDuelMember(
type: 'antagonist' | 'protagonist' | 'both',
id: number,
) {
const awaitedTankDefinitions = await tankDefinitions;
const awaitedProvisionDefinitions = await provisionDefinitions;
const tank = awaitedTankDefinitions[id];
const member = tankToDuelMember(tank, awaitedProvisionDefinitions);
mutateDuel((draft) => {
draft.assigned = true;
if (type === 'antagonist') {
draft.antagonist = member;
} else if (type === 'protagonist') {
draft.protagonist = member;
} else {
draft.protagonist = member;
draft.antagonist = { ...member };
}
});
}
+19 -3
View File
@@ -1,5 +1,13 @@
import { Draft, produce } from 'immer';
import { createContext, ReactNode, useContext, useRef } from 'react';
import { usePathname } from 'next/navigation';
import {
createContext,
ReactNode,
useContext,
useMemo,
use as useReact,
useRef,
} from 'react';
import { StoreApi, useStore as useStoreZustand } from 'zustand';
interface StoreProviderProps {
@@ -13,16 +21,24 @@ type ExtractState<S> = S extends {
? T
: never;
interface InitialContext {
pathName: string;
}
export function createNextSafeStore<Store extends StoreApi<unknown>>(
initial: () => Store,
initial: (context: InitialContext) => Store | Promise<Store>,
) {
type Type = Store extends StoreApi<infer T> ? T : never;
const Context = createContext<Store | undefined>(undefined);
function Provider({ children }: StoreProviderProps) {
const pathName = usePathname();
const storeRef = useRef<Store>();
if (!storeRef.current) storeRef.current = initial() as Store;
const context = { pathName };
const storePromise = useMemo(async () => await initial(context), []);
const awaitedStore = useReact(storePromise);
if (!storeRef.current) storeRef.current = awaitedStore;
return (
<Context.Provider value={storeRef.current}>{children}</Context.Provider>
+2 -2
View File
@@ -4,7 +4,7 @@ import {
TankDefinition,
tankDefinitions,
} from '../core/blitzkit/tankDefinitions';
import { useDuel } from '../stores/duel';
import * as Duel from '../stores/duel';
export enum UseEccentricityMode {
Tier,
@@ -13,7 +13,7 @@ export enum UseEccentricityMode {
export function useEccentricity(mode: UseEccentricityMode) {
const awaitedTankDefinitions = use(tankDefinitions);
const tank = useDuel((state) => state.protagonist!.tank);
const tank = Duel.use((state) => state.protagonist!.tank);
const similarTanks = useMemo(
() =>
Object.values(awaitedTankDefinitions).filter(
+3 -3
View File
@@ -1,15 +1,15 @@
import { use, useMemo } from 'react';
import { equipmentDefinitions } from '../core/blitzkit/equipmentDefinitions';
import { useDuel } from '../stores/duel';
import * as Duel from '../stores/duel';
export function useEquipment(id: number, antagonist = false) {
const awaitedEquipmentDefinitions = use(equipmentDefinitions);
const member = useDuel(
const member = Duel.use(
(state) => state[antagonist ? 'antagonist' : 'protagonist']!,
);
const equipmentRows =
awaitedEquipmentDefinitions.presets[member.tank.equipment];
const equipmentMatrix = useDuel(
const equipmentMatrix = Duel.use(
(state) =>
state[antagonist ? 'antagonist' : 'protagonist']!.equipmentMatrix,
);
+2 -2
View File
@@ -1,6 +1,6 @@
import { useDuel } from '../stores/duel';
import * as Duel from '../stores/duel';
export function useProvision(id: number) {
const provisions = useDuel((state) => state.protagonist!.provisions);
const provisions = Duel.use((state) => state.protagonist!.provisions);
return provisions.includes(id);
}
+23 -12
View File
@@ -1,14 +1,19 @@
import { produce } from 'immer';
'use client';
import { create } from 'zustand';
import { subscribeWithSelector } from 'zustand/middleware';
import { provisionDefinitions } from '../../core/blitzkit/provisionDefinitions';
import {
EngineDefinition,
GunDefinition,
ShellDefinition,
TankDefinition,
tankDefinitions,
TrackDefinition,
TurretDefinition,
} from '../../core/blitzkit/tankDefinitions';
import { tankToDuelMember } from '../../core/blitzkit/tankToDuelMember';
import { createNextSafeStore } from '../../core/zustand/createNextSafeStore';
type EquipmentMatrixItem = -1 | 0 | 1;
type EquipmentMatrixRow = [
@@ -40,17 +45,23 @@ export interface DuelMember {
}
export interface Duel {
assigned: boolean;
protagonist?: DuelMember;
antagonist?: DuelMember;
protagonist: DuelMember;
antagonist: DuelMember;
}
export const useDuel = create<Duel>()(
subscribeWithSelector<Duel>(() => ({
assigned: false,
})),
export const { Provider, use, useMutation, useStore } = createNextSafeStore(
async ({ pathName }) => {
const id = Number(pathName.split('/')[3]);
const awaitedTankDefinitions = await tankDefinitions;
const awaitedProvisionDefinitions = await provisionDefinitions;
const tank = awaitedTankDefinitions[id];
const protagonist = tankToDuelMember(tank, awaitedProvisionDefinitions);
return create<Duel>()(
subscribeWithSelector<Duel>(() => ({
protagonist,
antagonist: protagonist,
})),
);
},
);
export function mutateDuel(recipe: (draft: Duel) => void) {
useDuel.setState(produce(recipe));
}
-6
View File
@@ -1,6 +0,0 @@
import { mintDark } from '@radix-ui/colors';
import { createColors } from 'bepaint';
export const mintTheme = {
colors: createColors(mintDark),
};