feat: migrated duel store

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

No files matched your search

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