From 1416d64e34403c68123436c13ee949f7a40fd6d6 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 13 Oct 2026 20:37:53 -0500 Subject: [PATCH] add skills --- .../core/src/blitzkit/createDefaultSkills.ts | 5 +- .../src/blitzkit/tankDefinitions/constants.ts | 13 -- packages/core/src/config/index.ts | 1 + packages/core/src/config/skills.ts | 1 + packages/i18n/strings/en.json | 3 + .../src/components/Compare/CompareTable.tsx | 9 +- .../src/components/CrewSkillManager.tsx | 11 +- packages/website/src/components/Guesser.tsx | 6 +- .../website/src/components/ModuleManager.tsx | 18 +-- .../TankSearch/components/TierFilter.tsx | 4 +- .../components/TankSearchFilters/index.tsx | 6 +- .../components/Modules.tsx | 4 +- .../components/Skills.tsx | 3 +- .../HeroSection/components/Options/index.tsx | 6 +- .../Tankopedia/MetaSection/index.tsx | 4 +- .../TechTreeSection/components/Node.tsx | 4 +- .../TankopediaCharacteristics/index.tsx | 6 +- .../TankopediaLoadout/index.module.css | 25 ++++ .../components/TankopediaLoadout/index.tsx | 116 +++++++++++++++++- .../[...locale]/mixer/_index.tsx | 6 +- 20 files changed, 196 insertions(+), 55 deletions(-) create mode 100644 packages/core/src/config/skills.ts diff --git a/packages/core/src/blitzkit/createDefaultSkills.ts b/packages/core/src/blitzkit/createDefaultSkills.ts index b36611f90..f9d6d0f1b 100644 --- a/packages/core/src/blitzkit/createDefaultSkills.ts +++ b/packages/core/src/blitzkit/createDefaultSkills.ts @@ -1,12 +1,13 @@ -import { SkillDefinitions } from ".."; +import { SkillDefinitions } from "@blitzkit/protos"; import { permanentSkills } from "../blitz/permanentSkills"; +import { MAX_SKILL_LEVEL } from "../config/skills"; export function createDefaultSkills(skillDefinitions: SkillDefinitions) { const skills: Record = {}; Object.values(skillDefinitions.classes).forEach((tankSkills) => { tankSkills.skills.forEach((skill) => { - skills[skill] = permanentSkills.includes(skill) ? 7 : 0; + skills[skill] = permanentSkills.includes(skill) ? MAX_SKILL_LEVEL : 0; }); }); diff --git a/packages/core/src/blitzkit/tankDefinitions/constants.ts b/packages/core/src/blitzkit/tankDefinitions/constants.ts index a831bcdc9..13230094e 100644 --- a/packages/core/src/blitzkit/tankDefinitions/constants.ts +++ b/packages/core/src/blitzkit/tankDefinitions/constants.ts @@ -43,19 +43,6 @@ export const TANK_ICONS_COLLECTOR: Record = { [TankClass.TANK_CLASS_HEAVY]: "https://i.imgur.com/8xRf3nc.png", }; export const TIERS = times(10, (index) => index + 1); -export const TIER_ROMAN_NUMERALS: Record = { - 1: "I", - 2: "II", - 3: "III", - 4: "IV", - 5: "V", - 6: "VI", - 7: "VII", - 8: "VIII", - 9: "IX", - 10: "X", - 11: "XI", -}; export const flags: Record = { ussr: "<:ussr:1218421042033197197>", diff --git a/packages/core/src/config/index.ts b/packages/core/src/config/index.ts index c9bce2bce..7aa910d6e 100644 --- a/packages/core/src/config/index.ts +++ b/packages/core/src/config/index.ts @@ -1,3 +1,4 @@ export * from "./gunTypeOrder"; +export * from "./skills"; export * from "./tankClassOrder"; export * from "./treeTypeOrder"; diff --git a/packages/core/src/config/skills.ts b/packages/core/src/config/skills.ts new file mode 100644 index 000000000..36febccbf --- /dev/null +++ b/packages/core/src/config/skills.ts @@ -0,0 +1 @@ +export const MAX_SKILL_LEVEL = 7; diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json index 2c39a8494..e051cbd3b 100644 --- a/packages/i18n/strings/en.json +++ b/packages/i18n/strings/en.json @@ -143,6 +143,9 @@ "modules": "Modules", "equipment": "Equipment", "shells": "Shells", + "clear": "Clear", + "maximize": "Maximize", + "skills": "Crew skills", "alternative": "Alt" }, diff --git a/packages/website/src/components/Compare/CompareTable.tsx b/packages/website/src/components/Compare/CompareTable.tsx index 411054d8a..83cd10744 100644 --- a/packages/website/src/components/Compare/CompareTable.tsx +++ b/packages/website/src/components/Compare/CompareTable.tsx @@ -1,4 +1,9 @@ -import { alias, availableProvisions, permanentSkills } from "@blitzkit/core"; +import { + alias, + availableProvisions, + MAX_SKILL_LEVEL, + permanentSkills, +} from "@blitzkit/core"; import { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity"; import { ComponentPlaceholderIcon } from "@radix-ui/react-icons"; import { @@ -153,7 +158,7 @@ export function CompareTable({ stats }: CompareTableProps) { onClick={() => { CompareEphemeral.mutate((draft) => { Object.keys(draft.crewSkills).forEach((skill) => { - draft.crewSkills[skill] = 7; + draft.crewSkills[skill] = MAX_SKILL_LEVEL; }); }); }} diff --git a/packages/website/src/components/CrewSkillManager.tsx b/packages/website/src/components/CrewSkillManager.tsx index 1ddcd534f..8e66d99bb 100644 --- a/packages/website/src/components/CrewSkillManager.tsx +++ b/packages/website/src/components/CrewSkillManager.tsx @@ -1,4 +1,9 @@ -import { alias, permanentSkills, romanize } from "@blitzkit/core"; +import { + alias, + MAX_SKILL_LEVEL, + permanentSkills, + romanize, +} from "@blitzkit/core"; import { Flex } from "@radix-ui/themes"; import { api } from "../api/dynamic"; import { GenericTankComponentButton } from "./ModuleButtons/GenericTankComponentButton"; @@ -34,10 +39,10 @@ export function CrewSkillManager({ const draft = { ...skillLevels }; if (event.shiftKey) { - draft[skill] = draft[skill] === 0 ? 7 : 0; + draft[skill] = draft[skill] === 0 ? MAX_SKILL_LEVEL : 0; } else { draft[skill] = level - 1; - if (draft[skill] < 0) draft[skill] = 7; + if (draft[skill] < 0) draft[skill] = MAX_SKILL_LEVEL; } onChange(draft); diff --git a/packages/website/src/components/Guesser.tsx b/packages/website/src/components/Guesser.tsx index e028f5328..184f1cc20 100644 --- a/packages/website/src/components/Guesser.tsx +++ b/packages/website/src/components/Guesser.tsx @@ -1,4 +1,4 @@ -import { alias, SEARCH_KEYS, TIER_ROMAN_NUMERALS } from "@blitzkit/core"; +import { alias, romanize, SEARCH_KEYS } from "@blitzkit/core"; import { literals } from "@blitzkit/i18n"; import { TankDefinition } from "@blitzkit/protos"; import { @@ -163,7 +163,7 @@ export function Guesser() { justify="center" > - {TIER_ROMAN_NUMERALS[result.tier]} + {romanize(result.tier)} } /> @@ -215,7 +215,7 @@ export function Guesser() { }} > {literals(strings.website.tools.guess.tier, { - tier: TIER_ROMAN_NUMERALS[tier], + tier: romanize(tier), })} ); diff --git a/packages/website/src/components/ModuleManager.tsx b/packages/website/src/components/ModuleManager.tsx index 4f61531db..f186cd8b6 100644 --- a/packages/website/src/components/ModuleManager.tsx +++ b/packages/website/src/components/ModuleManager.tsx @@ -1,4 +1,4 @@ -import { TIER_ROMAN_NUMERALS } from "@blitzkit/core"; +import { romanize } from "@blitzkit/core"; import type { EngineDefinition, GunDefinition, @@ -42,7 +42,7 @@ export function ModuleManager({ setTurretMenuOpen(true)} /> @@ -71,7 +71,7 @@ export function ModuleManager({ {" "} {unwrap(turret.name!)} @@ -83,7 +83,7 @@ export function ModuleManager({ setGunMenuOpen(true)} /> @@ -111,7 +111,7 @@ export function ModuleManager({ {" "} {unwrap(gun.name!)} @@ -123,7 +123,7 @@ export function ModuleManager({ setEngineMenuOpen(true)} /> @@ -150,7 +150,7 @@ export function ModuleManager({ {" "} {unwrap(engine.name!)} @@ -162,7 +162,7 @@ export function ModuleManager({ setTrackMenuOpen(true)} /> @@ -189,7 +189,7 @@ export function ModuleManager({ {" "} {unwrap(track.name!)} diff --git a/packages/website/src/components/TankSearch/components/TierFilter.tsx b/packages/website/src/components/TankSearch/components/TierFilter.tsx index 7129ac418..910608bfc 100644 --- a/packages/website/src/components/TankSearch/components/TierFilter.tsx +++ b/packages/website/src/components/TankSearch/components/TierFilter.tsx @@ -1,4 +1,4 @@ -import { TIER_ROMAN_NUMERALS } from "@blitzkit/core"; +import { romanize } from "@blitzkit/core"; import { Flex, IconButton, Text } from "@radix-ui/themes"; import { memo } from "react"; import { awaitableTiers } from "../../../core/awaitables/tiers"; @@ -41,7 +41,7 @@ export const TierFilter = memo(() => { }); }} > - {TIER_ROMAN_NUMERALS[tier]} + {romanize(tier)} ); })} diff --git a/packages/website/src/components/TankSearchFilters/index.tsx b/packages/website/src/components/TankSearchFilters/index.tsx index 635c63c3b..029509870 100644 --- a/packages/website/src/components/TankSearchFilters/index.tsx +++ b/packages/website/src/components/TankSearchFilters/index.tsx @@ -1,7 +1,7 @@ import { alias, gunTypeOrder, - TIER_ROMAN_NUMERALS, + romanize, treeTypeOrder, } from "@blitzkit/core"; import { tankClassOrder } from "@blitzkit/core/src/config/tankClassOrder"; @@ -203,7 +203,7 @@ function TiersFilter() { {tiers.slice(0, MAX_ICONS).map((tier) => ( - {TIER_ROMAN_NUMERALS[tier]} + {romanize(tier)} ))} @@ -239,7 +239,7 @@ function TiersFilter() { key={tier} > {literals(strings.website.common.tank_search.tier, { - tier: TIER_ROMAN_NUMERALS[tier], + tier: romanize(tier), })} ); diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx index f43651195..b123944e2 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Modules.tsx @@ -2,7 +2,7 @@ import { alias, formatCompact, tankIcon, - TIER_ROMAN_NUMERALS, + romanize, } from "@blitzkit/core"; import { EngineDefinition, @@ -93,7 +93,7 @@ function ModuleButton({ top: isTank ? 8 : 4, }} > - {TIER_ROMAN_NUMERALS[tier]} + {romanize(tier)} {unlock.cost!.value > 0 && ( diff --git a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Skills.tsx b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Skills.tsx index 0e16fd790..8a82df99e 100644 --- a/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Skills.tsx +++ b/packages/website/src/components/Tankopedia/CharacteristicsSection/components/Skills.tsx @@ -1,3 +1,4 @@ +import { MAX_SKILL_LEVEL } from "@blitzkit/core"; import { InfoCircledIcon } from "@radix-ui/react-icons"; import { Button, @@ -59,7 +60,7 @@ export function Skills() { onClick={() => { Tankopedia.mutate((draft) => { Object.keys(draft.skills).forEach((skill) => { - draft.skills[skill] = 7; + draft.skills[skill] = MAX_SKILL_LEVEL; }); }); }} 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 65bee8a9e..372ae4582 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx @@ -1,7 +1,7 @@ import { alias, resolvePenetrationCoefficient, - TIER_ROMAN_NUMERALS, + romanize, uniqueGuns, } from "@blitzkit/core"; import { literals } from "@blitzkit/i18n/src/literals"; @@ -137,7 +137,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) { mr="2" mb="1" > - {TIER_ROMAN_NUMERALS[antagonistGun.tier]} + {romanize(antagonistGun.tier)} {literals(strings.common.units.mm, { diff --git a/packages/website/src/components/Tankopedia/MetaSection/index.tsx b/packages/website/src/components/Tankopedia/MetaSection/index.tsx index b2d80a902..01cff2c2b 100644 --- a/packages/website/src/components/Tankopedia/MetaSection/index.tsx +++ b/packages/website/src/components/Tankopedia/MetaSection/index.tsx @@ -1,4 +1,4 @@ -import { alias, TIER_ROMAN_NUMERALS } from "@blitzkit/core"; +import { alias, romanize } from "@blitzkit/core"; import { TankPriceType, TankType } from "@blitzkit/protos"; import { Box, Flex } from "@radix-ui/themes"; import { Var } from "../../../core/radix/var"; @@ -80,7 +80,7 @@ export function MetaSection() { - {TIER_ROMAN_NUMERALS[protagonist.tier]} + {romanize(protagonist.tier)} - {TIER_ROMAN_NUMERALS[tank.tier]} + {romanize(tank.tier)} {unwrap(tank.name!)} diff --git a/packages/website/src/components/TankopediaCharacteristics/index.tsx b/packages/website/src/components/TankopediaCharacteristics/index.tsx index a5cdca439..0d2574fd0 100644 --- a/packages/website/src/components/TankopediaCharacteristics/index.tsx +++ b/packages/website/src/components/TankopediaCharacteristics/index.tsx @@ -1,4 +1,4 @@ -import { TIER_ROMAN_NUMERALS } from "@blitzkit/core"; +import { romanize } from "@blitzkit/core"; import { literals } from "@blitzkit/i18n/src/literals"; import { useRef } from "react"; import { characteristicsGroups } from "../../config/characteristicsGroups"; @@ -201,7 +201,7 @@ function CompareOptions() { value: `${TankopediaRelativeAgainst.ClassAndTier}`, relativeAgainst: TankopediaRelativeAgainst.ClassAndTier, label: literals(strings.tanks.characteristics.compare.class, { - tier: TIER_ROMAN_NUMERALS[tank.tier], + tier: romanize(tank.tier), class: strings.common.tank_class_short_plural_lowercase[tank.class], }), }, @@ -209,7 +209,7 @@ function CompareOptions() { value: `${TankopediaRelativeAgainst.Tier}`, relativeAgainst: TankopediaRelativeAgainst.Tier, label: literals(strings.tanks.characteristics.compare.tier, { - tier: TIER_ROMAN_NUMERALS[tank.tier], + tier: romanize(tank.tier), }), }, { diff --git a/packages/website/src/components/TankopediaLoadout/index.module.css b/packages/website/src/components/TankopediaLoadout/index.module.css index 7543110f2..96d214b6f 100644 --- a/packages/website/src/components/TankopediaLoadout/index.module.css +++ b/packages/website/src/components/TankopediaLoadout/index.module.css @@ -13,6 +13,31 @@ .upgrade-buttons { display: flex; + gap: var(--space-2); + } + } + + .crew-skills { + display: flex; + flex-direction: column; + gap: var(--space-3); + + .row { + display: flex; + gap: inherit; + } + + .skill { + position: relative; + + .level { + position: absolute; + + right: var(--space-1); + bottom: var(--space-1); + + font-size: var(--font-size-minor); + } } } diff --git a/packages/website/src/components/TankopediaLoadout/index.tsx b/packages/website/src/components/TankopediaLoadout/index.tsx index 16ad5d472..708eb96ba 100644 --- a/packages/website/src/components/TankopediaLoadout/index.tsx +++ b/packages/website/src/components/TankopediaLoadout/index.tsx @@ -1,6 +1,16 @@ -import { alias } from "@blitzkit/core"; +import { + alias, + MAX_SKILL_LEVEL, + permanentSkills, + romanize, +} from "@blitzkit/core"; import type { Consumable, EquipmentSlot, Provision } from "@blitzkit/protos"; -import { ClockIcon, ResetIcon, TrashIcon } from "@radix-ui/react-icons"; +import { + ClockIcon, + DoubleArrowUpIcon, + ResetIcon, + TrashIcon, +} from "@radix-ui/react-icons"; import { chunk } from "lodash-es"; import { api } from "../../api/dynamic"; import { equipmentColumns } from "../../config/equipment"; @@ -23,6 +33,7 @@ import styles from "./index.module.css"; const equipment = await api.equipment(); const provisions = await api.provisions(); const consumables = await api.consumables(); +const skills = await api.skills(); interface TankopediaLoadoutProps { characteristics: ComputedCharacteristics; @@ -39,12 +50,103 @@ export function TankopediaLoadout({ characteristics }: TankopediaLoadoutProps) { + ); } +function CrewSkills() { + const strings = useStrings(); + const selectedSkills = Tankopedia.use((state) => state.environment.skills); + + return ( +
+
+ {strings.tanks.loadout.skills} + +
+ { + Tankopedia.mutate((draft) => { + Object.keys(draft.environment.skills).forEach((skill) => { + draft.environment.skills[skill] = 0; + }); + }); + }} + > + + + + { + Tankopedia.mutate((draft) => { + Object.keys(draft.environment.skills).forEach((skill) => { + draft.environment.skills[skill] = MAX_SKILL_LEVEL; + }); + }); + }} + > + + +
+
+ +
+ {Object.entries(skills.classes).map(([tankClass, group]) => ( +
+ {group.skills.map((skill) => ( + + ))} +
+ ))} +
+
+ ); +} + +interface CrewSkillEntryProps { + id: string; + level: number; + permanent: boolean; +} + +function CrewSkillEntry({ id, level, permanent }: CrewSkillEntryProps) { + return ( + { + Tankopedia.mutate((draft) => { + if (event.shiftKey) { + draft.environment.skills[id] = level === 0 ? MAX_SKILL_LEVEL : 0; + } else { + draft.environment.skills[id] = + level === 0 ? MAX_SKILL_LEVEL : level - 1; + } + }); + }} + > + + {level > 0 && {romanize(level)}} + + ); +} + function State() { const strings = useStrings(); @@ -58,6 +160,7 @@ function State() { { Tankopedia.mutate((draft) => { draft.protagonist.consumables = []; @@ -87,6 +190,7 @@ function Status() { { Tankopedia.mutate((draft) => { draft.protagonist.consumables = []; @@ -103,6 +207,8 @@ function Status() { {group.map((key) => ( { Tankopedia.mutate((draft) => { draft.protagonist.provisions = []; @@ -230,6 +337,7 @@ function ProvisionEntry({ id, provision }: ProvisionEntryProps) { return ( { Tankopedia.mutate((draft) => { draft.protagonist.consumables = []; @@ -325,6 +434,7 @@ function ConsumableEntry({ id, consumable }: ConsumableProps) {