diff --git a/packages/website/src/components/TankopediaSkinSwitcher/index.tsx b/packages/website/src/components/TankopediaSkinSwitcher/index.tsx
index 6f60d4e1e..6cf61a20c 100644
--- a/packages/website/src/components/TankopediaSkinSwitcher/index.tsx
+++ b/packages/website/src/components/TankopediaSkinSwitcher/index.tsx
@@ -1,8 +1,10 @@
import { alias } from "@blitzkit/core";
+import { PauseIcon, PlayIcon } from "@radix-ui/react-icons";
import { uniqBy } from "lodash-es";
import { api } from "../../api/dynamic";
import { SKIN_NUMBER } from "../../constants/skinNumber";
import { DuelModule, useDuel } from "../../hooks/useDuel";
+import { hasSkinAnimations } from "../../hooks/useSkin";
import { useUnwrapper } from "../../hooks/useUnwrapper";
import { Tankopedia } from "../../stores/tankopedia";
import { TankopediaDisplay } from "../../stores/tankopediaPersistent/constants";
@@ -20,6 +22,11 @@ export function TankopediaSkinSwitcher() {
const skin = Tankopedia.use((state) => state.loaders.skin.requested);
const showControls = Tankopedia.use((state) => state.showControls);
const display = Tankopedia.use((state) => state.loaders.display.loaded);
+ const loadedSkin = Tankopedia.use((state) => state.loaders.skin.loaded);
+ const loadedAttachment = Tankopedia.use(
+ (state) => state.loaders.attachment.loaded,
+ );
+ const playing = Tankopedia.use((state) => state.animate);
const skins = uniqBy(
Object.values(camouflageDefinitions.camouflages).filter(
@@ -36,6 +43,11 @@ export function TankopediaSkinSwitcher() {
].filter((group) => group.length > 0);
const isVisible = showControls && display === TankopediaDisplay.Model;
+ const hasAnimations =
+ hasSkinAnimations(tank.id, loadedSkin) ||
+ hasSkinAnimations(tank.id, loadedAttachment);
+
+ console.log("hasAnimations", hasAnimations);
return (
@@ -80,6 +92,26 @@ export function TankopediaSkinSwitcher() {
})}
))}
+
+ {hasAnimations && (
+
+ {
+ Tankopedia.mutate((draft) => {
+ draft.animate = !draft.animate;
+ });
+ }}
+ >
+ {playing ? : }
+
+
+ )}
);
}
diff --git a/packages/website/src/hooks/useSkin.ts b/packages/website/src/hooks/useSkin.ts
index 402a05de3..fe8516d46 100644
--- a/packages/website/src/hooks/useSkin.ts
+++ b/packages/website/src/hooks/useSkin.ts
@@ -79,6 +79,14 @@ function skinPath(id: number, skin: number) {
return alias("models_legendary", `/${id}/skins/${skin}.glb`);
}
+/** whether an already loaded skin of this tank has animations */
+export function hasSkinAnimations(id: number, skin: number | undefined) {
+ return (
+ skin !== undefined &&
+ loadedSkins.get(skinPath(id, skin))?.mixer !== undefined
+ );
+}
+
// attachments load the same way as camouflages and can be shown with one
export function useSkin(id: number, loader: "skin" | "attachment" = "skin") {
const requested = Tankopedia.use((state) => state.loaders[loader].requested);
diff --git a/packages/website/src/hooks/useSkinAnimation.ts b/packages/website/src/hooks/useSkinAnimation.ts
index 1c686bd28..94d381296 100644
--- a/packages/website/src/hooks/useSkinAnimation.ts
+++ b/packages/website/src/hooks/useSkinAnimation.ts
@@ -1,8 +1,13 @@
import { invalidate, useFrame } from "@react-three/fiber";
+import { Tankopedia } from "../stores/tankopedia";
import type { Skin } from "./useSkin";
export function useSkinAnimation(skins: Skin[]) {
+ const animate = Tankopedia.use((state) => state.animate);
+
useFrame((_, delta) => {
+ if (animate) return;
+
for (const { mixer } of skins) {
if (!mixer) continue;
diff --git a/packages/website/src/stores/tankopedia.ts b/packages/website/src/stores/tankopedia.ts
index f24b2341a..0454d954a 100644
--- a/packages/website/src/stores/tankopedia.ts
+++ b/packages/website/src/stores/tankopedia.ts
@@ -100,6 +100,7 @@ interface Tankopedia {
disturbed: boolean;
revealed: boolean;
showControls: boolean;
+ animate: boolean;
protagonist: TankState;
antagonist: TankState;
@@ -131,6 +132,7 @@ export const Tankopedia = new Soapstone(
disturbed: false,
revealed: false,
showControls: false,
+ animate: true,
protagonist: createTankState(tank),
antagonist: createTankState(tank),