Merge branch 'main' into l10n_main

This commit is contained in:
tresabhi committed 2024-12-16 10:44:05 -06:00
commit 75d4d60b30
19 files changed
+338 -144

No files matched your search

@@ -1,8 +1,7 @@
name: Build Website
name: Website
on:
push:
branches: main
workflow_dispatch:
schedule:
@@ -15,7 +14,7 @@ permissions:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
cancel-in-progress: ${{ github.ref == 'refs/heads/main' }}
env:
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
@@ -52,14 +51,41 @@ jobs:
with:
path: dist/website
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
needs: build
deploy-github-pages:
if: github.ref == 'refs/heads/main'
name: Deploy to GitHub Pages
runs-on: ubuntu-latest
name: Deploy
needs: build
environment:
name: Production
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
deploy-vercel:
name: Deploy to Vercel
needs: build
runs-on: ubuntu-latest
environment:
name: Preview
steps:
# vercel begs for the .git dir so here it is my guy
- uses: actions/checkout@v4
- name: Download artifact
uses: actions/download-artifact@v4
with:
path: ./dist/website
- uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
github-token: ${{ secrets.GITHUB_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID}}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
working-directory: ./dist/website
+1
View File
@@ -26,3 +26,4 @@ target
# !packages/core/src/protos/*.proto
/.vercel
/.astro
.vercel
+1
View File
@@ -189,6 +189,7 @@
"unpickled",
"Unregistering",
"unrotate",
"vercel",
"vickers",
"vitepress",
"Vovko",
+15
View File
@@ -0,0 +1,15 @@
# BlitzKit 1.26.4
## Features
- `/auction` now warns if a tank stock is at or below 500 and now strikes through the tank name if its out
- Added advanced armor highlighting mode
- Green: penetration
- Red: blocked
- Pink: ricochet
- Cyan: penetration with 3 calibers rule
- Optimized the armor highlighting for about a 15% increase in frame rate
## Fixes
- Added 5000 doses of adrenaline, incase the BOT DECIDES TO DIE AGAIN FOR NO REASON
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "blitzkit",
"private": true,
"version": "1.26.3",
"version": "1.26.4",
"description": "🎉 Tools for everything World of Tanks Blitz",
"type": "module",
"types": "./types.d.ts",
+18 -10
View File
@@ -27,7 +27,7 @@ type Result =
available_from?: Date;
available_before?: Date;
price?: Price;
next_price?: Price | null;
next_price: Price | null;
available: true;
display?: boolean;
next_price_datetime: Date;
@@ -68,6 +68,8 @@ interface Currency {
type: 'currency';
}
const WARNING_COUNT = 500;
export const auctionCommand = new Promise<CommandRegistry>((resolve) => {
resolve({
command: createLocalizedCommand('auction'),
@@ -107,21 +109,27 @@ export const auctionCommand = new Promise<CommandRegistry>((resolve) => {
const name = `[${tank.name}](<https://blitzkit.app/tools/tankopedia/${
tank.id
}>)`;
const next = translate('bot.commands.auction.body.next', [
`<:gold:1317173197082333244> ${data.next_price!.value.toLocaleString(
interaction.locale,
)}`,
]);
const next =
data.next_price === null
? ''
: `\n-# ${translate('bot.commands.auction.body.next', [
`<:gold:1317173197082333244> ${data.next_price.value.toLocaleString(
interaction.locale,
)}`,
])}`;
const available = translate('bot.commands.auction.body.available', [
data.current_count.toLocaleString(interaction.locale),
data.initial_count.toLocaleString(interaction.locale),
]);
const isOut = data.current_count === 0;
const outString = isOut ? '~~' : '';
const isLow = data.current_count <= WARNING_COUNT;
return `${
index + 1
}. ${name} <:gold:1317173197082333244> ${data.price!.value.toLocaleString(
return `${index + 1}. ${
outString
}${name} <:gold:1317173197082333244> ${data.price!.value.toLocaleString(
interaction.locale,
)}\n-# ${available}\n-# ${next}`;
)}${outString}\n-#${isLow ? ' ‼️ ' : ' '}${available}${isOut ? '' : next}`;
});
const header = `${title}\n${subtitle}`;
const lines = [header, ...body];
+15 -5
View File
@@ -7,6 +7,7 @@ import {
getLeagueFromScore,
getRatingInfo,
getRatingNeighbors,
imgur,
} from '@blitzkit/core';
import { SlashCommandSubcommandBuilder } from 'discord.js';
import markdownEscape from 'markdown-escape';
@@ -119,6 +120,19 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
statsB2,
);
const leagueTheme = LEAGUE_ACCENT[league.index];
let rewardImage = reward
? reward.type === 'vehicle'
? reward.vehicle.preview_image_url
: reward.stuff.image_url
: undefined;
if (rewardImage) {
const { ok } = await fetch(rewardImage);
if (!ok) {
rewardImage = imgur('kP88vqr');
}
}
return (
<CommandWrapper>
@@ -325,11 +339,7 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
height: 64,
objectFit: 'cover',
}}
src={await iconPng(
reward.type === 'vehicle'
? reward.vehicle.preview_image_url
: reward.stuff.image_url,
)}
src={await iconPng(rewardImage!)}
/>
</div>
)}
+11
View File
@@ -7,8 +7,19 @@ import { manager } from './core/discord/manager';
console.log('pre manager event listeners and spawn');
let isFirst = true;
const interval = setInterval(() => {
if (!isFirst) {
console.warn('respawning all shards because manager died');
manager.respawnAll();
}
isFirst = false;
}, 5000);
const shards = await manager
.on('shardCreate', (shard) => {
clearInterval(interval);
console.log(`🟡 Launching shard ${shard.id}`);
shard.on('ready', () => console.log(`🟢 Launched shard ${shard.id}`));
})
@@ -49,6 +49,7 @@ export function PrimaryArmorSceneComponent({
damage: { value: null },
explosionRadius: { value: null },
greenPenetration: { value: null },
advancedHighlighting: { value: null },
opaque: { value: null },
inverseProjectionMatrix: { value: null },
@@ -90,6 +91,10 @@ export function PrimaryArmorSceneComponent({
function handleGreenPenetrationChange(greenPenetration: boolean) {
material.uniforms.greenPenetration.value = greenPenetration;
}
function handleAdvancedHighlightingChange(advancedHighlighting: boolean) {
material.uniforms.advancedHighlighting.value = advancedHighlighting;
invalidate();
}
function handleOpaqueChange(opaque: boolean) {
material.uniforms.opaque.value = opaque;
}
@@ -133,12 +138,13 @@ export function PrimaryArmorSceneComponent({
if (!noInvalidate) invalidate();
}
const initialState = tankopediaPersistentStore.getState();
handleShellChange();
handleGreenPenetrationChange(
tankopediaPersistentStore.getState().greenPenetration,
);
handleOpaqueChange(tankopediaPersistentStore.getState().opaque);
handleWireframeChange(tankopediaPersistentStore.getState().wireframe);
handleGreenPenetrationChange(initialState.greenPenetration);
handleAdvancedHighlightingChange(initialState.advancedHighlighting);
handleOpaqueChange(initialState.opaque);
handleWireframeChange(initialState.wireframe);
handleProtagonistEquipmentChange(
duelStore.getState().protagonist.equipmentMatrix,
);
@@ -156,6 +162,10 @@ export function PrimaryArmorSceneComponent({
(state) => state.greenPenetration,
handleGreenPenetrationChange,
),
tankopediaPersistentStore.subscribe(
(state) => state.advancedHighlighting,
handleAdvancedHighlightingChange,
),
tankopediaPersistentStore.subscribe(
(state) => state.opaque,
handleOpaqueChange,
@@ -12,82 +12,102 @@ uniform bool canSplash;
uniform float damage;
uniform float explosionRadius;
uniform bool greenPenetration;
uniform bool advancedHighlighting;
uniform bool opaque;
uniform mat4 inverseProjectionMatrix;
uniform highp sampler2D spacedArmorBuffer;
uniform highp sampler2D spacedArmorDepth;
float depthToDistance(float depth) {
vec4 clipPosition = vec4(gl_FragCoord.xy / resolution * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);
vec4 eyePosition = inverseProjectionMatrix * clipPosition;
return length(eyePosition.xyz / eyePosition.w);
const float HALF = 0.5;
const float EXPLOSION_DAMAGE_FACTOR = 1.1;
const float RANDOMIZATION_FACTOR = 0.05;
const float GREEN_VALUE = 0.392;
float getDistance(vec2 coord, float depth) {
vec4 clipPos = vec4(coord * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);
vec4 eyePos = inverseProjectionMatrix * clipPos;
return length(eyePos.xyz / eyePos.w);
}
vec3 getPenetrationColor(bool isThreeCalibersRule) {
if (advancedHighlighting) {
return vec3(0.0, 1.0, isThreeCalibersRule ? 1.0 : 0.0);
}
return vec3(0.0, 1.0, 0.0);
}
void main() {
float penetrationChance = -1.0;
float splashChance = -1.0;
vec2 screenCoord = gl_FragCoord.xy / resolution;
vec4 spacedArmorData = texture2D(spacedArmorBuffer, screenCoord);
bool isUnderSpacedArmor = spacedArmorData.a != 0.0;
vec2 screenCoordinates = gl_FragCoord.xy / resolution;
float angle = acos(dot(vNormal, -vViewPosition) / length(vViewPosition));
vec4 spacedArmorBufferFragment = texture2D(spacedArmorBuffer, screenCoordinates);
bool isUnderSpacedArmor = spacedArmorBufferFragment.a != 0.0;
float viewDistance = length(vViewPosition);
float angle = acos(dot(vNormal, -vViewPosition) / viewDistance);
bool threeCalibersRule = caliber > thickness * 3.0 || isUnderSpacedArmor;
float penetrationChance = -1.0;
float splashChance = 0.0;
bool didRicochet = false;
if (!threeCalibersRule && angle >= ricochet) {
penetrationChance = 0.0;
didRicochet = true;
} else {
bool twoCalibersRule = caliber > thickness * 2.0 && thickness > 0.0;
float finalNormalization = twoCalibersRule ? ((1.4 * normalization * caliber) / (2.0 * thickness)) : normalization;
float finalThickness = thickness / cos(max(0.0, angle - finalNormalization));
float thicknessRatio = thickness > 0.0 ? caliber / thickness : 0.0;
bool twoCalibersRule = thicknessRatio > 2.0;
float finalNorm = twoCalibersRule ? ((1.4 * normalization * caliber) / (2.0 * thickness)) : normalization;
float finalThickness = thickness / cos(max(0.0, angle - finalNorm));
// world space in meters
vec4 spacedArmorDepthFragment = texture2D(spacedArmorDepth, screenCoordinates);
float spacedArmorDistance = depthToDistance(spacedArmorDepthFragment.r);
float primaryArmorDistance = depthToDistance(gl_FragCoord.z);
float distanceFromSpacedArmor = primaryArmorDistance - spacedArmorDistance;
float remainingPen = penetration;
if (canSplash && isUnderSpacedArmor) {
float spacedArmorThickness = spacedArmorBufferFragment.r * penetration;
float finalDamage = 0.5 * damage * (1.0 - distanceFromSpacedArmor / explosionRadius) - 1.1 * (finalThickness + spacedArmorThickness);
if (isUnderSpacedArmor) {
float spacedArmorThickness = spacedArmorData.r * penetration;
remainingPen -= spacedArmorThickness;
penetrationChance = 0.0;
splashChance = finalDamage > 0.0 ? 1.0 : 0.0;
} else {
float remainingPenetration = penetration;
if (isExplosive && remainingPen > 0.0) {
float spacedDist = getDistance(screenCoord, texture2D(spacedArmorDepth, screenCoord).r);
float primaryDist = getDistance(screenCoord, gl_FragCoord.z);
float distFromArmor = primaryDist - spacedDist;
if (isUnderSpacedArmor) {
float spacedArmorThickness = spacedArmorBufferFragment.r * penetration;
remainingPenetration -= spacedArmorThickness;
if (canSplash) {
float finalDamage = HALF * damage * (1.0 - distFromArmor / explosionRadius) - EXPLOSION_DAMAGE_FACTOR * (finalThickness + spacedArmorThickness);
splashChance = step(0.0, finalDamage);
penetrationChance = 0.0;
if (isExplosive && remainingPenetration > 0.0) {
remainingPenetration -= 0.5 * remainingPenetration * distanceFromSpacedArmor;
} else {
remainingPen -= HALF * remainingPen * distFromArmor;
}
}
}
remainingPenetration = max(0.0, remainingPenetration);
float delta = finalThickness - remainingPenetration;
float randomization = remainingPenetration * 0.05;
if (penetrationChance < 0.0) {
remainingPen = max(0.0, remainingPen);
float delta = finalThickness - remainingPen;
float randomization = remainingPen * RANDOMIZATION_FACTOR;
penetrationChance = clamp(1.0 - (delta + randomization) / (2.0 * randomization), 0.0, 1.0);
if (canSplash) {
float splashDamage = 0.5 * damage - 1.1 * finalThickness;
splashChance = splashDamage > 0.0 ? 1.0 : 0.0;
} else {
splashChance = 0.0;
float splashDamage = HALF * damage - EXPLOSION_DAMAGE_FACTOR * finalThickness;
splashChance = step(0.0, splashDamage);
}
}
}
float alpha = opaque ? 1.0 : 0.5;
vec3 color = vec3(1.0, splashChance * 0.392, 0.0);
if (greenPenetration) {
// non-trigonometric red-green mixing: https://www.desmos.com/calculator/ceo1oq7l67
float falling = -pow(penetrationChance, 2.0) + 1.0;
float gaining = -pow(penetrationChance - 1.0, 2.0) + 1.0;
gl_FragColor = vec4(falling * color + gaining * vec3(0.0, 1.0, 0.0), alpha);
float alpha = opaque ? 1.0 : HALF;
vec3 baseColor = vec3(1.0, splashChance * GREEN_VALUE, 0.0);
if (advancedHighlighting && didRicochet) {
baseColor = vec3(1.0, baseColor.g, 1.0);
}
if (greenPenetration || advancedHighlighting) {
float falling = -penetrationChance * penetrationChance + 1.0;
float gaining = -(penetrationChance - 1.0) * (penetrationChance - 1.0) + 1.0;
vec3 penColor = getPenetrationColor(threeCalibersRule);
gl_FragColor = vec4(falling * baseColor + gaining * penColor, alpha);
} else {
gl_FragColor = vec4(color, (1.0 - penetrationChance) * alpha);
gl_FragColor = vec4(baseColor, (1.0 - penetrationChance) * alpha);
}
}
@@ -17,6 +17,7 @@ import {
import {
Box,
Button,
Checkbox,
Dialog,
DropdownMenu,
Flex,
@@ -68,6 +69,9 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
const greenPenetration = TankopediaPersistent.use(
(state) => state.greenPenetration,
);
const advancedHighlighting = TankopediaPersistent.use(
(state) => state.advancedHighlighting,
);
const wireframe = TankopediaPersistent.use((state) => state.wireframe);
const opaque = TankopediaPersistent.use((state) => state.opaque);
const fullScreenAvailable = useFullscreenAvailability();
@@ -325,53 +329,71 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
style={{ transform: 'translateX(-50%)' }}
>
{display === TankopediaDisplay.DynamicArmor && (
<Flex align="center" gap="2">
<Text color="gray" size="2">
Shooter:
</Text>
<Dialog.Root
open={antagonistSelectorOpen}
onOpenChange={setAntagonistSelectorOpen}
<>
<Flex
align="center"
gap="2"
mb="1"
style={{ cursor: 'pointer' }}
onClick={() => {
mutateTankopediaPersistent((draft) => {
draft.advancedHighlighting = !draft.advancedHighlighting;
});
}}
>
<Dialog.Trigger>
<Button variant="ghost">
<Flex gap="2" align="center">
<SmallTankIcon id={antagonistTank.id} size={16} />
{antagonistTank.name}
</Flex>
</Button>
</Dialog.Trigger>
<Checkbox checked={advancedHighlighting} />
<Text color="gray" size="2">
Advanced highlighting
</Text>
</Flex>
<Dialog.Content>
<Dialog.Title align="center">Select tank</Dialog.Title>
<Flex align="center" gap="2">
<Text color="gray" size="2">
Shooter:
</Text>
<Dialog.Root
open={antagonistSelectorOpen}
onOpenChange={setAntagonistSelectorOpen}
>
<Dialog.Trigger>
<Button variant="ghost">
<Flex gap="2" align="center">
<SmallTankIcon id={antagonistTank.id} size={16} />
{antagonistTank.name}
</Flex>
</Button>
</Dialog.Trigger>
<Tabs.Root
value={tab}
onValueChange={setTab}
style={{ position: 'relative' }}
>
<TankSearch
compact
onSelect={(tank) => {
mutateDuel((draft) => {
draft.antagonist.tank = tank;
draft.antagonist.engine = tank.engines.at(-1)!;
draft.antagonist.track = tank.tracks.at(-1)!;
draft.antagonist.turret = tank.turrets.at(-1)!;
draft.antagonist.gun =
draft.antagonist.turret.guns.at(-1)!;
draft.antagonist.shell =
draft.antagonist.gun.gun_type!.value.base.shells[0];
});
setAntagonistSelectorOpen(false);
}}
/>
</Tabs.Root>
</Dialog.Content>
</Dialog.Root>
</Flex>
<Dialog.Content>
<Dialog.Title align="center">Select tank</Dialog.Title>
<Tabs.Root
value={tab}
onValueChange={setTab}
style={{ position: 'relative' }}
>
<TankSearch
compact
onSelect={(tank) => {
mutateDuel((draft) => {
draft.antagonist.tank = tank;
draft.antagonist.engine = tank.engines.at(-1)!;
draft.antagonist.track = tank.tracks.at(-1)!;
draft.antagonist.turret = tank.turrets.at(-1)!;
draft.antagonist.gun =
draft.antagonist.turret.guns.at(-1)!;
draft.antagonist.shell =
draft.antagonist.gun.gun_type!.value.base.shells[0];
});
setAntagonistSelectorOpen(false);
}}
/>
</Tabs.Root>
</Dialog.Content>
</Dialog.Root>
</Flex>
</>
)}
<Flex gap="3" align="center" mt="2">
<SegmentedControl.Root
value={`${display}`}
@@ -8,10 +8,6 @@ import { degToRad } from 'three/src/math/MathUtils.js';
import { awaitableModelDefinitions } from '../../../../../../core/awaitables/modelDefinitions';
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
import { hasEquipment } from '../../../../../../core/blitzkit/hasEquipment';
import {
modelTransformEvent,
type ModelTransformEventData,
} from '../../../../../../core/blitzkit/modelTransform';
import { Pose, poseEvent } from '../../../../../../core/blitzkit/pose';
import { Duel } from '../../../../../../stores/duel';
import {
@@ -35,7 +31,11 @@ const ARCADE_MODE_DISTANCE = 19;
const ARCADE_MODE_ANGLE = degToRad(10);
const ARCADE_MODE_FOV = 54;
export function Controls() {
interface ControlsProps {
naked?: boolean;
}
export function Controls({ naked }: ControlsProps) {
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const display = TankopediaEphemeral.use((state) => state.display);
const duelStore = Duel.useStore();
@@ -82,7 +82,7 @@ export function Controls() {
antagonistModelDefinition.turret_origin.y +
antagonistTurretModelDefinition.gun_origin.y;
const [autoRotate, setAutoRotate] = useState(
display !== TankopediaDisplay.ShootingRange,
display !== TankopediaDisplay.ShootingRange && !naked,
);
const gunHeight =
protagonistHullOrigin.y +
@@ -249,10 +249,6 @@ export function Controls() {
canvas.addEventListener('wheel', handleWheel);
document.body.addEventListener('scroll', handleScroll);
const sniperModeCameraOffset = new Vector3();
let pitch = 0;
let yaw = 0;
function updateCamera() {
if (!orbitControls.current) return;
@@ -307,13 +303,6 @@ export function Controls() {
camera.updateProjectionMatrix();
}
function handleModelTransform(event: ModelTransformEventData) {
pitch = event.pitch;
yaw = event.yaw ?? yaw;
}
modelTransformEvent.on(handleModelTransform);
updateCamera();
const unsubscribeDisplay = tankopediaEphemeralStore.subscribe(
@@ -337,7 +326,6 @@ export function Controls() {
window.removeEventListener('keydown', handleKeyDown);
canvas.removeEventListener('wheel', handleWheel);
document.body.removeEventListener('scroll', handleScroll);
modelTransformEvent.off(handleModelTransform);
};
}, [display === TankopediaDisplay.ShootingRange]);
@@ -348,7 +336,6 @@ export function Controls() {
ref={orbitControls}
enabled={tankopediaEphemeralStore.getState().controlsEnabled}
enableDamping={false}
maxPolarAngle={degToRad(100)}
autoRotate={autoRotate}
autoRotateSpeed={1 / 4}
/>
@@ -3,7 +3,7 @@ import { Box, type BoxProps } from '@radix-ui/themes';
import { forwardRef } from 'react';
import { Var } from '../../../../../../../../core/radix/var';
const DOTS = 32;
// const DOTS = 32;
type TargetCircleProps = BoxProps & {
variant: 'client' | 'server';
@@ -31,10 +31,11 @@ import { TankModel } from './components/TankModel';
interface TankSandboxProps {
thicknessRange: ThicknessRange;
naked?: boolean;
}
export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
({ thicknessRange }, ref) => {
({ thicknessRange, naked }, ref) => {
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const canvas = useRef<HTMLCanvasElement>(null);
const hasImprovedVerticalStabilizer = useEquipment(122);
@@ -179,10 +180,15 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
draft.highlightArmor = undefined;
});
}}
style={{ userSelect: 'none' }}
style={{
userSelect: 'none',
width: naked ? 640 : '100%',
height: naked ? 480 : '100%',
outline: naked ? '1rem red solid' : undefined,
}}
>
<Controls />
<SceneProps />
<Controls naked={naked} />
{!naked && <SceneProps />}
{display !== TankopediaDisplay.StaticArmor && <TankModel />}
<ShotDisplay />
<ArmorPlateDisplay />
@@ -68,7 +68,7 @@ export function TierListTile(props: TierListTileProps) {
lastPosition.current = { x: event.clientX, y: event.clientY };
}, []);
const handlePointerUp = useCallback((event: PointerEvent) => {
const handlePointerUp = useCallback(() => {
if (!card.current) return;
const cardRect = card.current.getBoundingClientRect();
@@ -0,0 +1,40 @@
import { TankSandbox } from '../../../../../components/Tankopedia/HeroSection/components/TankSandbox';
import { awaitableModelDefinitions } from '../../../../../core/awaitables/modelDefinitions';
import { awaitableProvisionDefinitions } from '../../../../../core/awaitables/provisionDefinitions';
import { awaitableTankDefinitions } from '../../../../../core/awaitables/tankDefinitions';
import { App } from '../../../../../stores/app';
import { Duel } from '../../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral';
import { TankopediaPersistent } from '../../../../../stores/tankopediaPersistent';
const [provisionDefinitions, tankDefinitions, modelDefinitions] =
await Promise.all([
awaitableProvisionDefinitions,
awaitableTankDefinitions,
awaitableModelDefinitions,
]);
interface PageProps {
id: number;
}
export function Page({ id }: PageProps) {
const tank = tankDefinitions.tanks[id];
const model = modelDefinitions.models[id];
return (
<App.Provider>
<TankopediaPersistent.Provider>
<Duel.Provider data={{ provisionDefinitions, tank }}>
<TankopediaEphemeral.Provider data={model}>
<Content />
</TankopediaEphemeral.Provider>
</Duel.Provider>
</TankopediaPersistent.Provider>
</App.Provider>
);
}
function Content() {
return <TankSandbox naked thicknessRange={{ value: 0 }} />;
}
@@ -0,0 +1,30 @@
---
import { assertSecret } from '@blitzkit/core';
import type { GetStaticPaths } from 'astro';
import { getStaticPaths as tankopediaGetStaticPaths } from '../../../../tools/tankopedia/[id]/index.astro';
import { Page } from './_index';
export const getStaticPaths = (async () => {
if (assertSecret(import.meta.env.MODE) === 'development') {
return await tankopediaGetStaticPaths();
} else {
return [];
}
}) satisfies GetStaticPaths;
const { id } = Astro.params;
---
<html>
<head>
<style is:global>
body {
margin: 0;
}
</style>
</head>
<body>
<Page {id} client:load />
</body>
</html>
@@ -12,6 +12,7 @@ export type TankopediaTestTankDisplay = 'include' | 'exclude' | 'only';
export interface TankopediaPersistentStore {
wireframe: boolean;
opaque: boolean;
advancedHighlighting: boolean;
environment: (typeof ENVIRONMENTS)[number];
showGrid: boolean;
greenPenetration: boolean;
@@ -33,6 +34,7 @@ export const TankopediaPersistent = createContextualStore(() =>
subscribeWithSelector<TankopediaPersistentStore>(() => ({
wireframe: false,
opaque: false,
advancedHighlighting: false,
environment: ENVIRONMENTS[0],
showGrid: true,
greenPenetration: false,
+5
View File
@@ -0,0 +1,5 @@
{
"github": {
"enabled": false
}
}