feat: united duel data

This commit is contained in:
TrèsAbhi committed 2024-02-22 16:41:42 -06:00
1 parent da767213cb
commit 7e92f8ac50
23 files changed
+249 -249

No files matched your search

@@ -21,13 +21,13 @@ import {
import { mutateDuel, useDuel } from '../../../../../../stores/duel';
import {
mutateTankopediaTemporary,
useTankopediaTemporary,
useTankopediaPersistent,
} from '../../../../../../stores/tankopedia';
import { TankSearch } from '../../../components/TankSearch';
export function AntagonistBar() {
const antagonist = useDuel((state) => state.antagonist!);
const mode = useTankopediaTemporary((state) => state.mode);
const mode = useTankopediaPersistent((state) => state.mode);
const [tab, setTab] = useState('search');
const [antagonistSelectorOpen, setAntagonistSelectorVisible] =
useState(false);
@@ -4,17 +4,13 @@ import { ConsumableButton } from '../../../../../../../components/ModuleButtons/
import { checkConsumableProvisionInclusivity } from '../../../../../../../core/blitzkrieg/checkConsumableProvisionInclusivity';
import { consumableDefinitions } from '../../../../../../../core/blitzkrieg/consumableDefinitions';
import { useEquipment } from '../../../../../../../hooks/useEquipment';
import { useDuel } from '../../../../../../../stores/duel';
import {
mutateTankopediaTemporary,
useTankopediaTemporary,
} from '../../../../../../../stores/tankopedia';
import { mutateDuel, useDuel } from '../../../../../../../stores/duel';
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
export function Consumables() {
const protagonist = useDuel((state) => state.protagonist!);
const awaitedConsumableDefinitions = use(consumableDefinitions);
const consumables = useTankopediaTemporary((state) => state.consumables);
const consumables = useDuel((state) => state.protagonist!.consumables);
const consumablesList = Object.values(awaitedConsumableDefinitions).filter(
(consumable) =>
checkConsumableProvisionInclusivity(
@@ -23,8 +19,8 @@ export function Consumables() {
protagonist.gun,
),
);
const cooldownBooster = useTankopediaTemporary(
(state) => state.cooldownBooster,
const cooldownBooster = useDuel(
(state) => state.protagonist!.cooldownBooster,
);
const hasConsumableDeliverySystem = useEquipment(118);
const hasHighEndConsumables = useEquipment(101);
@@ -59,13 +55,14 @@ export function Consumables() {
consumable={consumable.id}
selected={selected}
onClick={() => {
mutateTankopediaTemporary((draft) => {
mutateDuel((draft) => {
if (selected) {
draft.consumables = draft.consumables.filter(
(id) => id !== consumable.id,
);
draft.protagonist!.consumables =
draft.protagonist!.consumables.filter(
(id) => id !== consumable.id,
);
} else {
draft.consumables.push(consumable.id);
draft.protagonist!.consumables.push(consumable.id);
}
});
}}
@@ -2,11 +2,7 @@ import { Flex, Heading } from '@radix-ui/themes';
import { use } from 'react';
import { EquipmentButton } from '../../../../../../../components/ModuleButtons/EquipmentButton';
import { equipmentDefinitions } from '../../../../../../../core/blitzkrieg/equipmentDefinitions';
import { useDuel } from '../../../../../../../stores/duel';
import {
mutateTankopediaTemporary,
useTankopediaTemporary,
} from '../../../../../../../stores/tankopedia';
import { mutateDuel, useDuel } from '../../../../../../../stores/duel';
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
export function Equipments() {
@@ -14,9 +10,7 @@ export function Equipments() {
const awaitedEquipmentDefinitions = use(equipmentDefinitions);
const equipmentRows =
awaitedEquipmentDefinitions.presets[protagonist.tank.equipment];
const equipmentMatrix = useTankopediaTemporary(
(state) => state.equipmentMatrix,
);
const equipmentMatrix = useDuel((state) => state.protagonist!.equipment);
return (
<ConfigurationChildWrapper>
@@ -33,11 +27,12 @@ export function Equipments() {
rowChild
selected={equipmentMatrix[rowIndex][columnIndex] === -1}
onClick={() => {
mutateTankopediaTemporary((draft) => {
mutateDuel((draft) => {
if (equipmentMatrix[rowIndex][columnIndex] === -1) {
draft.equipmentMatrix[rowIndex][columnIndex] = 0;
draft.protagonist!.equipment[rowIndex][columnIndex] = 0;
} else {
draft.equipmentMatrix[rowIndex][columnIndex] = -1;
draft.protagonist!.equipment[rowIndex][columnIndex] =
-1;
}
});
}}
@@ -48,11 +43,11 @@ export function Equipments() {
rowChild
selected={equipmentMatrix[rowIndex][columnIndex] === 1}
onClick={() => {
mutateTankopediaTemporary((draft) => {
mutateDuel((draft) => {
if (equipmentMatrix[rowIndex][columnIndex] === 1) {
draft.equipmentMatrix[rowIndex][columnIndex] = 0;
draft.protagonist!.equipment[rowIndex][columnIndex] = 0;
} else {
draft.equipmentMatrix[rowIndex][columnIndex] = 1;
draft.protagonist!.equipment[rowIndex][columnIndex] = 1;
}
});
}}
@@ -3,19 +3,15 @@ import { clamp, debounce } from 'lodash';
import { useEffect, useMemo, useRef, useState } from 'react';
import { GenericTankComponentButton } from '../../../../../../../components/ModuleButtons/GenericTankComponentButton';
import { asset } from '../../../../../../../core/blitzkrieg/asset';
import { useDuel } from '../../../../../../../stores/duel';
import {
mutateTankopediaTemporary,
useTankopediaTemporary,
} from '../../../../../../../stores/tankopedia';
import { mutateDuel, useDuel } from '../../../../../../../stores/duel';
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
export function Miscellaneous() {
const camouflage = useTankopediaTemporary((state) => state.camouflage);
const cooldownBooster = useTankopediaTemporary(
(state) => state.cooldownBooster,
const camouflage = useDuel((state) => state.protagonist!.camouflage);
const cooldownBooster = useDuel(
(state) => state.protagonist!.cooldownBooster,
);
const crewMastery = useTankopediaTemporary((state) => state.crewMastery);
const crewMastery = useDuel((state) => state.protagonist!.crewMastery);
const crewInput = useRef<HTMLInputElement>(null);
const [crewMasteryDraft, setCrewMasteryDraft] = useState(crewMastery);
const { tank } = useDuel((state) => state.protagonist!);
@@ -23,8 +19,8 @@ export function Miscellaneous() {
const debouncedApplyDraft = useMemo(
() =>
debounce((value) => {
mutateTankopediaTemporary((draft) => {
draft.crewMastery = value;
mutateDuel((draft) => {
draft.protagonist!.crewMastery = value;
});
}, 500),
[],
@@ -53,8 +49,8 @@ export function Miscellaneous() {
first
last
onClick={() => {
mutateTankopediaTemporary((draft) => {
draft.camouflage = !camouflage;
mutateDuel((draft) => {
draft.protagonist!.camouflage = !camouflage;
});
}}
/>
@@ -77,9 +73,10 @@ export function Miscellaneous() {
transform: 'translate(-50%, -50%) scale(0.8)',
}}
onClick={() => {
mutateTankopediaTemporary((draft) => {
draft.cooldownBooster++;
if (draft.cooldownBooster === 4) draft.cooldownBooster = 0;
mutateDuel((draft) => {
draft.protagonist!.cooldownBooster++;
if (draft.protagonist!.cooldownBooster === 4)
draft.protagonist!.cooldownBooster = 0;
});
}}
/>
@@ -106,8 +103,12 @@ export function Miscellaneous() {
return;
}
mutateTankopediaTemporary((draft) => {
draft.crewMastery = clamp(newValue / 100, 0.5, 1);
mutateDuel((draft) => {
draft.protagonist!.crewMastery = clamp(
newValue / 100,
0.5,
1,
);
});
}}
onKeyUp={(event) => {
@@ -3,16 +3,12 @@ import { use } from 'react';
import { ProvisionButton } from '../../../../../../../components/ModuleButtons/ProvisionButton';
import { availableProvisions } from '../../../../../../../core/blitzkrieg/availableProvisions';
import { provisionDefinitions } from '../../../../../../../core/blitzkrieg/provisionDefinitions';
import { useDuel } from '../../../../../../../stores/duel';
import {
mutateTankopediaTemporary,
useTankopediaTemporary,
} from '../../../../../../../stores/tankopedia';
import { mutateDuel, useDuel } from '../../../../../../../stores/duel';
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
export function Provisions() {
const { tank, gun } = useDuel((state) => state.protagonist!);
const provisions = useTankopediaTemporary((state) => state.provisions);
const provisions = useDuel((state) => state.protagonist!.provisions);
const awaitedProvisionDefinitions = use(provisionDefinitions);
const provisionsList = availableProvisions(
tank,
@@ -38,13 +34,14 @@ export function Provisions() {
provision={provision.id}
selected={selected}
onClick={() => {
mutateTankopediaTemporary((draft) => {
mutateDuel((draft) => {
if (selected) {
draft.provisions = draft.provisions.filter(
(id) => id !== provision.id,
);
draft.protagonist!.provisions =
draft.protagonist!.provisions.filter(
(id) => id !== provision.id,
);
} else {
draft.provisions.push(provision.id);
draft.protagonist!.provisions.push(provision.id);
}
});
}}
@@ -24,7 +24,6 @@ import { useEquipment } from '../../../../../../hooks/useEquipment';
import { useProvision } from '../../../../../../hooks/useProvision';
import { useProvisions } from '../../../../../../hooks/useProvisions';
import { mutateDuel, useDuel } from '../../../../../../stores/duel';
import { useTankopediaTemporary } from '../../../../../../stores/tankopedia';
import { Info } from './components/Info';
import { InfoWithDelta } from './components/InfoWithDelta';
@@ -35,7 +34,7 @@ export function Characteristics() {
);
const hasImprovedVentilation = useEquipment(102);
const provisions = useProvisions();
const crewMastery = useTankopediaTemporary((state) => state.crewMastery);
const crewMastery = useDuel((state) => state.protagonist!.crewMastery);
const provisionCrewBonus =
provisions.reduce(
(total, provision) =>
@@ -67,7 +66,7 @@ export function Characteristics() {
stockTurret.weight +
stockGun.weight;
const [penetrationDistance, setPenetrationDistance] = useState(250);
const camouflage = useTankopediaTemporary((state) => state.camouflage);
const camouflage = useDuel((state) => state.protagonist!.camouflage);
const hasRammer = useEquipment(100);
const hasCalibratedShells = useEquipment(103);
const hasEnhancedGunLayingDrive = useEquipment(104);
@@ -11,11 +11,11 @@ import { modelTransformEvent } from '../../../../../../core/blitzkrieg/modelTran
import { Pose, poseEvent } from '../../../../../../core/blitzkrieg/pose';
import { tankIcon } from '../../../../../../core/blitzkrieg/tankIcon';
import { useWideFormat } from '../../../../../../hooks/useWideFormat';
import { useDuel } from '../../../../../../stores/duel';
import {
import { mutateDuel, useDuel } from '../../../../../../stores/duel';
import mutateTankopediaPersistent, {
TankopediaMode,
mutateTankopediaTemporary,
useTankopediaTemporary,
useTankopediaPersistent,
} from '../../../../../../stores/tankopedia';
import { Controls } from '../Control';
import { Lighting } from '../Lighting';
@@ -33,7 +33,7 @@ export function TankSandbox() {
const awaitedModelDefinitions = use(modelDefinitions);
const canvasWrapper = useRef<HTMLDivElement>(null);
const [isFullScreen, setIsFullScreen] = useState(false);
const mode = useTankopediaTemporary((state) => state.mode);
const mode = useTankopediaPersistent((state) => state.mode);
const protagonist = useDuel((state) => state.protagonist!);
const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id];
const turretModelDefinition =
@@ -65,7 +65,7 @@ export function TankSandbox() {
function handlePoseEvent(pose: Pose) {
switch (pose) {
case Pose.HullDown: {
mutateTankopediaTemporary((draft) => {
mutateDuel((draft) => {
const [pitch, yaw] = applyPitchYawLimits(
-Infinity,
0,
@@ -74,15 +74,15 @@ export function TankSandbox() {
);
modelTransformEvent.emit({ pitch, yaw });
draft.model.pose.pitch = pitch;
draft.model.pose.yaw = yaw;
draft.protagonist!.pitch = pitch;
draft.protagonist!.yaw = yaw;
});
break;
}
case Pose.FaceHug: {
mutateTankopediaTemporary((draft) => {
mutateDuel((draft) => {
const [pitch, yaw] = applyPitchYawLimits(
Infinity,
0,
@@ -91,15 +91,15 @@ export function TankSandbox() {
);
modelTransformEvent.emit({ pitch, yaw });
draft.model.pose.pitch = pitch;
draft.model.pose.yaw = yaw;
draft.protagonist!.pitch = pitch;
draft.protagonist!.yaw = yaw;
});
break;
}
case Pose.Default:
mutateTankopediaTemporary((draft) => {
mutateDuel((draft) => {
const [pitch, yaw] = applyPitchYawLimits(
0,
0,
@@ -108,8 +108,8 @@ export function TankSandbox() {
);
modelTransformEvent.emit({ pitch, yaw });
draft.model.pose.pitch = pitch;
draft.model.pose.yaw = yaw;
draft.protagonist!.pitch = pitch;
draft.protagonist!.yaw = yaw;
});
break;
@@ -126,10 +126,10 @@ export function TankSandbox() {
});
useEffect(() => {
mutateTankopediaTemporary((draft) => {
[draft.model.pose.pitch, draft.model.pose.yaw] = applyPitchYawLimits(
draft.model.pose.pitch,
draft.model.pose.yaw,
mutateDuel((draft) => {
[draft.protagonist!.pitch, draft.protagonist!.yaw] = applyPitchYawLimits(
draft.protagonist!.pitch,
draft.protagonist!.yaw,
gunModelDefinition.pitch,
turretModelDefinition.yaw,
);
@@ -157,7 +157,7 @@ export function TankSandbox() {
<Tabs.Root
value={mode}
onValueChange={(mode) => {
mutateTankopediaTemporary((draft) => {
mutateTankopediaPersistent((draft) => {
draft.mode = mode as TankopediaMode;
});
}}
@@ -12,7 +12,7 @@ import { resolveArmor } from '../../../../../../../../../core/blitzkrieg/resolve
import { useArmor } from '../../../../../../../../../hooks/useArmor';
import { useModelDefinitions } from '../../../../../../../../../hooks/useModelDefinitions';
import { useDuel } from '../../../../../../../../../stores/duel';
import { useTankopediaTemporary } from '../../../../../../../../../stores/tankopedia';
import { useTankopediaPersistent } from '../../../../../../../../../stores/tankopedia';
export function ArmorHighlighting() {
const protagonist = useDuel((state) => state.protagonist!);
@@ -20,7 +20,7 @@ export function ArmorHighlighting() {
const modelDefinitions = useModelDefinitions();
const gunContainer = useRef<Group>(null);
const turretContainer = useRef<Group>(null);
const initialTankopediaState = useTankopediaTemporary.getState();
const initialTankopediaState = useTankopediaPersistent.getState();
useEffect(() => {
const hullOrigin = new Vector3(
@@ -80,14 +80,14 @@ export function ArmorHighlighting() {
turretContainer.current?.position.copy(turretPosition);
turretContainer.current?.rotation.copy(turretRotation);
}
const unsubscribe = useTankopediaTemporary.subscribe(
const unsubscribe = useTankopediaPersistent.subscribe(
(state) => state.mode,
(mode) => {
if (wrapper.current) wrapper.current.visible = mode === 'armor';
},
);
handleModelTransform(useTankopediaTemporary.getState().model.pose);
handleModelTransform(useDuel.getState().protagonist!);
modelTransformEvent.on(handleModelTransform);
return () => {
@@ -13,7 +13,7 @@ import { useArmor } from '../../../../../../../../../hooks/useArmor';
import { useModel } from '../../../../../../../../../hooks/useModel';
import { useModelDefinitions } from '../../../../../../../../../hooks/useModelDefinitions';
import { useDuel } from '../../../../../../../../../stores/duel';
import { useTankopediaTemporary } from '../../../../../../../../../stores/tankopedia';
import { useTankopediaPersistent } from '../../../../../../../../../stores/tankopedia';
interface ExternalModuleMaskProps {
ornamental?: boolean;
@@ -33,7 +33,7 @@ export const ExternalModuleMask = memo<ExternalModuleMaskProps>(
const modelDefinitions = useModelDefinitions();
const turretContainer = useRef<Group>(null);
const gunContainer = useRef<Group>(null);
const initialTankopediaState = useTankopediaTemporary.getState();
const initialTankopediaState = useTankopediaPersistent.getState();
useEffect(() => {
const hullOrigin = new Vector3(
@@ -102,7 +102,7 @@ export const ExternalModuleMask = memo<ExternalModuleMaskProps>(
turretContainer.current?.rotation.copy(turretRotation);
}
handleModelTransform(useTankopediaTemporary.getState().model.pose);
handleModelTransform(useDuel.getState().protagonist!);
modelTransformEvent.on(handleModelTransform);
return () => {
@@ -111,7 +111,7 @@ export const ExternalModuleMask = memo<ExternalModuleMaskProps>(
});
useEffect(() => {
const unsubscribe = useTankopediaTemporary.subscribe(
const unsubscribe = useTankopediaPersistent.subscribe(
(state) => state.mode,
(mode) => {
if (wrapper.current) wrapper.current.visible = mode === 'armor';
@@ -13,7 +13,7 @@ import { useArmor } from '../../../../../../../../../hooks/useArmor';
import { useModel } from '../../../../../../../../../hooks/useModel';
import { useModelDefinitions } from '../../../../../../../../../hooks/useModelDefinitions';
import { useDuel } from '../../../../../../../../../stores/duel';
import { useTankopediaTemporary } from '../../../../../../../../../stores/tankopedia';
import { useTankopediaPersistent } from '../../../../../../../../../stores/tankopedia';
interface SpacedArmorDepthProps {
ornamental?: boolean;
@@ -37,7 +37,7 @@ export const SpacedArmorDepth = memo<SpacedArmorDepthProps>(
const modelDefinitions = useModelDefinitions();
const turretContainer = useRef<Group>(null);
const gunContainer = useRef<Group>(null);
const initialTankopediaState = useTankopediaTemporary.getState();
const initialTankopediaState = useTankopediaPersistent.getState();
useEffect(() => {
if (!modelDefinitions) return;
@@ -108,7 +108,7 @@ export const SpacedArmorDepth = memo<SpacedArmorDepthProps>(
turretContainer.current?.rotation.copy(turretRotation);
}
handleModelTransform(useTankopediaTemporary.getState().model.pose);
handleModelTransform(useDuel.getState().protagonist!);
modelTransformEvent.on(handleModelTransform);
return () => {
@@ -117,7 +117,7 @@ export const SpacedArmorDepth = memo<SpacedArmorDepthProps>(
});
useEffect(() => {
const unsubscribe = useTankopediaTemporary.subscribe(
const unsubscribe = useTankopediaPersistent.subscribe(
(state) => state.mode,
(mode) => {
if (wrapper.current) wrapper.current.visible = mode === 'armor';
@@ -15,10 +15,9 @@ import {
} from '../../../../../../../core/blitzkrieg/modelTransform';
import { normalizeAnglePI } from '../../../../../../../core/math/normalizeAngle180';
import { useModel } from '../../../../../../../hooks/useModel';
import { useDuel } from '../../../../../../../stores/duel';
import { mutateDuel, useDuel } from '../../../../../../../stores/duel';
import mutateTankopediaPersistent, {
mutateTankopediaTemporary,
useTankopediaTemporary,
} from '../../../../../../../stores/tankopedia';
export const TankModel = memo(() => {
@@ -27,7 +26,6 @@ export const TankModel = memo(() => {
>(undefined);
const protagonist = useDuel((draft) => draft.protagonist!);
const canvas = useThree((state) => state.gl.domElement);
const physical = useTankopediaTemporary((state) => state.model.pose);
const hullContainer = useRef<Group>(null);
const turretContainer = useRef<Group>(null);
const gunContainer = useRef<Group>(null);
@@ -156,8 +154,8 @@ export const TankModel = memo(() => {
event.stopPropagation();
if (isTurret) {
yaw = physical.yaw;
pitch = physical.pitch;
yaw = protagonist.yaw;
pitch = protagonist.pitch;
mutateTankopediaPersistent((draft) => {
draft.model.visual.controlsEnabled = false;
@@ -179,9 +177,9 @@ export const TankModel = memo(() => {
modelTransformEvent.emit({ pitch, yaw });
}
function handlePointerUp() {
mutateTankopediaTemporary((draft) => {
draft.model.pose.pitch = normalizeAnglePI(pitch);
draft.model.pose.yaw = normalizeAnglePI(yaw);
mutateDuel((draft) => {
draft.protagonist!.pitch = normalizeAnglePI(pitch);
draft.protagonist!.yaw = normalizeAnglePI(yaw);
});
mutateTankopediaPersistent((draft) => {
draft.model.visual.controlsEnabled = true;
@@ -223,8 +221,8 @@ export const TankModel = memo(() => {
mutateTankopediaTemporary((draft) => {
draft.shot = undefined;
});
pitch = physical.pitch;
yaw = physical.yaw;
pitch = protagonist.pitch;
yaw = protagonist.yaw;
window.addEventListener('pointermove', handlePointerMove);
window.addEventListener('pointerup', handlePointerUp);
}
@@ -241,9 +239,9 @@ export const TankModel = memo(() => {
mutateTankopediaPersistent((draft) => {
draft.model.visual.controlsEnabled = true;
});
mutateTankopediaTemporary((draft) => {
draft.model.pose.pitch = normalizeAnglePI(pitch);
draft.model.pose.yaw = normalizeAnglePI(yaw);
mutateDuel((draft) => {
draft.protagonist!.pitch = normalizeAnglePI(pitch);
draft.protagonist!.yaw = normalizeAnglePI(yaw);
});
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
@@ -4,33 +4,29 @@ import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawL
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions';
import { modelTransformEvent } from '../../../../../../core/blitzkrieg/modelTransform';
import { useEquipment } from '../../../../../../hooks/useEquipment';
import { useDuel } from '../../../../../../stores/duel';
import {
mutateTankopediaTemporary,
useTankopediaTemporary,
} from '../../../../../../stores/tankopedia';
import { mutateDuel, useDuel } from '../../../../../../stores/duel';
import { useTankopediaPersistent } from '../../../../../../stores/tankopedia';
import { QuickEquipmentButton } from './components/QuickEquipmentButton';
export function RotationInputs() {
const protagonist = useDuel((state) => state.protagonist!);
const physical = useTankopediaTemporary((state) => state.model.pose);
const awaitedModelDefinitions = use(modelDefinitions);
const turretYawInput = useRef<HTMLInputElement>(null);
const gunPitchInput = useRef<HTMLInputElement>(null);
const mode = useTankopediaTemporary((state) => state.mode);
const hasCalibratedShells = useEquipment(103);
const mode = useTankopediaPersistent((state) => state.mode);
const hasCalibratedShells = useEquipment(103, true);
const hasEnhancedArmor = useEquipment(110);
useEffect(() => {
turretYawInput.current!.value = `${-Math.round(
physical.yaw * (180 / Math.PI),
protagonist.yaw * (180 / Math.PI),
)}`;
}, [physical.yaw]);
}, [protagonist.yaw]);
useEffect(() => {
gunPitchInput.current!.value = `${-Math.round(
physical.pitch * (180 / Math.PI),
protagonist.pitch * (180 / Math.PI),
)}`;
}, [physical.pitch]);
}, [protagonist.pitch]);
const tankModelDefinition = awaitedModelDefinitions[protagonist.tank.id];
const turretModelDefinition =
@@ -68,18 +64,18 @@ export function RotationInputs() {
Yaw
</TextField.Slot>
<TextField.Input
defaultValue={Math.round(physical.yaw * (180 / Math.PI))}
defaultValue={Math.round(protagonist.yaw * (180 / Math.PI))}
onBlur={() => {
const [pitch, yaw] = applyPitchYawLimits(
physical.pitch,
protagonist.pitch,
-Number(turretYawInput.current?.value) * (Math.PI / 180),
gunModelDefinition.pitch,
turretModelDefinition.yaw,
);
modelTransformEvent.emit({ pitch, yaw });
mutateTankopediaTemporary((state) => {
state.model.pose.pitch = pitch;
state.model.pose.yaw = yaw;
useDuel((state) => {
state.protagonist!.pitch = pitch;
state.protagonist!.yaw = yaw;
});
turretYawInput.current!.value = `${Math.round(
yaw * (180 / Math.PI),
@@ -116,18 +112,18 @@ export function RotationInputs() {
Pitch
</TextField.Slot>
<TextField.Input
defaultValue={-Math.round(physical.pitch * (180 / Math.PI))}
defaultValue={-Math.round(protagonist.pitch * (180 / Math.PI))}
onBlur={() => {
const [pitch, yaw] = applyPitchYawLimits(
-Number(gunPitchInput.current?.value) * (Math.PI / 180),
physical.yaw,
protagonist.yaw,
gunModelDefinition.pitch,
turretModelDefinition.yaw,
);
modelTransformEvent.emit({ pitch, yaw });
mutateTankopediaTemporary((state) => {
state.model.pose.pitch = pitch;
state.model.pose.yaw = yaw;
mutateDuel((state) => {
state.protagonist!.pitch = pitch;
state.protagonist!.yaw = yaw;
});
gunPitchInput.current!.value = `${Math.round(
pitch * (180 / Math.PI),
@@ -158,8 +154,8 @@ export function RotationInputs() {
equipment={103}
active={hasCalibratedShells}
onClick={() => {
mutateTankopediaTemporary((draft) => {
draft.equipmentMatrix[0][0] = hasCalibratedShells ? 0 : 1;
mutateDuel((draft) => {
draft.antagonist!.equipment[0][0] = hasCalibratedShells ? 0 : 1;
});
}}
/>
@@ -167,8 +163,8 @@ export function RotationInputs() {
equipment={110}
active={hasEnhancedArmor}
onClick={() => {
mutateTankopediaTemporary((draft) => {
draft.equipmentMatrix[1][1] = hasEnhancedArmor ? 0 : -1;
mutateDuel((draft) => {
draft.protagonist!.equipment[1][1] = hasEnhancedArmor ? 0 : -1;
});
}}
/>
+5 -43
View File
@@ -1,11 +1,9 @@
'use client';
import { Flex } from '@radix-ui/themes';
import { use, useEffect } from 'react';
import { useEffect } from 'react';
import PageWrapper from '../../../../components/PageWrapper';
import { availableProvisions } from '../../../../core/blitzkrieg/availableProvisions';
import { provisionDefinitions } from '../../../../core/blitzkrieg/provisionDefinitions';
import { tankDefinitions } from '../../../../core/blitzkrieg/tankDefinitions';
import { assignDuelMember } from '../../../../core/blitzkrieg/assignDuelMember';
import { useWideFormat } from '../../../../hooks/useWideFormat';
import { mutateDuel, useDuel } from '../../../../stores/duel';
import { mutateTankopediaTemporary } from '../../../../stores/tankopedia';
@@ -20,26 +18,12 @@ import { TankSandbox } from './components/Model/TankSandbox';
import { Title } from './components/Title';
export default function Page({ params }: { params: { id: string } }) {
const id = parseInt(params.id);
const awaitedTankDefinitions = use(tankDefinitions);
const tank = awaitedTankDefinitions[id];
const gun = tank.turrets.at(-1)!.guns.at(-1)!;
const initialId = parseInt(params.id);
const assigned = useDuel((state) => state.assigned);
const wideFormat = useWideFormat();
useEffect(() => {
mutateDuel((draft) => {
draft.assigned = true;
draft.protagonist = {
tank,
engine: tank.engines.at(-1)!,
turret: tank.turrets.at(-1)!,
gun,
shell: tank.turrets.at(-1)!.guns.at(-1)!.shells[0],
track: tank.tracks.at(-1)!,
};
draft.antagonist = { ...draft.protagonist };
});
assignDuelMember('both', initialId);
function handleKeyDown(event: KeyboardEvent) {
if (document.activeElement instanceof HTMLInputElement) return;
@@ -72,34 +56,12 @@ export default function Page({ params }: { params: { id: string } }) {
}
}
(async () => {
const provisionsList = availableProvisions(
tank,
gun,
await provisionDefinitions,
);
mutateTankopediaTemporary((draft) => {
draft.model.pose.yaw = 0;
draft.model.pose.pitch = 0;
draft.consumables = [];
draft.provisions = provisionsList
.filter((provision) => provision.crew !== undefined)
.map((provision) => provision.id);
draft.shot = undefined;
draft.equipmentMatrix = [
[1, -1, -1],
[0, 0, 0],
[0, 0, 0],
];
});
})();
window.addEventListener('keydown', handleKeyDown);
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [id, assigned]);
}, [initialId, assigned]);
return (
<PageWrapper color="purple" size="double">
@@ -22,7 +22,6 @@ import {
Shot,
mutateTankopediaTemporary,
useTankopediaPersistent,
useTankopediaTemporary,
} from '../../../../stores/tankopedia';
import { externalModuleMaskRenderTarget } from '../ExternalModuleMask';
import { spacedArmorDepthRenderTarget } from '../SpacedArmorDepth';
@@ -52,7 +51,7 @@ export const ArmorMesh = memo<ArmorMeshProps>(
const surfaceNormal = new Vector3();
const mesh = useRef<Mesh>(null);
const scene = useThree((state) => state.scene);
const hasCalibratedShells = useAwait(hasEquipment(103));
const hasCalibratedShells = useAwait(hasEquipment(103, true));
const hasEnhancedArmor = useAwait(hasEquipment(110));
const wireframe = initialTankopedia.model.visual.wireframe;
@@ -92,8 +91,8 @@ export const ArmorMesh = memo<ArmorMeshProps>(
}
const unsubscribes = [
useTankopediaTemporary.subscribe(
(state) => state.equipmentMatrix,
useDuel.subscribe(
(state) => state.protagonist!.equipment,
updateQuickEquipments,
),
useDuel.subscribe(
+57
View File
@@ -0,0 +1,57 @@
import {
DuelMember,
genericDefaultEquipmentMatrix,
mutateDuel,
} from '../../stores/duel';
import { availableProvisions } from './availableProvisions';
import { provisionDefinitions } from './provisionDefinitions';
import { tankDefinitions } from './tankDefinitions';
export async function assignDuelMember(
type: 'antagonist' | 'protagonist' | 'both',
id: number,
) {
const awaitedTankDefinitions = await tankDefinitions;
const awaitedProvisionDefinitions = await provisionDefinitions;
const tank = awaitedTankDefinitions[id];
const turret = tank.turrets.at(-1)!;
const gun = turret.guns.at(-1)!;
const shell = gun.shells[0];
const engine = tank.engines.at(-1)!;
const track = tank.tracks.at(-1)!;
const provisionsList = availableProvisions(
tank,
gun,
awaitedProvisionDefinitions,
);
const member: DuelMember = {
tank,
engine,
turret,
gun,
shell,
track,
equipment: genericDefaultEquipmentMatrix,
pitch: 0,
yaw: 0,
camouflage: true,
consumables: [],
cooldownBooster: 0,
crewMastery: 1,
provisions: provisionsList
.filter((provision) => provision.crew !== undefined)
.map((provision) => provision.id),
};
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 };
}
});
}
+5 -5
View File
@@ -1,13 +1,13 @@
import { useDuel } from '../../stores/duel';
import { useTankopediaTemporary } from '../../stores/tankopedia';
import { equipmentDefinitions } from './equipmentDefinitions';
export async function hasEquipment(id: number) {
export async function hasEquipment(id: number, antagonist = false) {
const awaitedEquipmentDefinitions = await equipmentDefinitions;
const protagonist = useDuel.getState().protagonist!;
const member = useDuel.getState()[antagonist ? 'antagonist' : 'protagonist']!;
const equipmentRows =
awaitedEquipmentDefinitions.presets[protagonist.tank.equipment];
const equipmentMatrix = useTankopediaTemporary.getState().equipmentMatrix;
awaitedEquipmentDefinitions.presets[member.tank.equipment];
const equipmentMatrix =
useDuel.getState()[antagonist ? 'antagonist' : 'protagonist']!.equipment;
return equipmentRows.some((equipmentRow, rowIndex) => {
return equipmentRow.some((equipment, columnIndex) => {
+4 -2
View File
@@ -1,10 +1,12 @@
import { EquipmentPreset } from '../core/blitzkrieg/equipmentDefinitions';
import { EquipmentMatrix } from '../stores/tankopedia';
import { EquipmentMatrix } from '../stores/duel';
export function useAppliedEquipments(
matrix: EquipmentMatrix,
preset: EquipmentPreset,
) {
const flatMatrix = matrix.flat();
return preset.flat().map((options, index) => options[flatMatrix[index] == 1 ? 1 : 0]);
return preset
.flat()
.map((options, index) => options[flatMatrix[index] == 1 ? 1 : 0]);
}
+2 -2
View File
@@ -1,6 +1,6 @@
import { useTankopediaTemporary } from '../stores/tankopedia';
import { useDuel } from '../stores/duel';
export function useConsumable(id: number) {
const consumables = useTankopediaTemporary((state) => state.consumables);
const consumables = useDuel((state) => state.protagonist!.consumables);
return consumables.includes(id);
}
+8 -7
View File
@@ -1,15 +1,16 @@
import { use, useMemo } from 'react';
import { equipmentDefinitions } from '../core/blitzkrieg/equipmentDefinitions';
import { useDuel } from '../stores/duel';
import { useTankopediaTemporary } from '../stores/tankopedia';
export function useEquipment(id: number) {
export function useEquipment(id: number, antagonist = false) {
const awaitedEquipmentDefinitions = use(equipmentDefinitions);
const protagonist = useDuel((state) => state.protagonist!);
const member = useDuel(
(state) => state[antagonist ? 'antagonist' : 'protagonist']!,
);
const equipmentRows =
awaitedEquipmentDefinitions.presets[protagonist.tank.equipment];
const equipmentMatrix = useTankopediaTemporary(
(state) => state.equipmentMatrix,
awaitedEquipmentDefinitions.presets[member.tank.equipment];
const equipmentMatrix = useDuel(
(state) => state[antagonist ? 'antagonist' : 'protagonist']!.equipment,
);
const value = useMemo(
() =>
@@ -21,7 +22,7 @@ export function useEquipment(id: number) {
return equipped === id;
});
}),
[equipmentMatrix, protagonist.tank],
[equipmentMatrix, member.tank],
);
return value;
+2 -2
View File
@@ -1,6 +1,6 @@
import { useTankopediaTemporary } from '../stores/tankopedia';
import { useDuel } from '../stores/duel';
export function useProvision(id: number) {
const provisions = useTankopediaTemporary((state) => state.provisions);
const provisions = useDuel((state) => state.protagonist!.provisions);
return provisions.includes(id);
}
+2 -2
View File
@@ -1,9 +1,9 @@
import { use } from 'react';
import { provisionDefinitions } from '../core/blitzkrieg/provisionDefinitions';
import { useTankopediaTemporary } from '../stores/tankopedia';
import { useDuel } from '../stores/duel';
export function useProvisions() {
const provisions = useTankopediaTemporary((state) => state.provisions);
const provisions = useDuel((state) => state.protagonist!.provisions);
const awaitedProvisionDefinitions = use(provisionDefinitions);
return provisions.map((id) => awaitedProvisionDefinitions[id]);
}
+43 -1
View File
@@ -1,7 +1,43 @@
import { produce } from 'immer';
import { create } from 'zustand';
import { subscribeWithSelector } from 'zustand/middleware';
import { DuelMember } from './tankopedia';
import {
EngineDefinition,
GunDefinition,
ShellDefinition,
TankDefinition,
TrackDefinition,
TurretDefinition,
} from '../core/blitzkrieg/tankDefinitions';
type EquipmentMatrixItem = -1 | 0 | 1;
type EquipmentMatrixRow = [
EquipmentMatrixItem,
EquipmentMatrixItem,
EquipmentMatrixItem,
];
export type EquipmentMatrix = [
EquipmentMatrixRow,
EquipmentMatrixRow,
EquipmentMatrixRow,
];
export interface DuelMember {
tank: TankDefinition;
engine: EngineDefinition;
turret: TurretDefinition;
gun: GunDefinition;
shell: ShellDefinition;
track: TrackDefinition;
equipment: EquipmentMatrix;
pitch: number;
yaw: number;
consumables: number[];
crewMastery: number;
provisions: number[];
camouflage: boolean;
cooldownBooster: number;
}
export interface Duel {
assigned: boolean;
@@ -18,3 +54,9 @@ export const useDuel = create<Duel>()(
export function mutateDuel(recipe: (draft: Duel) => void) {
useDuel.setState(produce(recipe));
}
export const genericDefaultEquipmentMatrix: EquipmentMatrix = [
[1, 1, -1],
[0, 0, 0],
[0, 0, 0],
];
+5 -51
View File
@@ -5,28 +5,13 @@ import { create } from 'zustand';
import { persist, subscribeWithSelector } from 'zustand/middleware';
import { ENVIRONMENTS } from '../app/tools/tankopedia/[id]/components/Lighting';
import { TankType, TreeType } from '../components/Tanks';
import {
EngineDefinition,
GunDefinition,
ShellDefinition,
TankDefinition,
Tier,
TrackDefinition,
TurretDefinition,
} from '../core/blitzkrieg/tankDefinitions';
import { Tier } from '../core/blitzkrieg/tankDefinitions';
export type TankopediaSortBy = keyof typeof SORT_NAMES;
export type TankopediaSortDirection = 'ascending' | 'descending';
export type TankopediaTestTankDisplay = 'include' | 'exclude' | 'only';
export type TankopediaMode = 'model' | 'armor';
export interface DuelMember {
tank: TankDefinition;
engine: EngineDefinition;
turret: TurretDefinition;
gun: GunDefinition;
shell: ShellDefinition;
track: TrackDefinition;
}
interface TankopediaPersistent {
model: {
visual: {
@@ -50,24 +35,10 @@ interface TankopediaPersistent {
test: TankopediaTestTankDisplay;
page: number;
};
mode: TankopediaMode;
}
export type EquipmentMatrix = (-1 | 0 | 1)[][];
interface TankopediaTemporary {
model: {
pose: {
yaw: number;
pitch: number;
};
};
equipmentMatrix: EquipmentMatrix;
mode: TankopediaMode;
consumables: number[];
crewMastery: number;
provisions: number[];
camouflage: boolean;
cooldownBooster: number;
shot?: Shot;
}
@@ -152,31 +123,14 @@ export const useTankopediaPersistent = create<TankopediaPersistent>()(
test: 'include',
page: 0,
},
mode: 'model',
})),
{ name: 'tankopedia', merge },
),
);
export const useTankopediaTemporary = create<TankopediaTemporary>()(
subscribeWithSelector<TankopediaTemporary>(() => ({
model: {
pose: {
yaw: 0,
pitch: 0,
},
},
equipmentMatrix: [
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
],
crewMastery: 1,
mode: 'model',
consumables: [],
provisions: [],
camouflage: true,
cooldownBooster: 0,
})),
subscribeWithSelector<TankopediaTemporary>(() => ({})),
);
export default function mutateTankopediaPersistent(