diff --git a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/index.tsx b/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/index.tsx index a2419b4af..cdd7575ee 100644 --- a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/index.tsx +++ b/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/index.tsx @@ -17,9 +17,9 @@ import { Vector2, } from "three"; import { degToRad } from "three/src/math/MathUtils.js"; -import { hasEquipment } from "../../../../core/blitzkit/hasEquipment"; import { jsxTree } from "../../../../core/blitzkit/jsxTree"; -import { Duel } from "../../../../stores/duel"; +import { DuelModule, getDuel } from "../../../../hooks/useDuel"; +import { hasEquipment } from "../../../../hooks/useEquipment"; import { Tankopedia } from "../../../../stores/tankopedia"; import { TankopediaPersistent } from "../../../../stores/tankopediaPersistent"; import { transitionEvent } from "../../../Lighting"; @@ -82,9 +82,8 @@ export function PrimaryArmorSceneComponent({ useEffect(() => { function handleShellChange() { - const tankopediaEphemeral = Tankopedia.state; const shell = - tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell; + Tankopedia.state.customShell ?? getDuel("antagonist", DuelModule.Shell); material.uniforms.caliber.value = shell.caliber; material.uniforms.ricochet.value = degToRad( @@ -117,15 +116,16 @@ export function PrimaryArmorSceneComponent({ material.wireframe = wireframe; } function handleProtagonistEquipmentChange(noInvalidate = false) { - const equipment = Duel.state.protagonist.equipmentMatrix; + const equipment = Tankopedia.state.protagonist.equipment; + const protagonist = getDuel("protagonist", DuelModule.Tank); const hasEnhancedArmor = hasEquipment( 110, - Duel.state.protagonist.tank.equipment_preset, + protagonist.equipment_preset, equipment, ); const equalizer = - (Duel.state.equalize - ? Duel.state.protagonist.tank.equalizer?.armor + (Tankopedia.state.equalize + ? protagonist.equalizer?.armor : undefined) ?? 1; material.uniforms.thickness.value = @@ -134,10 +134,11 @@ export function PrimaryArmorSceneComponent({ if (!noInvalidate) invalidate(); } function handleAntagonistEquipmentChange(noInvalidate = false) { - const equipment = Duel.state.antagonist.equipmentMatrix; + const equipment = Tankopedia.state.antagonist.equipment; const tankopediaEphemeral = Tankopedia.state; const shell = - tankopediaEphemeral.customShell ?? Duel.state.antagonist.shell; + tankopediaEphemeral.customShell ?? + getDuel("antagonist", DuelModule.Shell); const penetration = shell.penetration!.near; const hasCalibratedShells = hasEquipment( 103, diff --git a/packages/website/src/components/Armor/components/StaticArmorSceneComponent.tsx b/packages/website/src/components/Armor/components/StaticArmorSceneComponent.tsx index 4046ae014..673887854 100644 --- a/packages/website/src/components/Armor/components/StaticArmorSceneComponent.tsx +++ b/packages/website/src/components/Armor/components/StaticArmorSceneComponent.tsx @@ -74,7 +74,7 @@ export function StaticArmorSceneComponent({ ...props }: StaticArmorSceneComponentProps) { const camera = useThree((state) => state.camera); - const hasEnhancedArmor = useEquipment(110); + const hasEnhancedArmor = useEquipment("protagonist", 110); const tank = Duel.use((state) => state.protagonist.tank); const equalize = Duel.use((state) => state.equalize); const equalizer = (equalize ? tank.equalizer : undefined) ?? defaultEqualizer; diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Crew.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Crew.tsx index e5a6e1481..633d077b7 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Crew.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/components/Crew.tsx @@ -17,7 +17,7 @@ export function Crew({ stats }: StatsAcceptorProps) { const tank = Duel.use((state) => state.protagonist.tank); const { strings } = useLocale(); const provisions = Duel.use((state) => state.protagonist.provisions); - const hasImprovedVentilation = useEquipment(102); + const hasImprovedVentilation = useEquipment("protagonist", 102); const provisionCrewBonus = provisions.reduce( (total, provision) => diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/index.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/index.tsx index b739150db..106ecfc1a 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/index.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Characteristics/index.tsx @@ -71,8 +71,8 @@ export function Characteristics({ skeleton }: MaybeSkeletonComponentProps) { }, ); - const hasImprovedVerticalStabilizer = useEquipment(122); - const hasDownImprovedVerticalStabilizer = useEquipment(124); + const hasImprovedVerticalStabilizer = useEquipment("protagonist", 122); + const hasDownImprovedVerticalStabilizer = useEquipment("protagonist", 124); useEffect(() => { const [pitch, yaw] = applyPitchYawLimits( diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Consumables.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Consumables.tsx index 972a69356..02949cc26 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Consumables.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Consumables.tsx @@ -32,8 +32,8 @@ export function Consumables() { const cooldownBooster = Duel.use( (state) => state.protagonist.cooldownBooster, ); - const hasConsumableDeliverySystem = useEquipment(118); - const hasHighEndConsumables = useEquipment(101); + const hasConsumableDeliverySystem = useEquipment("protagonist", 118); + const hasHighEndConsumables = useEquipment("protagonist", 101); const { strings } = useLocale(); return ( diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/QuickInputs/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/QuickInputs/index.tsx index d4fd3d5b6..e62181391 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/QuickInputs/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/QuickInputs/index.tsx @@ -23,8 +23,8 @@ export function QuickInputs() { const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id]; const initialTurretPitch = tankModelDefinition.initial_turret_rotation?.pitch ?? 0; - const hasImprovedVerticalStabilizer = useEquipment(122); - const hasDownImprovedVerticalStabilizer = useEquipment(124); + const hasImprovedVerticalStabilizer = useEquipment("protagonist", 122); + const hasDownImprovedVerticalStabilizer = useEquipment("protagonist", 124); function displayPitch(pitch: number) { return radToDeg(pitch) - initialTurretPitch; diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx index 05fc67d92..c982217af 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx @@ -68,9 +68,9 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) { const antagonistShell = Duel.use((state) => state.antagonist.shell); const [antagonistSelectorOpen, setAntagonistSelectorOpen] = useState(false); const antagonistTank = Duel.use((state) => state.antagonist.tank); - const hasCalibratedShells = useEquipment(103, true); + const hasCalibratedShells = useEquipment("protagonist", 103, true); const [tab, setTab] = useState("search"); - const hasEnhancedArmor = useEquipment(110); + const hasEnhancedArmor = useEquipment("protagonist", 110); const antagonistUniqueGuns = uniqueGuns(antagonistTank.turrets); const { strings, unwrap } = useLocale(); const revealed = Tankopedia.use((state) => state.revealed); diff --git a/packages/website/src/components/TankopediaSandbox/index.tsx b/packages/website/src/components/TankopediaSandbox/index.tsx index 595647e86..c911e98b2 100644 --- a/packages/website/src/components/TankopediaSandbox/index.tsx +++ b/packages/website/src/components/TankopediaSandbox/index.tsx @@ -8,9 +8,9 @@ import { import { applyPitchYawLimits } from "../../core/blitz/applyPitchYawLimits"; import { modelTransformEvent } from "../../core/blitzkit/modelTransform"; import { Pose, poseEvent } from "../../core/blitzkit/pose"; +import { DuelModule, useDuel } from "../../hooks/useDuel"; import { useEquipment } from "../../hooks/useEquipment"; import { useModel } from "../../hooks/useModel"; -import { useProtagonistTurret } from "../../hooks/useProtagonistTurret"; import { Tankopedia } from "../../stores/tankopedia"; import { TankopediaPersistent } from "../../stores/tankopediaPersistent"; import { TankopediaDisplay } from "../../stores/tankopediaPersistent/constants"; @@ -39,12 +39,12 @@ export const TankopediaSandbox = forwardRef< TankSandboxProps >(({ thicknessRange }, ref) => { const canvas = useRef(null); - const hasImprovedVerticalStabilizer = useEquipment(122); - const hasDownImprovedVerticalStabilizer = useEquipment(124); + const hasImprovedVerticalStabilizer = useEquipment("protagonist", 122); + const hasDownImprovedVerticalStabilizer = useEquipment("protagonist", 124); const tank = useDuel("protagonist", DuelModule.Tank); const gun = useDuel("protagonist", DuelModule.Gun); - const turret = useProtagonistTurret(); + const turret = useDuel("protagonist", DuelModule.Turret); const model = Tankopedia.use((state) => state.protagonist.model); const turretModelDefinition = model.turrets[turret.id]; diff --git a/packages/website/src/core/blitzkit/hasEquipment.ts b/packages/website/src/core/blitzkit/hasEquipment.ts deleted file mode 100644 index 1445996f0..000000000 --- a/packages/website/src/core/blitzkit/hasEquipment.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { api } from "../../api/dynamic"; -import type { EquipmentMatrix } from "../../stores/duel"; - -const equipmentDefinitions = await api.equipment(); - -export function hasEquipment( - id: number, - equipmentPreset: string, - equipmentMatrix: EquipmentMatrix, -) { - const preset = equipmentDefinitions.presets[equipmentPreset]; - return preset.slots.some((slot, index) => { - const row = Math.floor(index / 3); - const column = index % 3; - const choice = equipmentMatrix[row][column]; - - if (choice === 0) return false; - - const equipped = slot[choice === -1 ? "left" : "right"]; - - return equipped === id; - }); -} diff --git a/packages/website/src/hooks/useDuel.ts b/packages/website/src/hooks/useDuel.ts index b04077e2b..6a084cae9 100644 --- a/packages/website/src/hooks/useDuel.ts +++ b/packages/website/src/hooks/useDuel.ts @@ -72,3 +72,55 @@ export function useDuel(side: DuelSide, module: DuelModule) { const shellId = Tankopedia.use((state) => state[side].shell); return gun.shells.find((shell) => shell.id === shellId)!; } + +export function getDuel( + side: DuelSide, + module: DuelModule.Tank, +): TankDefinition; +export function getDuel( + side: DuelSide, + module: DuelModule.Track, +): TrackDefinition; +export function getDuel( + side: DuelSide, + module: DuelModule.Engine, +): EngineDefinition; +export function getDuel( + side: DuelSide, + module: DuelModule.Turret, +): TurretDefinition; +export function getDuel(side: DuelSide, module: DuelModule.Gun): GunDefinition; +export function getDuel( + side: DuelSide, + module: DuelModule.Shell, +): ShellDefinition; + +export function getDuel(side: DuelSide, module: DuelModule) { + const tank = tanks.tanks[Tankopedia.state[side].tank]; + + if (module === DuelModule.Tank) return tank; + + if (module === DuelModule.Track) { + return tank.tracks.find( + (track) => track.id === Tankopedia.state[side].track, + )!; + } + + if (module === DuelModule.Engine) { + return tank.engines.find( + (engine) => engine.id === Tankopedia.state[side].engine, + )!; + } + + const turret = tank.turrets.find( + (turret) => turret.id === Tankopedia.state[side].turret, + )!; + + if (module === DuelModule.Turret) return turret; + + const gun = turret.guns.find((gun) => gun.id === Tankopedia.state[side].gun)!; + + if (module === DuelModule.Gun) return gun; + + return gun.shells.find((shell) => shell.id === Tankopedia.state[side].shell)!; +} diff --git a/packages/website/src/hooks/useEquipment.ts b/packages/website/src/hooks/useEquipment.ts index ecd36e196..41ec4eb58 100644 --- a/packages/website/src/hooks/useEquipment.ts +++ b/packages/website/src/hooks/useEquipment.ts @@ -9,25 +9,30 @@ export type DuelSide = "protagonist" | "antagonist"; export function useEquipment(side: DuelSide, id: number) { const member = Tankopedia.use((state) => state[side]); + const appliedEquipment = Tankopedia.use((state) => state[side].equipment); + const tank = tanks.tanks[member.tank]; - const preset = equipment.presets[tank.equipment_preset]; - const equipmentMatrix = Tankopedia.use( - (state) => state[side].equipment_matrix, + + const value = useMemo( + () => hasEquipment(id, tank.equipment_preset, appliedEquipment), + [appliedEquipment, member.tank], ); - const value = useMemo(() => { - return preset.slots.some((slot, index) => { - const row = Math.floor(index / 3); - const column = index % 3; - const choice = equipmentMatrix[row][column]; - - if (choice === 0) return false; - - const equipped = slot[choice === -1 ? "left" : "right"]; - - return equipped === id; - }); - }, [equipmentMatrix, member.tank]); - return value; } + +export function hasEquipment( + id: number, + _preset: string, + applied: Record, +) { + const preset = equipment.presets[_preset]; + + return preset.slots.some((slot, index) => { + if (!(index in applied)) return false; + + const choice = applied[index]; + + return slot.options[choice] === id; + }); +} diff --git a/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/_index.tsx b/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/_index.tsx index 66a74a2e5..304828a33 100644 --- a/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/_index.tsx +++ b/packages/website/src/pages/[...chunk_default]/[...locale]/tanks/[slug]/_index.tsx @@ -20,7 +20,7 @@ export const Page = withErrorWrapper( Tankopedia.useInitialization(protagonistTank); const protagonist = Tankopedia.use((state) => state.protagonist); - // const protagonistEquipment = useEquipment(protagonistTank.tank!); + // const protagonistEquipment = useEquipment("protagonist", protagonistTank.tank!); const characteristics = useMemo( () => computeCharacteristics(), diff --git a/packages/website/src/stores/tankopedia.ts b/packages/website/src/stores/tankopedia.ts index 5729ede2f..df056192b 100644 --- a/packages/website/src/stores/tankopedia.ts +++ b/packages/website/src/stores/tankopedia.ts @@ -1,5 +1,9 @@ import { createDefaultSkills } from "@blitzkit/core"; -import type { ShellDefinition, TankDefinition } from "@blitzkit/protos"; +import type { + ModelDefinition, + ShellDefinition, + TankDefinition, +} from "@blitzkit/protos"; import type { Vector3 } from "three"; import { Varuna } from "varuna"; import { api } from "../api/dynamic"; @@ -81,6 +85,9 @@ interface Tankopedia { protagonist: TankState; antagonist: TankState; + model: ModelDefinition; + + equalize: boolean; shot?: Shot; skills: Record; @@ -118,6 +125,8 @@ export const Tankopedia = new Varuna((tank) => ({ antagonist: createTankState(tank), model: models.models[tank.id], + equalize: false, + relativeAgainst: TankopediaRelativeAgainst.Class, editStatic: false, skills: createDefaultSkills(skills),