mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-11-01 06:59:51 +01:00
Fix: Unify Tankopedia model, camouflage and attachment loaders
- Tankopedia state uses `loaders: { model, skin, attachment }` with `requested` and `loaded`
- useSkin swaps the camouflage halfway through the lighting transition for both cached and downloaded skins
- Remove the leftover "Unreleased" camouflage name handling
This commit is contained in:
1 parent
523fee5010
commit
c772d47a07
8 files changed
+74
-83
No files matched your search
+3
-1
@@ -44,7 +44,9 @@ export function GunFlexibilityVisualizer({
|
||||
const tank = Duel.use((state) => state.protagonist.tank);
|
||||
const turret = Duel.use((state) => state.protagonist.turret);
|
||||
const gun = Duel.use((state) => state.protagonist.gun);
|
||||
const modelRequested = Tankopedia.use((state) => state.modelRequested);
|
||||
const modelRequested = Tankopedia.use(
|
||||
(state) => state.loaders.model.requested,
|
||||
);
|
||||
|
||||
const tankModel = modelDefinition.models[tank.id];
|
||||
const turretModel = tankModel.turrets[turret.id];
|
||||
|
||||
+19
-27
@@ -1,5 +1,5 @@
|
||||
import { alias } from "@blitzkit/core";
|
||||
import { Flex, Text, Tooltip } from "@radix-ui/themes";
|
||||
import { Flex, Tooltip } from "@radix-ui/themes";
|
||||
import { uniqBy } from "lodash-es";
|
||||
import { SKIN_NUMBER } from "../../../../../../constants/skinNumber";
|
||||
import { api } from "../../../../../../core/blitzkit/api";
|
||||
@@ -13,17 +13,18 @@ const camouflageDefinitions = await api.camouflageDefinitions();
|
||||
|
||||
export function SkinSwitcher() {
|
||||
const tank = Duel.use((state) => state.protagonist.tank);
|
||||
const skin = Tankopedia.use((state) => state.requestedSkin);
|
||||
const attachment = Tankopedia.use((state) => state.requestedAttachment);
|
||||
const skin = Tankopedia.use((state) => state.loaders.skin.requested);
|
||||
const attachment = Tankopedia.use(
|
||||
(state) => state.loaders.attachment.requested,
|
||||
);
|
||||
const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay);
|
||||
const disturbed = Tankopedia.use((state) => state.disturbed);
|
||||
const { unwrap, strings } = useLocale();
|
||||
const { unwrap } = useLocale();
|
||||
const skins = uniqBy(
|
||||
Object.values(camouflageDefinitions.camouflages).filter(
|
||||
(camouflage) => camouflage.tank_id === tank.id,
|
||||
),
|
||||
(camouflage) =>
|
||||
(camouflage.name && unwrap(camouflage.name)) || camouflage.id,
|
||||
(camouflage) => unwrap(camouflage.name!),
|
||||
);
|
||||
|
||||
// attachments aren't camouflages so they get their own group
|
||||
@@ -54,26 +55,16 @@ export function SkinSwitcher() {
|
||||
{groups.map((group) => (
|
||||
<Flex key={group[0].id} direction="column">
|
||||
{group.map((camouflage, index) => {
|
||||
const rawName = camouflage.name && unwrap(camouflage.name);
|
||||
const name = rawName
|
||||
? rawName.replace("%(camo_num)", SKIN_NUMBER)
|
||||
: strings.website.tools.tankopedia.sandbox.skins.unreleased;
|
||||
const name = unwrap(camouflage.name!).replace(
|
||||
"%(camo_num)",
|
||||
SKIN_NUMBER,
|
||||
);
|
||||
const top = index === 0 ? "var(--radius-full)" : "0";
|
||||
const bottom =
|
||||
index === group.length - 1 ? "var(--radius-full)" : "0";
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
key={camouflage.id}
|
||||
content={
|
||||
rawName ? (
|
||||
name
|
||||
) : (
|
||||
<Text style={{ color: "var(--orange-9)" }}>{name}</Text>
|
||||
)
|
||||
}
|
||||
side="left"
|
||||
>
|
||||
<Tooltip key={camouflage.id} content={name} side="left">
|
||||
<SidebarIconButton
|
||||
selected={
|
||||
(camouflage.attachment ? attachment : skin) ===
|
||||
@@ -92,13 +83,14 @@ export function SkinSwitcher() {
|
||||
borderRadius: "50%",
|
||||
}}
|
||||
onClick={() => {
|
||||
const slot = camouflage.attachment
|
||||
? "requestedAttachment"
|
||||
: "requestedSkin";
|
||||
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft[slot] =
|
||||
draft[slot] === camouflage.id
|
||||
const loader =
|
||||
draft.loaders[
|
||||
camouflage.attachment ? "attachment" : "skin"
|
||||
];
|
||||
|
||||
loader.requested =
|
||||
loader.requested === camouflage.id
|
||||
? undefined
|
||||
: camouflage.id;
|
||||
});
|
||||
|
||||
@@ -82,7 +82,9 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
const revealed = Tankopedia.use((state) => state.revealed);
|
||||
const disturbed = Tankopedia.use((state) => state.disturbed);
|
||||
const highGraphics = TankopediaPersistent.use((state) => state.highGraphics);
|
||||
const modelRequested = Tankopedia.use((state) => state.modelRequested);
|
||||
const modelRequested = Tankopedia.use(
|
||||
(state) => state.loaders.model.requested,
|
||||
);
|
||||
const equalize = Duel.use((state) => state.equalize);
|
||||
const equalizer = Duel.use((state) => state.antagonist.tank.equalizer);
|
||||
|
||||
|
||||
+6
-6
@@ -13,12 +13,14 @@ const camouflageDefinitions = await api.camouflageDefinitions();
|
||||
|
||||
export function Title({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
const { unwrap, strings } = useLocale();
|
||||
const modelRequested = Tankopedia.use((state) => state.modelRequested);
|
||||
const modelRequested = Tankopedia.use(
|
||||
(state) => state.loaders.model.requested,
|
||||
);
|
||||
const protagonist = Duel.use((state) => state.protagonist.tank);
|
||||
const revealed = Tankopedia.use((state) => state.revealed);
|
||||
const disturbed = Tankopedia.use((state) => state.disturbed);
|
||||
const hasRevealed = useRef(false);
|
||||
const skin = Tankopedia.use((state) => state.requestedSkin);
|
||||
const skin = Tankopedia.use((state) => state.loaders.skin.requested);
|
||||
const camouflage =
|
||||
skin === undefined ? undefined : camouflageDefinitions.camouflages[skin];
|
||||
const tankName = unwrap(protagonist.name!);
|
||||
@@ -27,9 +29,7 @@ export function Title({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
camouflage
|
||||
? camouflage.tank_name
|
||||
? unwrap(camouflage.tank_name)
|
||||
: camouflage.name
|
||||
? `${tankName} ${unwrap(camouflage.name)}`
|
||||
: tankName
|
||||
: `${tankName} ${unwrap(camouflage.name!)}`
|
||||
: tankName
|
||||
).replace("%(camo_num)", SKIN_NUMBER);
|
||||
const fontSize = revealed
|
||||
@@ -109,7 +109,7 @@ export function Title({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
onClick={() => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.modelRequested = true;
|
||||
draft.loaders.model.requested = true;
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
||||
+3
-13
@@ -49,8 +49,8 @@ export function Lighting({ hasPbr }: Props) {
|
||||
const revealed = Tankopedia.use((state) => state.revealed);
|
||||
const skinPending = Tankopedia.use(
|
||||
(state) =>
|
||||
state.loadedSkin !== state.skin ||
|
||||
state.loadedAttachment !== state.attachment,
|
||||
state.loaders.skin.requested !== state.loaders.skin.loaded ||
|
||||
state.loaders.attachment.requested !== state.loaders.attachment.loaded,
|
||||
);
|
||||
|
||||
const animationTime = useRef(REVEAL_ANIMATION_TIME);
|
||||
@@ -65,6 +65,7 @@ export function Lighting({ hasPbr }: Props) {
|
||||
useEffect(() => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.revealed = true;
|
||||
draft.loaders.model.loaded = true;
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -246,17 +247,6 @@ function Animator({
|
||||
draft.display = requestedDisplay;
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
x >= 1 &&
|
||||
(Tankopedia.state.skin !== Tankopedia.state.loadedSkin ||
|
||||
Tankopedia.state.attachment !== Tankopedia.state.loadedAttachment)
|
||||
) {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.skin = draft.loadedSkin;
|
||||
draft.attachment = draft.loadedAttachment;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return null;
|
||||
|
||||
@@ -20,7 +20,9 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
const canvas = useRef<HTMLCanvasElement>(null!);
|
||||
const isFullScreen = useFullScreen();
|
||||
const protagonist = Duel.use((state) => state.protagonist.tank);
|
||||
const modelRequested = Tankopedia.use((state) => state.modelRequested);
|
||||
const modelRequested = Tankopedia.use(
|
||||
(state) => state.loaders.model.requested,
|
||||
);
|
||||
const thicknessRange = useMemo(() => {
|
||||
const entries = Object.values(tankDefinitions.tanks);
|
||||
const filtered = entries.filter(
|
||||
|
||||
@@ -35,36 +35,28 @@ function skinPath(id: number, skin: number) {
|
||||
}
|
||||
|
||||
// attachments load the same way as camouflages and can be shown with one
|
||||
const slots = {
|
||||
skin: { requested: "requestedSkin", loaded: "loadedSkin" },
|
||||
attachment: { requested: "requestedAttachment", loaded: "loadedAttachment" },
|
||||
} as const;
|
||||
|
||||
export function useSkin(id: number, slot: keyof typeof slots = "skin") {
|
||||
const { requested, loaded } = slots[slot];
|
||||
const requestedSkin = Tankopedia.use((state) => state[requested]);
|
||||
const skin = Tankopedia.use((state) => state[slot]);
|
||||
export function useSkin(id: number, loader: "skin" | "attachment" = "skin") {
|
||||
const requested = Tankopedia.use((state) => state.loaders[loader].requested);
|
||||
const loaded = Tankopedia.use((state) => state.loaders[loader].loaded);
|
||||
|
||||
useEffect(() => {
|
||||
const path =
|
||||
requestedSkin === undefined ? undefined : skinPath(id, requestedSkin);
|
||||
if (requested === Tankopedia.state.loaders[loader].loaded) return;
|
||||
|
||||
const path = requested === undefined ? undefined : skinPath(id, requested);
|
||||
const cached = path === undefined || loadedSkins.has(path);
|
||||
let settled = false;
|
||||
|
||||
if (path === undefined || loadedSkins.has(path)) {
|
||||
if (!cached) {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft[loaded] = requestedSkin;
|
||||
draft.revealed = false;
|
||||
});
|
||||
return;
|
||||
|
||||
if (!skins.has(path)) skins.set(path, loadSkin(path));
|
||||
}
|
||||
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.revealed = false;
|
||||
});
|
||||
|
||||
if (!skins.has(path)) skins.set(path, loadSkin(path));
|
||||
|
||||
// swaps halfway through the lighting transition
|
||||
Promise.all([
|
||||
skins.get(path),
|
||||
path && skins.get(path),
|
||||
new Promise((resolve) =>
|
||||
setTimeout(resolve, TRANSITION_ANIMATION_TIME * 1e3),
|
||||
),
|
||||
@@ -73,9 +65,8 @@ export function useSkin(id: number, slot: keyof typeof slots = "skin") {
|
||||
|
||||
settled = true;
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft[loaded] = requestedSkin;
|
||||
draft[slot] = requestedSkin;
|
||||
draft.revealed = true;
|
||||
draft.loaders[loader].loaded = requested;
|
||||
if (!cached) draft.revealed = true;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -83,11 +74,16 @@ export function useSkin(id: number, slot: keyof typeof slots = "skin") {
|
||||
if (settled) return;
|
||||
|
||||
settled = true;
|
||||
|
||||
if (cached) return;
|
||||
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.revealed = true;
|
||||
});
|
||||
};
|
||||
}, [id, slot, requestedSkin]);
|
||||
}, [id, loader, requested]);
|
||||
|
||||
return skin === undefined ? undefined : loadedSkins.get(skinPath(id, skin));
|
||||
return loaded === undefined
|
||||
? undefined
|
||||
: loadedSkins.get(skinPath(id, loaded));
|
||||
}
|
||||
@@ -78,10 +78,19 @@ export enum TankopediaRelativeAgainst {
|
||||
All,
|
||||
}
|
||||
|
||||
interface LoaderState<Type> {
|
||||
requested: Type;
|
||||
loaded: Type;
|
||||
}
|
||||
|
||||
interface Tankopedia {
|
||||
disturbed: boolean;
|
||||
revealed: boolean;
|
||||
modelRequested: boolean;
|
||||
loaders: {
|
||||
model: LoaderState<boolean>;
|
||||
skin: LoaderState<number | undefined>;
|
||||
attachment: LoaderState<number | undefined>;
|
||||
};
|
||||
shot?: Shot;
|
||||
skills: Record<string, number>;
|
||||
relativeAgainst: TankopediaRelativeAgainst;
|
||||
@@ -103,12 +112,6 @@ interface Tankopedia {
|
||||
);
|
||||
xpMultiplier: (typeof XP_MULTIPLIERS)[number];
|
||||
customShell?: ShellDefinition;
|
||||
requestedSkin?: number;
|
||||
loadedSkin?: number;
|
||||
skin?: number;
|
||||
requestedAttachment?: number;
|
||||
loadedAttachment?: number;
|
||||
attachment?: number;
|
||||
requestedDisplay: TankopediaDisplay;
|
||||
display: TankopediaDisplay;
|
||||
statSearch?: string;
|
||||
@@ -119,7 +122,11 @@ const skillDefinitions = await api.skillDefinitions();
|
||||
export const Tankopedia = new Varuna<Tankopedia, ModelDefinition>((model) => ({
|
||||
disturbed: false,
|
||||
revealed: false,
|
||||
modelRequested: App.state.autoLoadModels,
|
||||
loaders: {
|
||||
model: { requested: App.state.autoLoadModels, loaded: false },
|
||||
skin: { requested: undefined, loaded: undefined },
|
||||
attachment: { requested: undefined, loaded: undefined },
|
||||
},
|
||||
relativeAgainst: TankopediaRelativeAgainst.Class,
|
||||
editStatic: false,
|
||||
skills: createDefaultSkills(skillDefinitions),
|
||||
|
||||
Reference in new issue
Block a user