diff --git a/packages/closed b/packages/closed index 2d6977bb4..9a99ff408 160000 --- a/packages/closed +++ b/packages/closed @@ -1 +1 @@ -Subproject commit 2d6977bb4875026a9bb1d314b522d72bf3ef9a3d +Subproject commit 9a99ff408a7eed95900befcf7ce64d962185c1c5 diff --git a/packages/website-ue/src/api/abstract.ts b/packages/website-ue/src/api/abstract.ts index c9cc3516b..5c2db88f7 100644 --- a/packages/website-ue/src/api/abstract.ts +++ b/packages/website-ue/src/api/abstract.ts @@ -9,6 +9,7 @@ import type { Backgrounds } from "@protos/backgrounds"; import type { Consumables } from "@protos/consumables"; import type { Equipment } from "@protos/equipment"; import type { PopularTanks } from "@protos/popular_tanks"; +import type { Sets } from "@protos/sets"; import type { Tank } from "@protos/tank"; import type { TankList } from "@protos/tank_list"; import type { TankUpgradePresets } from "@protos/tank_upgrade_presets"; @@ -31,6 +32,7 @@ export abstract class AbstractAPI { abstract equipment(): Promise; abstract consumables(): Promise; abstract tierPrices(): Promise; + abstract sets(): Promise; abstract strings(locale: string): Promise; abstract gameStrings(locale: string): Promise>; diff --git a/packages/website-ue/src/api/client.ts b/packages/website-ue/src/api/client.ts index f6bd2f492..2de7e8e6c 100644 --- a/packages/website-ue/src/api/client.ts +++ b/packages/website-ue/src/api/client.ts @@ -4,6 +4,7 @@ import { Avatars } from "@protos/avatars"; import { Consumables } from "@protos/consumables"; import { Equipment } from "@protos/equipment"; import { PopularTanks } from "@protos/popular_tanks"; +import { Sets } from "@protos/sets"; import { TankList } from "@protos/tank_list"; import { TankUpgradePresets } from "@protos/tank_upgrade_presets"; import { Tanks } from "@protos/tanks"; @@ -71,6 +72,11 @@ export class ClientAPI extends AbstractAPI { return fetchPB("/api/tier-prices.pb", TierPrices); } + @Cache() + sets() { + return fetchPB("/api/sets.pb", Sets); + } + @Cache() async strings(locale: string) { const response = await fetch(`/api/strings/${locale}.json`); diff --git a/packages/website-ue/src/api/server.ts b/packages/website-ue/src/api/server.ts index ad6427f9b..32c7a6604 100644 --- a/packages/website-ue/src/api/server.ts +++ b/packages/website-ue/src/api/server.ts @@ -8,6 +8,7 @@ import type { RemoteStorageComponent } from "@protos/blitz_static_remote_storage import { Consumables } from "@protos/consumables"; import { Equipment } from "@protos/equipment"; import type { PopularTanks } from "@protos/popular_tanks"; +import { Sets } from "@protos/sets"; import type { Tank } from "@protos/tank"; import type { TankListEntry } from "@protos/tank_list"; import { TankUpgradePresets } from "@protos/tank_upgrade_presets"; @@ -294,6 +295,17 @@ export class ServerAPI extends AbstractAPI { return tierPrices; } + @Cache() + async sets() { + const sets = Sets.create(); + + for (const item of this.metadata.group("TankSetEntity")) { + sets.sets[item.id] = item.TankSetCatalog(); + } + + return sets; + } + @Cache(true) async strings(locale: string) { const localized = (await globbedStrings[ diff --git a/packages/website-ue/src/components/TankopediaLoadout/index.tsx b/packages/website-ue/src/components/TankopediaLoadout/index.tsx index 737e5f795..e85c99ca1 100644 --- a/packages/website-ue/src/components/TankopediaLoadout/index.tsx +++ b/packages/website-ue/src/components/TankopediaLoadout/index.tsx @@ -19,6 +19,7 @@ import { maxModulesPerRow, originalLineName, } from "../../config/modules"; +import { useCompatibility } from "../../hooks/useCompatibility"; import { useConsumables } from "../../hooks/useConsumables"; import { useEquipment } from "../../hooks/useEquipment"; import { useProtagonist } from "../../hooks/useProtagonist"; @@ -26,7 +27,6 @@ import { useStrings } from "../../hooks/useStrings"; import { useTierPrices } from "../../hooks/useTierPrices"; import { useUpgradePreset } from "../../hooks/useUpgradePreset"; import { Tankopedia } from "../../stores/tankopedia"; -import { isCompatible } from "../../tankopedia/isCompatible"; import { Button } from "../Button"; import { Heading } from "../Heading"; import { Link } from "../Link"; @@ -54,10 +54,12 @@ export function TankopediaLoadout() { function Consumables() { const consumables = useConsumables(); const tierPrices = useTierPrices(); + const tank = useProtagonist(); + const isCompatible = useCompatibility(); return Object.values(consumables.consumables).map( ({ compatibility, consumable }) => { - if (!isCompatible(compatibility!)) return null; + if (!isCompatible(compatibility!, tank)) return null; return {consumable!.name_key}; }, diff --git a/packages/website-ue/src/hooks/useCompatibility.ts b/packages/website-ue/src/hooks/useCompatibility.ts new file mode 100644 index 000000000..1fe804338 --- /dev/null +++ b/packages/website-ue/src/hooks/useCompatibility.ts @@ -0,0 +1,113 @@ +import type { CompatibilityComponent } from "@protos/blitz_static_compatibility_component"; +import type { TankCompatibility_Include } from "@protos/blitz_static_tank_compatibility"; +import { TankTraits } from "@protos/blitz_static_tank_compatibility"; +import { TankAttributeChange_AttributeName } from "@protos/blitz_static_tank_upgrade_single_stage"; +import type { Sets } from "@protos/sets"; +import type { Tank } from "@protos/tank"; +import { useSets } from "./useSets"; + +export function useCompatibility( + compatibility: CompatibilityComponent, + tank: Tank, +) { + const sets = useSets(); + + return function ( + { + tank_compatibility, + tank_set_compatibility, + game_type_compatibility, + quest_compatibility, + quest_slots_compatibility, + tank_style_compatibility, + }: CompatibilityComponent, + tank: Tank, + ) { + if ( + tank_set_compatibility || + game_type_compatibility || + quest_compatibility || + quest_slots_compatibility || + tank_style_compatibility + ) { + throw new Error("Not implemented"); + } + + if ( + tank_compatibility !== undefined && + (!tankCompatibilityApplies(tank_compatibility.include!, tank, sets) || + tankCompatibilityApplies(tank_compatibility.exclude!, tank, sets)) + ) { + return false; + } + + return true; + }; +} + +function tankCompatibilityApplies( + { + nations, + tiers, + tier_catalog_ids, + classes, + catalogs_id, + tank_traits, + types, + tank_sets_catalog_ids, + is_special, + tech_tags, + ...rest + }: TankCompatibility_Include, + tank: Tank, + sets: Sets, +) { + if (is_special) { + throw new Error("is_special Not implemented"); + } + + return ( + nations.some((nation) => nation === tank.tank!.nation) || + tiers.some((tier) => tier === tank.tank!.tier) || + tier_catalog_ids.some((tier) => tier === tank.tank!.tier_catalog_id) || + classes.some((_class) => _class === tank.tank!.tank_class) || + catalogs_id.some((id) => id === tank.id) || + tank_traits.some((trait) => tankHasTraits(trait, tank)) || + types.some((type) => type === tank.tank!.tank_type) || + tank_sets_catalog_ids.some((id) => + Object.entries(sets.sets).some( + ([setId, set]) => + setId === id && + set.tank_set_rewards.some((reward) => + reward.tank_set_reward_on_level!.reward_list.some( + (reward) => reward.tank_reward?.tank_catalog_id === tank.id, + ), + ), + ), + ) || + tech_tags.some((tag) => tank.tank!.tech_tags.includes(tag)) + ); +} + +function tankHasTraits(traits: TankTraits, tank: Tank) { + switch (traits) { + case TankTraits.TANK_TRAITS_HAS_CLIP: + // TODO: use current tank state to check if the current gun has a clip + + return tank.tank!.upgrade_lines.some((line) => + line.stages.some((stage) => + stage.attributes.every((attribute) => + attribute.attribute_name !== + TankAttributeChange_AttributeName.ATTRIBUTE_NAME_IS_PUMP && + attribute.attribute_name === + TankAttributeChange_AttributeName.ATTRIBUTE_NAME_CLIP_SIZE + ? attribute.value === 1 + : true, + ), + ), + ); + + default: + throw new Error(`Unknown trait: ${traits}`); + } +} diff --git a/packages/website-ue/src/hooks/useSets.ts b/packages/website-ue/src/hooks/useSets.ts new file mode 100644 index 000000000..e070699c0 --- /dev/null +++ b/packages/website-ue/src/hooks/useSets.ts @@ -0,0 +1,7 @@ +import { api } from "../api/dynamic"; +import { useAwait } from "./useAwait"; + +export function useSets() { + const sets = useAwait(() => api.sets(), "sets"); + return sets; +} diff --git a/packages/website-ue/src/pages/[api]/sets.pb.ts b/packages/website-ue/src/pages/[api]/sets.pb.ts new file mode 100644 index 000000000..c88120707 --- /dev/null +++ b/packages/website-ue/src/pages/[api]/sets.pb.ts @@ -0,0 +1,9 @@ +import { Sets } from "@protos/sets"; +import { api } from "../../api/dynamic"; + +export { getStaticPaths } from "./_index"; + +export async function GET() { + const sets = await api.sets(); + return new Response(Sets.encode(sets).finish()); +} diff --git a/packages/website-ue/src/tankopedia/isCompatible.ts b/packages/website-ue/src/tankopedia/isCompatible.ts deleted file mode 100644 index 707b89249..000000000 --- a/packages/website-ue/src/tankopedia/isCompatible.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { CompatibilityComponent } from "@protos/blitz_static_compatibility_component"; - -export function isCompatible(compatibility: CompatibilityComponent) { - console.log(compatibility); - - return true; -}