diff --git a/scripts/buildAssets/definitions.ts b/scripts/buildAssets/definitions.ts
index 6f737f4f7..89af27e07 100644
--- a/scripts/buildAssets/definitions.ts
+++ b/scripts/buildAssets/definitions.ts
@@ -453,6 +453,36 @@ export async function definitions(production: boolean) {
`${DATA}/${POI.vehicleDefinitions}/${nation}/components/chassis.xml.dvpl`,
);
+ function resolveUnlocks(unlocks?: Unlocks) {
+ if (!unlocks) return undefined;
+
+ return Object.entries(unlocks)
+ .map(([type, list]) =>
+ (Array.isArray(list) ? list : [list]).map((item) => {
+ const typeTyped = type as keyof Unlocks;
+ let rawId: number;
+
+ if (typeTyped === 'chassis') {
+ rawId = chassisList.root.ids[item['#text']];
+ } else if (typeTyped === 'engine') {
+ rawId = enginesList.root.ids[item['#text']];
+ } else if (typeTyped === 'gun') {
+ rawId = gunList.root.ids[item['#text']];
+ } else if (typeTyped === 'turret') {
+ rawId = turretList.root.ids[item['#text']];
+ } else if (typeTyped === 'vehicle') {
+ rawId = tankList.root[item['#text']].id;
+ }
+
+ return {
+ type: typeTyped,
+ id: toUniqueId(nation, rawId!),
+ };
+ }),
+ )
+ .flat();
+ }
+
for (const tankKey in tankList.root) {
if (botPattern.test(tankKey)) continue;
@@ -626,6 +656,7 @@ export async function definitions(production: boolean) {
soft: terrainResistances[2],
},
tier: track.level as Tier,
+ unlocks: resolveUnlocks(track.unlocks),
});
modelDefinitions[tankId].tracks[trackId] = {
@@ -650,6 +681,7 @@ export async function definitions(production: boolean) {
tier: engineListEntry.level as Tier,
weight: engineListEntry.weight,
power: engineListEntry.power,
+ unlocks: resolveUnlocks(engine.unlocks),
});
});
@@ -711,6 +743,7 @@ export async function definitions(production: boolean) {
health: turret.maxHealth,
viewRange: turret.circularVisionRadius,
weight: turret.weight,
+ unlocks: resolveUnlocks(turret.unlocks),
});
modelDefinitions[tankId].turrets[turretId] = {
@@ -829,6 +862,7 @@ export async function definitions(production: boolean) {
shot: shotDispersionFactors.afterShot,
traverse: shotDispersionFactors.turretRotation,
},
+ unlocks: resolveUnlocks(gun.unlocks),
} as GunDefinition);
modelDefinitions[tankId].turrets[turretId].guns[gunId] = {
diff --git a/src/app/tools/tankopedia/[id]/components/Model/ModulesSection.tsx b/src/app/tools/tankopedia/[id]/components/Model/ModulesSection.tsx
new file mode 100644
index 000000000..de04ef3c7
--- /dev/null
+++ b/src/app/tools/tankopedia/[id]/components/Model/ModulesSection.tsx
@@ -0,0 +1,73 @@
+import { Heading } from '@radix-ui/themes';
+import { use } from 'react';
+import PageWrapper from '../../../../../../components/PageWrapper';
+import {
+ EngineDefinition,
+ GunDefinition,
+ ModuleType,
+ tankDefinitions,
+ TrackDefinition,
+ TurretDefinition,
+ Unlock,
+} from '../../../../../../core/blitzkit/tankDefinitions';
+import { useDuel } from '../../../../../../stores/duel';
+
+export function ModulesSection() {
+ const awaitedTankDefinitions = use(tankDefinitions);
+ const tank = useDuel((state) => state.protagonist!.tank);
+
+ function tree(type: ModuleType, unlocks: Unlock[]) {
+ return (
+
+ {unlocks.map((unlock) => {
+ let module:
+ | TrackDefinition
+ | EngineDefinition
+ | TurretDefinition
+ | GunDefinition
+ | undefined = undefined;
+
+ if (unlock.type === 'chassis') {
+ module = tank.tracks.find((track) => track.id === unlock.id)!;
+ } else if (unlock.type === 'engine') {
+ module = tank.engines.find((engine) => engine.id === unlock.id)!;
+ } else if (unlock.type === 'turret') {
+ module = tank.turrets.find((turret) => turret.id === unlock.id)!;
+ } else if (unlock.type === 'gun') {
+ module = tank.turrets
+ .find((turret) =>
+ turret.guns.some((gun) => gun.id === unlock.id),
+ )!
+ .guns.find((gun) => gun.id === unlock.id)!;
+ }
+
+ if (module === undefined) return null;
+
+ return (
+ -
+ {unlock.type} {module.name}
+ {module.unlocks && tree(type, module.unlocks)}
+
+ );
+ })}
+
+ );
+ }
+
+ return (
+
+ turret0
+ {tank.turrets[0].unlocks && tree('turret', tank.turrets[0].unlocks)}
+
+ gun0
+ {tank.turrets[0].guns[0].unlocks &&
+ tree('gun', tank.turrets[0].guns[0].unlocks)}
+
+ engine0
+ {tank.engines[0].unlocks && tree('engine', tank.engines[0].unlocks)}
+
+ track0
+ {tank.tracks[0].unlocks && tree('chassis', tank.tracks[0].unlocks)}
+
+ );
+}
diff --git a/src/app/tools/tankopedia/[id]/page.tsx b/src/app/tools/tankopedia/[id]/page.tsx
index 8b111e1ff..9b0965cd4 100644
--- a/src/app/tools/tankopedia/[id]/page.tsx
+++ b/src/app/tools/tankopedia/[id]/page.tsx
@@ -7,6 +7,7 @@ import { mutateDuel, useDuel } from '../../../../stores/duel';
import { mutateTankopediaTemporary } from '../../../../stores/tankopedia';
import { HeroSection } from './components/Model/HeroSection';
import { HistoricalSection } from './components/Model/HistoricalSection';
+import { ModulesSection } from './components/Model/ModulesSection';
import { TechTreeSection } from './components/Model/TechTreeSection';
import { Videos } from './components/Videos';
@@ -62,6 +63,7 @@ export default function Page({ params }: { params: { id: string } }) {
{/* */}
+
{/*
*/}
diff --git a/src/core/blitzkit/tankDefinitions/index.ts b/src/core/blitzkit/tankDefinitions/index.ts
index 551570587..1df044345 100644
--- a/src/core/blitzkit/tankDefinitions/index.ts
+++ b/src/core/blitzkit/tankDefinitions/index.ts
@@ -14,6 +14,11 @@ export type CrewMember =
| 'driver'
| 'loader';
export type TankDefinitions = Record;
+export type ModuleType = 'vehicle' | 'engine' | 'chassis' | 'turret' | 'gun';
+export interface Unlock {
+ type: ModuleType;
+ id: number;
+}
export interface Crew {
type: CrewMember;
count?: number;
@@ -69,6 +74,7 @@ export interface TrackDefinition {
medium: number;
soft: number;
};
+ unlocks?: Unlock[];
}
export interface EngineDefinition {
id: number;
@@ -78,6 +84,7 @@ export interface EngineDefinition {
fireChance: number;
power: number;
weight: number;
+ unlocks?: Unlock[];
}
export type TankDefinitionPrice =
| { type: 'credits'; value: number }
@@ -92,6 +99,7 @@ export interface TurretDefinition {
tier: Tier;
guns: GunDefinition[];
weight: number;
+ unlocks?: Unlock[];
}
export type GunDefinition =
| GunDefinitionRegular
@@ -113,6 +121,7 @@ interface GunDefinitionBase {
shot: number;
damaged: number;
};
+ unlocks?: Unlock[];
}
interface GunDefinitionRegular extends GunDefinitionBase {
type: 'regular';