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));
}