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