diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json index 5985a405a..bcafe5e9f 100644 --- a/packages/i18n/strings/en.json +++ b/packages/i18n/strings/en.json @@ -83,6 +83,7 @@ "dynamics": "Dynamics", "status": "Status", "consumables": "Consumables", + "provisions": "Provisions", "modules": "Modules", "equipment": "Equipment", "alternative": "Alt" diff --git a/packages/website/src/components/Button/index.module.css b/packages/website/src/components/Button/index.module.css index f42a976c4..51f97e1e1 100644 --- a/packages/website/src/components/Button/index.module.css +++ b/packages/website/src/components/Button/index.module.css @@ -21,6 +21,10 @@ padding: var(--space-2) var(--space-3); } + &[data-size="major"] { + font-size: 2em; + } + &[data-list-child="true"]:not(:first-child), *:not(:first-child) > &[data-parent-list-child="true"] { border-top-left-radius: 0; diff --git a/packages/website/src/components/Button/index.tsx b/packages/website/src/components/Button/index.tsx index 056844e5f..e2a25188f 100644 --- a/packages/website/src/components/Button/index.tsx +++ b/packages/website/src/components/Button/index.tsx @@ -7,7 +7,7 @@ export interface ButtonProps extends ComponentProps<"button"> { color?: Color; radius?: `${1 | 2 | 3}` | "max"; variant?: "solid" | "surface" | "soft" | "inverted" | "ghost" | "outline"; - size?: "regular" | "minor"; + size?: "regular" | "minor" | "major"; highContrast?: boolean; // TODO: add high contrast listChild?: boolean; diff --git a/packages/website/src/components/TankopediaLoadout/index.module.css b/packages/website/src/components/TankopediaLoadout/index.module.css index 04a0e04a1..1881479be 100644 --- a/packages/website/src/components/TankopediaLoadout/index.module.css +++ b/packages/website/src/components/TankopediaLoadout/index.module.css @@ -1,6 +1,9 @@ .section { + max-width: 21rem; + display: flex; flex-direction: column; + align-items: flex-start; gap: var(--space-4); .header { @@ -41,9 +44,15 @@ } } + .provisions { + display: flex; + flex-wrap: wrap; + gap: var(--space-3); + } + .consumables { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); + display: flex; + flex-wrap: wrap; gap: var(--space-3); .consumable-wrapper { @@ -100,8 +109,6 @@ aspect-ratio: 1 / 1; padding: var(--space-3); - font-size: var(--space-5); - &[data-selected="true"] { z-index: 1; } diff --git a/packages/website/src/components/TankopediaLoadout/index.tsx b/packages/website/src/components/TankopediaLoadout/index.tsx index a7d727777..f0b0c6597 100644 --- a/packages/website/src/components/TankopediaLoadout/index.tsx +++ b/packages/website/src/components/TankopediaLoadout/index.tsx @@ -1,12 +1,12 @@ import { alias } from "@blitzkit/core"; -import type { Consumable, EquipmentSlot } from "@blitzkit/protos"; +import type { Consumable, EquipmentSlot, Provision } from "@blitzkit/protos"; import { ClockIcon, ResetIcon, TrashIcon } from "@radix-ui/react-icons"; import { chunk } from "lodash-es"; import { api } from "../../api/dynamic"; import { equipmentColumns } from "../../config/equipment"; -import { useConsumables } from "../../hooks/useConsumables"; import { DuelModule, useDuel } from "../../hooks/useDuel"; import { useEquipmentPreset } from "../../hooks/useEquipmentPreset"; +import { useProvision } from "../../hooks/useProvision"; import { useStrings } from "../../hooks/useStrings"; import { useTankCompatibility } from "../../hooks/useTankCompatibility"; import { useUnwrapper } from "../../hooks/useUnwrapper"; @@ -22,6 +22,8 @@ import { Tooltip } from "../Tooltip"; import styles from "./index.module.css"; const equipment = await api.equipment(); +const provisions = await api.provisions(); +const consumables = await api.consumables(); interface TankopediaLoadoutProps { characteristics: ComputedCharacteristics; @@ -35,6 +37,7 @@ export function TankopediaLoadout({ characteristics }: TankopediaLoadoutProps) { <> {/* {showModules && } */} + @@ -122,13 +125,88 @@ function Status() { ); } +function Provisions() { + const strings = useStrings(); + const tank = useDuel("protagonist", DuelModule.Tank); + const gun = useDuel("protagonist", DuelModule.Gun); + const isCompatible = useTankCompatibility(tank, gun); + + return ( +
+
+ {strings.tanks.loadout.provisions} + + { + Tankopedia.mutate((draft) => { + draft.protagonist.provisions = []; + }); + }} + > + + +
+ +
+ {Object.entries(provisions.provisions).map(([id, provision]) => { + if ( + provision.game_mode_exclusive || + !isCompatible(provision.include, provision.exclude) + ) { + return null; + } + + return ( + + ); + })} +
+
+ ); +} + +interface ProvisionEntryProps { + id: number; + provision: Provision; +} + +function ProvisionEntry({ id, provision }: ProvisionEntryProps) { + const isSelected = useProvision("protagonist", id); + const unwrap = useUnwrapper(); + + return ( + + { + Tankopedia.mutate((draft) => { + const index = draft.protagonist.provisions.indexOf(id); + + if (index === -1) { + draft.protagonist.provisions.push(id); + } else { + draft.protagonist.provisions.splice(index, 1); + } + }); + }} + > + + + + ); +} + interface ConsumablesProps { characteristics: ComputedCharacteristics; } function Consumables({ characteristics }: ConsumablesProps) { const strings = useStrings(); - const consumables = useConsumables(); const tank = useDuel("protagonist", DuelModule.Tank); const gun = useDuel("protagonist", DuelModule.Gun); const isCompatible = useTankCompatibility(tank, gun); @@ -152,7 +230,10 @@ function Consumables({ characteristics }: ConsumablesProps) {
{Object.entries(consumables.consumables).map(([id, consumable]) => { - if (!isCompatible(consumable.include, consumable.exclude)) { + if ( + consumable.game_mode_exclusive || + !isCompatible(consumable.include, consumable.exclude) + ) { return null; } @@ -174,7 +255,6 @@ function ConsumableEntry({ id, consumable }: ConsumableProps) { const isSelected = Tankopedia.use((state) => state.protagonist.consumables.includes(id), ); - const tank = useDuel("protagonist", DuelModule.Tank); const unwrap = useUnwrapper(); return ( @@ -338,6 +418,7 @@ function EquipmentOption({ return ( { coefficient *= (effect.minDamageFactor + effect.maxDamageFactor) / 2; }); + // reactive armor + script("antagonist", "ShieldKit", (effect) => { + if (shell.type === ShellType.SHELL_TYPE_HE) return; + coefficient *= 1 - effect.shieldCoef; + }); + + // dynamic armour system + script("antagonist", "ArmorMover", (effect) => { + coefficient *= 1 - effect.bonusValues!.damageReductionPercent / 100; + }); + + // spall liner + script("antagonist", "AntiHighExplosive", (effect) => { + if (shell.type !== ShellType.SHELL_TYPE_HE) return; + coefficient *= effect.bonusValues!.factor; + }); + return coefficient * shell.armor_damage; }, }, @@ -46,19 +64,11 @@ export const characteristics = { compute({ shell, script }) { let coefficient = 1; + // tungsten shells script("protagonist", "TungstenTip", (effect) => { coefficient *= (effect.minDamageFactor + effect.maxDamageFactor) / 2; }); - script("antagonist", "ShieldKit", (effect) => { - if (shell.type === ShellType.SHELL_TYPE_HE) return; - coefficient *= 1 - effect.shieldCoef; - }); - - script("antagonist", "ArmorMover", (effect) => { - coefficient *= 1 - effect.bonusValues!.damageReductionPercent / 100; - }); - return coefficient * shell.module_damage; }, }, diff --git a/packages/website/src/core/blitzkit/tankCharacteristics.ts b/packages/website/src/core/blitzkit/tankCharacteristics.ts index ceba0146c..31596587d 100644 --- a/packages/website/src/core/blitzkit/tankCharacteristics.ts +++ b/packages/website/src/core/blitzkit/tankCharacteristics.ts @@ -11,7 +11,6 @@ import { import { coefficient } from "@blitzkit/core/src/blitzkit/coefficient"; import { CrewType, - ShellType, TankClass, type EngineDefinition, type EquipmentDefinitions, @@ -24,7 +23,6 @@ import { type TurretDefinition, } from "@blitzkit/protos"; import type { EquipmentMatrix } from "../../stores/duel"; -import { SPALL_LINER_HE_DAMAGE_DELTA } from "./spallLiner"; import { defaultEqualizer } from "./tankToDuelMember"; export type TankCharacteristics = ReturnType; @@ -168,10 +166,6 @@ export function tankCharacteristics( const intraClipCoefficient = coefficient([hasShellReloadBoost, -0.3]); const burstShells = gun.burst?.count ?? 1; const burstInterShell = gun.burst?.interval; - const armorDamageCoefficient = coefficient([ - applySpallLiner && shell.type === ShellType.SHELL_TYPE_HE, - SPALL_LINER_HE_DAMAGE_DELTA, - ]); const assaultDamageCoefficient = gun.assault_ranges && gun.assault_ranges.types.includes(shell.type) ? (gun.assault_ranges.ranges.find( diff --git a/packages/website/src/hooks/useCharacteristics.ts b/packages/website/src/hooks/useCharacteristics.ts index b904bbb06..aa1d9d291 100644 --- a/packages/website/src/hooks/useCharacteristics.ts +++ b/packages/website/src/hooks/useCharacteristics.ts @@ -59,6 +59,13 @@ export function computeCharacteristics( scriptsMap.protagonist.set(script["#text"], script); } + for (const id of states[side].provisions) { + if (!(id in scripts.provisions)) continue; + + const script = scripts.provisions[id]; + scriptsMap.protagonist.set(script["#text"], script); + } + for (const index in states[side].equipment) { const choice = states[side].equipment[index]; const id = equipmentPreset.slots[index].options[choice]; diff --git a/packages/website/src/hooks/useConsumables.ts b/packages/website/src/hooks/useConsumables.ts deleted file mode 100644 index 175fe4751..000000000 --- a/packages/website/src/hooks/useConsumables.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { api } from "../api/dynamic"; -import { useAwait } from "./useAwait"; - -export function useConsumables() { - const consumables = useAwait(() => api.consumables(), "consumables"); - return consumables; -} diff --git a/packages/website/src/hooks/useProvision.ts b/packages/website/src/hooks/useProvision.ts index 054664f7e..5ed210504 100644 --- a/packages/website/src/hooks/useProvision.ts +++ b/packages/website/src/hooks/useProvision.ts @@ -1,6 +1,6 @@ -import { Duel } from '../stores/duel'; +import { Tankopedia } from "../stores/tankopedia"; +import type { DuelSide } from "./useEquipment"; -export function useProvision(id: number) { - const provisions = Duel.use((state) => state.protagonist.provisions); - return provisions.includes(id); +export function useProvision(side: DuelSide, id: number) { + return Tankopedia.use((state) => state[side].provisions.includes(id)); }