mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-22 19:19:52 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50a3d1e922 | ||
|
|
e296267ccd | ||
|
|
d9aca6a3ed | ||
|
|
d07a6e954e | ||
|
|
6bcaf685f8 | ||
|
|
066f17a2f3 | ||
|
|
374d6c22f9 | ||
|
|
55927258ca | ||
|
|
bb99220497 | ||
|
|
4f6b41e264 | ||
|
|
f9c4cee30c | ||
|
|
d865043b34 | ||
|
|
a4080f21b9 | ||
|
|
853c520d8a | ||
|
|
d78f8c6ec4 | ||
|
|
318a71bd32 | ||
|
|
bc9c9ad4e7 | ||
|
|
b38c732983 |
No files matched your search
@@ -1,8 +1,7 @@
|
|||||||
name: Build Website
|
name: Website
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: main
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
schedule:
|
schedule:
|
||||||
@@ -15,7 +14,7 @@ permissions:
|
|||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: ${{ github.ref == 'refs/heads/main' }}
|
||||||
|
|
||||||
env:
|
env:
|
||||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||||
@@ -52,14 +51,41 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: dist/website
|
path: dist/website
|
||||||
|
|
||||||
deploy:
|
deploy-github-pages:
|
||||||
environment:
|
if: github.ref == 'refs/heads/main'
|
||||||
name: github-pages
|
|
||||||
url: ${{ steps.deployment.outputs.page_url }}
|
name: Deploy to GitHub Pages
|
||||||
needs: build
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Deploy
|
needs: build
|
||||||
|
environment:
|
||||||
|
name: Production
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
id: deployment
|
id: deployment
|
||||||
uses: actions/deploy-pages@v4
|
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
|
||||||
@@ -26,3 +26,4 @@ target
|
|||||||
# !packages/core/src/protos/*.proto
|
# !packages/core/src/protos/*.proto
|
||||||
/.vercel
|
/.vercel
|
||||||
/.astro
|
/.astro
|
||||||
|
.vercel
|
||||||
Vendored
+1
@@ -189,6 +189,7 @@
|
|||||||
"unpickled",
|
"unpickled",
|
||||||
"Unregistering",
|
"Unregistering",
|
||||||
"unrotate",
|
"unrotate",
|
||||||
|
"vercel",
|
||||||
"vickers",
|
"vickers",
|
||||||
"vitepress",
|
"vitepress",
|
||||||
"Vovko",
|
"Vovko",
|
||||||
|
|||||||
@@ -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
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "blitzkit",
|
"name": "blitzkit",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.26.3",
|
"version": "1.26.4",
|
||||||
"description": "🎉 Tools for everything World of Tanks Blitz",
|
"description": "🎉 Tools for everything World of Tanks Blitz",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"types": "./types.d.ts",
|
"types": "./types.d.ts",
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ interface Currency {
|
|||||||
type: 'currency';
|
type: 'currency';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const WARNING_COUNT = 500;
|
||||||
|
|
||||||
export const auctionCommand = new Promise<CommandRegistry>((resolve) => {
|
export const auctionCommand = new Promise<CommandRegistry>((resolve) => {
|
||||||
resolve({
|
resolve({
|
||||||
command: createLocalizedCommand('auction'),
|
command: createLocalizedCommand('auction'),
|
||||||
@@ -116,12 +118,16 @@ export const auctionCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
data.current_count.toLocaleString(interaction.locale),
|
data.current_count.toLocaleString(interaction.locale),
|
||||||
data.initial_count.toLocaleString(interaction.locale),
|
data.initial_count.toLocaleString(interaction.locale),
|
||||||
]);
|
]);
|
||||||
|
const nextLine = `\n-# ${next}`;
|
||||||
|
const isOut = data.current_count === 0;
|
||||||
|
const outString = isOut ? '~~' : '';
|
||||||
|
const isLow = data.current_count <= WARNING_COUNT;
|
||||||
|
|
||||||
return `${
|
return `${index + 1}. ${
|
||||||
index + 1
|
outString
|
||||||
}. ${name} <:gold:1317173197082333244> ${data.price!.value.toLocaleString(
|
}${name} <:gold:1317173197082333244> ${data.price!.value.toLocaleString(
|
||||||
interaction.locale,
|
interaction.locale,
|
||||||
)}\n-# ${available}\n-# ${next}`;
|
)}${outString}\n-#${isLow ? ' ‼️ ' : ' '}${available}${isOut ? '' : nextLine}`;
|
||||||
});
|
});
|
||||||
const header = `${title}\n${subtitle}`;
|
const header = `${title}\n${subtitle}`;
|
||||||
const lines = [header, ...body];
|
const lines = [header, ...body];
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
getLeagueFromScore,
|
getLeagueFromScore,
|
||||||
getRatingInfo,
|
getRatingInfo,
|
||||||
getRatingNeighbors,
|
getRatingNeighbors,
|
||||||
|
imgur,
|
||||||
} from '@blitzkit/core';
|
} from '@blitzkit/core';
|
||||||
import { SlashCommandSubcommandBuilder } from 'discord.js';
|
import { SlashCommandSubcommandBuilder } from 'discord.js';
|
||||||
import markdownEscape from 'markdown-escape';
|
import markdownEscape from 'markdown-escape';
|
||||||
@@ -119,6 +120,19 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
statsB2,
|
statsB2,
|
||||||
);
|
);
|
||||||
const leagueTheme = LEAGUE_ACCENT[league.index];
|
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 (
|
return (
|
||||||
<CommandWrapper>
|
<CommandWrapper>
|
||||||
@@ -325,11 +339,7 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
height: 64,
|
height: 64,
|
||||||
objectFit: 'cover',
|
objectFit: 'cover',
|
||||||
}}
|
}}
|
||||||
src={await iconPng(
|
src={await iconPng(rewardImage!)}
|
||||||
reward.type === 'vehicle'
|
|
||||||
? reward.vehicle.preview_image_url
|
|
||||||
: reward.stuff.image_url,
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -7,8 +7,19 @@ import { manager } from './core/discord/manager';
|
|||||||
|
|
||||||
console.log('pre manager event listeners and spawn');
|
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
|
const shards = await manager
|
||||||
.on('shardCreate', (shard) => {
|
.on('shardCreate', (shard) => {
|
||||||
|
clearInterval(interval);
|
||||||
console.log(`🟡 Launching shard ${shard.id}`);
|
console.log(`🟡 Launching shard ${shard.id}`);
|
||||||
shard.on('ready', () => console.log(`🟢 Launched shard ${shard.id}`));
|
shard.on('ready', () => console.log(`🟢 Launched shard ${shard.id}`));
|
||||||
})
|
})
|
||||||
|
|||||||
+15
-5
@@ -49,6 +49,7 @@ export function PrimaryArmorSceneComponent({
|
|||||||
damage: { value: null },
|
damage: { value: null },
|
||||||
explosionRadius: { value: null },
|
explosionRadius: { value: null },
|
||||||
greenPenetration: { value: null },
|
greenPenetration: { value: null },
|
||||||
|
advancedHighlighting: { value: null },
|
||||||
opaque: { value: null },
|
opaque: { value: null },
|
||||||
|
|
||||||
inverseProjectionMatrix: { value: null },
|
inverseProjectionMatrix: { value: null },
|
||||||
@@ -90,6 +91,10 @@ export function PrimaryArmorSceneComponent({
|
|||||||
function handleGreenPenetrationChange(greenPenetration: boolean) {
|
function handleGreenPenetrationChange(greenPenetration: boolean) {
|
||||||
material.uniforms.greenPenetration.value = greenPenetration;
|
material.uniforms.greenPenetration.value = greenPenetration;
|
||||||
}
|
}
|
||||||
|
function handleAdvancedHighlightingChange(advancedHighlighting: boolean) {
|
||||||
|
material.uniforms.advancedHighlighting.value = advancedHighlighting;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
function handleOpaqueChange(opaque: boolean) {
|
function handleOpaqueChange(opaque: boolean) {
|
||||||
material.uniforms.opaque.value = opaque;
|
material.uniforms.opaque.value = opaque;
|
||||||
}
|
}
|
||||||
@@ -133,12 +138,13 @@ export function PrimaryArmorSceneComponent({
|
|||||||
if (!noInvalidate) invalidate();
|
if (!noInvalidate) invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const initialState = tankopediaPersistentStore.getState();
|
||||||
|
|
||||||
handleShellChange();
|
handleShellChange();
|
||||||
handleGreenPenetrationChange(
|
handleGreenPenetrationChange(initialState.greenPenetration);
|
||||||
tankopediaPersistentStore.getState().greenPenetration,
|
handleAdvancedHighlightingChange(initialState.advancedHighlighting);
|
||||||
);
|
handleOpaqueChange(initialState.opaque);
|
||||||
handleOpaqueChange(tankopediaPersistentStore.getState().opaque);
|
handleWireframeChange(initialState.wireframe);
|
||||||
handleWireframeChange(tankopediaPersistentStore.getState().wireframe);
|
|
||||||
handleProtagonistEquipmentChange(
|
handleProtagonistEquipmentChange(
|
||||||
duelStore.getState().protagonist.equipmentMatrix,
|
duelStore.getState().protagonist.equipmentMatrix,
|
||||||
);
|
);
|
||||||
@@ -156,6 +162,10 @@ export function PrimaryArmorSceneComponent({
|
|||||||
(state) => state.greenPenetration,
|
(state) => state.greenPenetration,
|
||||||
handleGreenPenetrationChange,
|
handleGreenPenetrationChange,
|
||||||
),
|
),
|
||||||
|
tankopediaPersistentStore.subscribe(
|
||||||
|
(state) => state.advancedHighlighting,
|
||||||
|
handleAdvancedHighlightingChange,
|
||||||
|
),
|
||||||
tankopediaPersistentStore.subscribe(
|
tankopediaPersistentStore.subscribe(
|
||||||
(state) => state.opaque,
|
(state) => state.opaque,
|
||||||
handleOpaqueChange,
|
handleOpaqueChange,
|
||||||
|
|||||||
+66
-46
@@ -12,82 +12,102 @@ uniform bool canSplash;
|
|||||||
uniform float damage;
|
uniform float damage;
|
||||||
uniform float explosionRadius;
|
uniform float explosionRadius;
|
||||||
uniform bool greenPenetration;
|
uniform bool greenPenetration;
|
||||||
|
uniform bool advancedHighlighting;
|
||||||
uniform bool opaque;
|
uniform bool opaque;
|
||||||
uniform mat4 inverseProjectionMatrix;
|
uniform mat4 inverseProjectionMatrix;
|
||||||
|
|
||||||
uniform highp sampler2D spacedArmorBuffer;
|
uniform highp sampler2D spacedArmorBuffer;
|
||||||
uniform highp sampler2D spacedArmorDepth;
|
uniform highp sampler2D spacedArmorDepth;
|
||||||
|
|
||||||
float depthToDistance(float depth) {
|
const float HALF = 0.5;
|
||||||
vec4 clipPosition = vec4(gl_FragCoord.xy / resolution * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);
|
const float EXPLOSION_DAMAGE_FACTOR = 1.1;
|
||||||
vec4 eyePosition = inverseProjectionMatrix * clipPosition;
|
const float RANDOMIZATION_FACTOR = 0.05;
|
||||||
return length(eyePosition.xyz / eyePosition.w);
|
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() {
|
void main() {
|
||||||
float penetrationChance = -1.0;
|
vec2 screenCoord = gl_FragCoord.xy / resolution;
|
||||||
float splashChance = -1.0;
|
vec4 spacedArmorData = texture2D(spacedArmorBuffer, screenCoord);
|
||||||
|
bool isUnderSpacedArmor = spacedArmorData.a != 0.0;
|
||||||
|
|
||||||
vec2 screenCoordinates = gl_FragCoord.xy / resolution;
|
float viewDistance = length(vViewPosition);
|
||||||
float angle = acos(dot(vNormal, -vViewPosition) / length(vViewPosition));
|
float angle = acos(dot(vNormal, -vViewPosition) / viewDistance);
|
||||||
|
|
||||||
vec4 spacedArmorBufferFragment = texture2D(spacedArmorBuffer, screenCoordinates);
|
|
||||||
bool isUnderSpacedArmor = spacedArmorBufferFragment.a != 0.0;
|
|
||||||
|
|
||||||
bool threeCalibersRule = caliber > thickness * 3.0 || isUnderSpacedArmor;
|
bool threeCalibersRule = caliber > thickness * 3.0 || isUnderSpacedArmor;
|
||||||
|
float penetrationChance = -1.0;
|
||||||
|
float splashChance = 0.0;
|
||||||
|
bool didRicochet = false;
|
||||||
|
|
||||||
if (!threeCalibersRule && angle >= ricochet) {
|
if (!threeCalibersRule && angle >= ricochet) {
|
||||||
penetrationChance = 0.0;
|
penetrationChance = 0.0;
|
||||||
|
didRicochet = true;
|
||||||
} else {
|
} else {
|
||||||
bool twoCalibersRule = caliber > thickness * 2.0 && thickness > 0.0;
|
float thicknessRatio = thickness > 0.0 ? caliber / thickness : 0.0;
|
||||||
float finalNormalization = twoCalibersRule ? ((1.4 * normalization * caliber) / (2.0 * thickness)) : normalization;
|
bool twoCalibersRule = thicknessRatio > 2.0;
|
||||||
float finalThickness = thickness / cos(max(0.0, angle - finalNormalization));
|
float finalNorm = twoCalibersRule ? ((1.4 * normalization * caliber) / (2.0 * thickness)) : normalization;
|
||||||
|
float finalThickness = thickness / cos(max(0.0, angle - finalNorm));
|
||||||
|
|
||||||
// world space in meters
|
float remainingPen = penetration;
|
||||||
vec4 spacedArmorDepthFragment = texture2D(spacedArmorDepth, screenCoordinates);
|
|
||||||
float spacedArmorDistance = depthToDistance(spacedArmorDepthFragment.r);
|
|
||||||
float primaryArmorDistance = depthToDistance(gl_FragCoord.z);
|
|
||||||
float distanceFromSpacedArmor = primaryArmorDistance - spacedArmorDistance;
|
|
||||||
|
|
||||||
if (canSplash && isUnderSpacedArmor) {
|
if (isUnderSpacedArmor) {
|
||||||
float spacedArmorThickness = spacedArmorBufferFragment.r * penetration;
|
float spacedArmorThickness = spacedArmorData.r * penetration;
|
||||||
float finalDamage = 0.5 * damage * (1.0 - distanceFromSpacedArmor / explosionRadius) - 1.1 * (finalThickness + spacedArmorThickness);
|
remainingPen -= spacedArmorThickness;
|
||||||
|
|
||||||
penetrationChance = 0.0;
|
if (isExplosive && remainingPen > 0.0) {
|
||||||
splashChance = finalDamage > 0.0 ? 1.0 : 0.0;
|
float spacedDist = getDistance(screenCoord, texture2D(spacedArmorDepth, screenCoord).r);
|
||||||
} else {
|
float primaryDist = getDistance(screenCoord, gl_FragCoord.z);
|
||||||
float remainingPenetration = penetration;
|
float distFromArmor = primaryDist - spacedDist;
|
||||||
|
|
||||||
if (isUnderSpacedArmor) {
|
if (canSplash) {
|
||||||
float spacedArmorThickness = spacedArmorBufferFragment.r * penetration;
|
float finalDamage = HALF * damage * (1.0 - distFromArmor / explosionRadius) - EXPLOSION_DAMAGE_FACTOR * (finalThickness + spacedArmorThickness);
|
||||||
remainingPenetration -= spacedArmorThickness;
|
splashChance = step(0.0, finalDamage);
|
||||||
|
penetrationChance = 0.0;
|
||||||
|
|
||||||
if (isExplosive && remainingPenetration > 0.0) {
|
} else {
|
||||||
remainingPenetration -= 0.5 * remainingPenetration * distanceFromSpacedArmor;
|
remainingPen -= HALF * remainingPen * distFromArmor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
remainingPenetration = max(0.0, remainingPenetration);
|
if (penetrationChance < 0.0) {
|
||||||
float delta = finalThickness - remainingPenetration;
|
remainingPen = max(0.0, remainingPen);
|
||||||
float randomization = remainingPenetration * 0.05;
|
float delta = finalThickness - remainingPen;
|
||||||
|
float randomization = remainingPen * RANDOMIZATION_FACTOR;
|
||||||
penetrationChance = clamp(1.0 - (delta + randomization) / (2.0 * randomization), 0.0, 1.0);
|
penetrationChance = clamp(1.0 - (delta + randomization) / (2.0 * randomization), 0.0, 1.0);
|
||||||
|
|
||||||
if (canSplash) {
|
if (canSplash) {
|
||||||
float splashDamage = 0.5 * damage - 1.1 * finalThickness;
|
float splashDamage = HALF * damage - EXPLOSION_DAMAGE_FACTOR * finalThickness;
|
||||||
splashChance = splashDamage > 0.0 ? 1.0 : 0.0;
|
splashChance = step(0.0, splashDamage);
|
||||||
} else {
|
|
||||||
splashChance = 0.0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float alpha = opaque ? 1.0 : 0.5;
|
float alpha = opaque ? 1.0 : HALF;
|
||||||
vec3 color = vec3(1.0, splashChance * 0.392, 0.0);
|
vec3 baseColor = vec3(1.0, splashChance * GREEN_VALUE, 0.0);
|
||||||
if (greenPenetration) {
|
|
||||||
// non-trigonometric red-green mixing: https://www.desmos.com/calculator/ceo1oq7l67
|
if (advancedHighlighting && didRicochet) {
|
||||||
float falling = -pow(penetrationChance, 2.0) + 1.0;
|
baseColor = vec3(1.0, baseColor.g, 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);
|
|
||||||
|
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 {
|
} else {
|
||||||
gl_FragColor = vec4(color, (1.0 - penetrationChance) * alpha);
|
gl_FragColor = vec4(baseColor, (1.0 - penetrationChance) * alpha);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+65
-43
@@ -17,6 +17,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
|
Checkbox,
|
||||||
Dialog,
|
Dialog,
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
Flex,
|
Flex,
|
||||||
@@ -68,6 +69,9 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
|||||||
const greenPenetration = TankopediaPersistent.use(
|
const greenPenetration = TankopediaPersistent.use(
|
||||||
(state) => state.greenPenetration,
|
(state) => state.greenPenetration,
|
||||||
);
|
);
|
||||||
|
const advancedHighlighting = TankopediaPersistent.use(
|
||||||
|
(state) => state.advancedHighlighting,
|
||||||
|
);
|
||||||
const wireframe = TankopediaPersistent.use((state) => state.wireframe);
|
const wireframe = TankopediaPersistent.use((state) => state.wireframe);
|
||||||
const opaque = TankopediaPersistent.use((state) => state.opaque);
|
const opaque = TankopediaPersistent.use((state) => state.opaque);
|
||||||
const fullScreenAvailable = useFullscreenAvailability();
|
const fullScreenAvailable = useFullscreenAvailability();
|
||||||
@@ -325,53 +329,71 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
|||||||
style={{ transform: 'translateX(-50%)' }}
|
style={{ transform: 'translateX(-50%)' }}
|
||||||
>
|
>
|
||||||
{display === TankopediaDisplay.DynamicArmor && (
|
{display === TankopediaDisplay.DynamicArmor && (
|
||||||
<Flex align="center" gap="2">
|
<>
|
||||||
<Text color="gray" size="2">
|
<Flex
|
||||||
Shooter:
|
align="center"
|
||||||
</Text>
|
gap="2"
|
||||||
<Dialog.Root
|
mb="1"
|
||||||
open={antagonistSelectorOpen}
|
style={{ cursor: 'pointer' }}
|
||||||
onOpenChange={setAntagonistSelectorOpen}
|
onClick={() => {
|
||||||
|
mutateTankopediaPersistent((draft) => {
|
||||||
|
draft.advancedHighlighting = !draft.advancedHighlighting;
|
||||||
|
});
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Dialog.Trigger>
|
<Checkbox checked={advancedHighlighting} />
|
||||||
<Button variant="ghost">
|
<Text color="gray" size="2">
|
||||||
<Flex gap="2" align="center">
|
Advanced highlighting
|
||||||
<SmallTankIcon id={antagonistTank.id} size={16} />
|
</Text>
|
||||||
{antagonistTank.name}
|
</Flex>
|
||||||
</Flex>
|
|
||||||
</Button>
|
|
||||||
</Dialog.Trigger>
|
|
||||||
|
|
||||||
<Dialog.Content>
|
<Flex align="center" gap="2">
|
||||||
<Dialog.Title align="center">Select tank</Dialog.Title>
|
<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
|
<Dialog.Content>
|
||||||
value={tab}
|
<Dialog.Title align="center">Select tank</Dialog.Title>
|
||||||
onValueChange={setTab}
|
|
||||||
style={{ position: 'relative' }}
|
<Tabs.Root
|
||||||
>
|
value={tab}
|
||||||
<TankSearch
|
onValueChange={setTab}
|
||||||
compact
|
style={{ position: 'relative' }}
|
||||||
onSelect={(tank) => {
|
>
|
||||||
mutateDuel((draft) => {
|
<TankSearch
|
||||||
draft.antagonist.tank = tank;
|
compact
|
||||||
draft.antagonist.engine = tank.engines.at(-1)!;
|
onSelect={(tank) => {
|
||||||
draft.antagonist.track = tank.tracks.at(-1)!;
|
mutateDuel((draft) => {
|
||||||
draft.antagonist.turret = tank.turrets.at(-1)!;
|
draft.antagonist.tank = tank;
|
||||||
draft.antagonist.gun =
|
draft.antagonist.engine = tank.engines.at(-1)!;
|
||||||
draft.antagonist.turret.guns.at(-1)!;
|
draft.antagonist.track = tank.tracks.at(-1)!;
|
||||||
draft.antagonist.shell =
|
draft.antagonist.turret = tank.turrets.at(-1)!;
|
||||||
draft.antagonist.gun.gun_type!.value.base.shells[0];
|
draft.antagonist.gun =
|
||||||
});
|
draft.antagonist.turret.guns.at(-1)!;
|
||||||
setAntagonistSelectorOpen(false);
|
draft.antagonist.shell =
|
||||||
}}
|
draft.antagonist.gun.gun_type!.value.base.shells[0];
|
||||||
/>
|
});
|
||||||
</Tabs.Root>
|
setAntagonistSelectorOpen(false);
|
||||||
</Dialog.Content>
|
}}
|
||||||
</Dialog.Root>
|
/>
|
||||||
</Flex>
|
</Tabs.Root>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Root>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Flex gap="3" align="center" mt="2">
|
<Flex gap="3" align="center" mt="2">
|
||||||
<SegmentedControl.Root
|
<SegmentedControl.Root
|
||||||
value={`${display}`}
|
value={`${display}`}
|
||||||
|
|||||||
+6
-19
@@ -8,10 +8,6 @@ import { degToRad } from 'three/src/math/MathUtils.js';
|
|||||||
import { awaitableModelDefinitions } from '../../../../../../core/awaitables/modelDefinitions';
|
import { awaitableModelDefinitions } from '../../../../../../core/awaitables/modelDefinitions';
|
||||||
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
||||||
import { hasEquipment } from '../../../../../../core/blitzkit/hasEquipment';
|
import { hasEquipment } from '../../../../../../core/blitzkit/hasEquipment';
|
||||||
import {
|
|
||||||
modelTransformEvent,
|
|
||||||
type ModelTransformEventData,
|
|
||||||
} from '../../../../../../core/blitzkit/modelTransform';
|
|
||||||
import { Pose, poseEvent } from '../../../../../../core/blitzkit/pose';
|
import { Pose, poseEvent } from '../../../../../../core/blitzkit/pose';
|
||||||
import { Duel } from '../../../../../../stores/duel';
|
import { Duel } from '../../../../../../stores/duel';
|
||||||
import {
|
import {
|
||||||
@@ -35,7 +31,11 @@ const ARCADE_MODE_DISTANCE = 19;
|
|||||||
const ARCADE_MODE_ANGLE = degToRad(10);
|
const ARCADE_MODE_ANGLE = degToRad(10);
|
||||||
const ARCADE_MODE_FOV = 54;
|
const ARCADE_MODE_FOV = 54;
|
||||||
|
|
||||||
export function Controls() {
|
interface ControlsProps {
|
||||||
|
naked?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Controls({ naked }: ControlsProps) {
|
||||||
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
||||||
const display = TankopediaEphemeral.use((state) => state.display);
|
const display = TankopediaEphemeral.use((state) => state.display);
|
||||||
const duelStore = Duel.useStore();
|
const duelStore = Duel.useStore();
|
||||||
@@ -82,7 +82,7 @@ export function Controls() {
|
|||||||
antagonistModelDefinition.turret_origin.y +
|
antagonistModelDefinition.turret_origin.y +
|
||||||
antagonistTurretModelDefinition.gun_origin.y;
|
antagonistTurretModelDefinition.gun_origin.y;
|
||||||
const [autoRotate, setAutoRotate] = useState(
|
const [autoRotate, setAutoRotate] = useState(
|
||||||
display !== TankopediaDisplay.ShootingRange,
|
display !== TankopediaDisplay.ShootingRange && !naked,
|
||||||
);
|
);
|
||||||
const gunHeight =
|
const gunHeight =
|
||||||
protagonistHullOrigin.y +
|
protagonistHullOrigin.y +
|
||||||
@@ -249,10 +249,6 @@ export function Controls() {
|
|||||||
canvas.addEventListener('wheel', handleWheel);
|
canvas.addEventListener('wheel', handleWheel);
|
||||||
document.body.addEventListener('scroll', handleScroll);
|
document.body.addEventListener('scroll', handleScroll);
|
||||||
|
|
||||||
const sniperModeCameraOffset = new Vector3();
|
|
||||||
let pitch = 0;
|
|
||||||
let yaw = 0;
|
|
||||||
|
|
||||||
function updateCamera() {
|
function updateCamera() {
|
||||||
if (!orbitControls.current) return;
|
if (!orbitControls.current) return;
|
||||||
|
|
||||||
@@ -307,13 +303,6 @@ export function Controls() {
|
|||||||
camera.updateProjectionMatrix();
|
camera.updateProjectionMatrix();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleModelTransform(event: ModelTransformEventData) {
|
|
||||||
pitch = event.pitch;
|
|
||||||
yaw = event.yaw ?? yaw;
|
|
||||||
}
|
|
||||||
|
|
||||||
modelTransformEvent.on(handleModelTransform);
|
|
||||||
|
|
||||||
updateCamera();
|
updateCamera();
|
||||||
|
|
||||||
const unsubscribeDisplay = tankopediaEphemeralStore.subscribe(
|
const unsubscribeDisplay = tankopediaEphemeralStore.subscribe(
|
||||||
@@ -337,7 +326,6 @@ export function Controls() {
|
|||||||
window.removeEventListener('keydown', handleKeyDown);
|
window.removeEventListener('keydown', handleKeyDown);
|
||||||
canvas.removeEventListener('wheel', handleWheel);
|
canvas.removeEventListener('wheel', handleWheel);
|
||||||
document.body.removeEventListener('scroll', handleScroll);
|
document.body.removeEventListener('scroll', handleScroll);
|
||||||
modelTransformEvent.off(handleModelTransform);
|
|
||||||
};
|
};
|
||||||
}, [display === TankopediaDisplay.ShootingRange]);
|
}, [display === TankopediaDisplay.ShootingRange]);
|
||||||
|
|
||||||
@@ -348,7 +336,6 @@ export function Controls() {
|
|||||||
ref={orbitControls}
|
ref={orbitControls}
|
||||||
enabled={tankopediaEphemeralStore.getState().controlsEnabled}
|
enabled={tankopediaEphemeralStore.getState().controlsEnabled}
|
||||||
enableDamping={false}
|
enableDamping={false}
|
||||||
maxPolarAngle={degToRad(100)}
|
|
||||||
autoRotate={autoRotate}
|
autoRotate={autoRotate}
|
||||||
autoRotateSpeed={1 / 4}
|
autoRotateSpeed={1 / 4}
|
||||||
/>
|
/>
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ import { Box, type BoxProps } from '@radix-ui/themes';
|
|||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
import { Var } from '../../../../../../../../core/radix/var';
|
import { Var } from '../../../../../../../../core/radix/var';
|
||||||
|
|
||||||
const DOTS = 32;
|
// const DOTS = 32;
|
||||||
|
|
||||||
type TargetCircleProps = BoxProps & {
|
type TargetCircleProps = BoxProps & {
|
||||||
variant: 'client' | 'server';
|
variant: 'client' | 'server';
|
||||||
|
|||||||
+10
-4
@@ -31,10 +31,11 @@ import { TankModel } from './components/TankModel';
|
|||||||
|
|
||||||
interface TankSandboxProps {
|
interface TankSandboxProps {
|
||||||
thicknessRange: ThicknessRange;
|
thicknessRange: ThicknessRange;
|
||||||
|
naked?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
||||||
({ thicknessRange }, ref) => {
|
({ thicknessRange, naked }, ref) => {
|
||||||
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
||||||
const canvas = useRef<HTMLCanvasElement>(null);
|
const canvas = useRef<HTMLCanvasElement>(null);
|
||||||
const hasImprovedVerticalStabilizer = useEquipment(122);
|
const hasImprovedVerticalStabilizer = useEquipment(122);
|
||||||
@@ -179,10 +180,15 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
|||||||
draft.highlightArmor = undefined;
|
draft.highlightArmor = undefined;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
style={{ userSelect: 'none' }}
|
style={{
|
||||||
|
userSelect: 'none',
|
||||||
|
width: naked ? 640 : '100%',
|
||||||
|
height: naked ? 480 : '100%',
|
||||||
|
outline: naked ? '1rem red solid' : undefined,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Controls />
|
<Controls naked={naked} />
|
||||||
<SceneProps />
|
{!naked && <SceneProps />}
|
||||||
{display !== TankopediaDisplay.StaticArmor && <TankModel />}
|
{display !== TankopediaDisplay.StaticArmor && <TankModel />}
|
||||||
<ShotDisplay />
|
<ShotDisplay />
|
||||||
<ArmorPlateDisplay />
|
<ArmorPlateDisplay />
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export function TierListTile(props: TierListTileProps) {
|
|||||||
|
|
||||||
lastPosition.current = { x: event.clientX, y: event.clientY };
|
lastPosition.current = { x: event.clientX, y: event.clientY };
|
||||||
}, []);
|
}, []);
|
||||||
const handlePointerUp = useCallback((event: PointerEvent) => {
|
const handlePointerUp = useCallback(() => {
|
||||||
if (!card.current) return;
|
if (!card.current) return;
|
||||||
|
|
||||||
const cardRect = card.current.getBoundingClientRect();
|
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 {
|
export interface TankopediaPersistentStore {
|
||||||
wireframe: boolean;
|
wireframe: boolean;
|
||||||
opaque: boolean;
|
opaque: boolean;
|
||||||
|
advancedHighlighting: boolean;
|
||||||
environment: (typeof ENVIRONMENTS)[number];
|
environment: (typeof ENVIRONMENTS)[number];
|
||||||
showGrid: boolean;
|
showGrid: boolean;
|
||||||
greenPenetration: boolean;
|
greenPenetration: boolean;
|
||||||
@@ -33,6 +34,7 @@ export const TankopediaPersistent = createContextualStore(() =>
|
|||||||
subscribeWithSelector<TankopediaPersistentStore>(() => ({
|
subscribeWithSelector<TankopediaPersistentStore>(() => ({
|
||||||
wireframe: false,
|
wireframe: false,
|
||||||
opaque: false,
|
opaque: false,
|
||||||
|
advancedHighlighting: false,
|
||||||
environment: ENVIRONMENTS[0],
|
environment: ENVIRONMENTS[0],
|
||||||
showGrid: true,
|
showGrid: true,
|
||||||
greenPenetration: false,
|
greenPenetration: false,
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"github": {
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user