mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 02:37:05 +02:00
feat: real popular tanks list
This commit is contained in:
1 parent
31a61c8a6b
commit
4b8a154142
8 files changed
+100
-45
No files matched your search
Vendored
+2
@@ -7,6 +7,8 @@
|
||||
"adsense",
|
||||
"aeson",
|
||||
"alphatest",
|
||||
"analyticsdata",
|
||||
"analyticsreporting",
|
||||
"APAC",
|
||||
"apcr",
|
||||
"applypatches",
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
"csv-parse": "^5.5.6",
|
||||
"decompress": "^4.2.1",
|
||||
"fast-xml-parser": "^4.4.1",
|
||||
"googleapis": "^134.0.0",
|
||||
"progress": "^2.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"@react-three/fiber": "^8.17.10",
|
||||
"compare-versions": "^6.1.1",
|
||||
"echarts": "^5.5.1",
|
||||
"googleapis": "^144.0.0",
|
||||
"immer": "^10.1.1",
|
||||
"js-cookie": "^3.0.5",
|
||||
"markdown-to-jsx": "^7.5.0",
|
||||
|
||||
@@ -1,30 +1,67 @@
|
||||
import { Box, Flex, Heading } from '@radix-ui/themes';
|
||||
import { awaitableAverageDefinitions } from '../core/awaitables/averageDefinitions';
|
||||
import { assertSecret } from '@blitzkit/core';
|
||||
import { EyeOpenIcon } from '@radix-ui/react-icons';
|
||||
import { Box, Flex, Heading, Text } from '@radix-ui/themes';
|
||||
import { google } from 'googleapis';
|
||||
import { awaitableTankDefinitions } from '../core/awaitables/tankDefinitions';
|
||||
import { TankopediaPersistent } from '../stores/tankopediaPersistent';
|
||||
import { TankCard } from './TankCard';
|
||||
|
||||
const [tankDefinitions, averageDefinitions] = await Promise.all([
|
||||
awaitableTankDefinitions,
|
||||
awaitableAverageDefinitions,
|
||||
]);
|
||||
const tanks = Object.values(tankDefinitions.tanks);
|
||||
const tankAverages = Object.entries(averageDefinitions.averages).map(
|
||||
([id, average]) => ({
|
||||
id: Number(id),
|
||||
...average,
|
||||
}),
|
||||
);
|
||||
const [tankDefinitions] = await Promise.all([awaitableTankDefinitions, ,]);
|
||||
|
||||
// lol don't ask questions
|
||||
const testTanks = tanks
|
||||
.filter((tank) => tank.testing)
|
||||
.sort((a, b) => b.tier - a.tier);
|
||||
const mostPlayedTanks = tankAverages
|
||||
.sort((a, b) => b.samples.d_1 - a.samples.d_1)
|
||||
.map(({ id }) => tankDefinitions.tanks[id])
|
||||
.filter((tank) => tank !== undefined && !tank.testing);
|
||||
const popularTanks = [...testTanks, ...mostPlayedTanks].slice(0, 8);
|
||||
const auth = await google.auth.getClient({
|
||||
keyFile: import.meta.env.GOOGLE_APPLICATION_CREDENTIALS,
|
||||
scopes: ['https://www.googleapis.com/auth/analytics.readonly'],
|
||||
});
|
||||
const analytics = google.analyticsdata({ version: 'v1beta', auth });
|
||||
const report = await analytics.properties.runReport({
|
||||
property: `properties/${assertSecret(import.meta.env.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID)}`,
|
||||
requestBody: {
|
||||
dateRanges: [{ startDate: '7daysAgo', endDate: 'today' }],
|
||||
dimensions: [{ name: 'pagePath' }],
|
||||
metrics: [{ name: 'screenPageViews' }],
|
||||
orderBys: [{ metric: { metricName: 'screenPageViews' }, desc: true }],
|
||||
dimensionFilter: {
|
||||
filter: {
|
||||
fieldName: 'pagePath',
|
||||
stringFilter: { matchType: 'BEGINS_WITH', value: '/tools/tankopedia/' },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!report.data.rows) {
|
||||
throw new Error('No rows in report');
|
||||
}
|
||||
|
||||
const hotTanks = report.data.rows
|
||||
.filter(
|
||||
(row) =>
|
||||
row.dimensionValues &&
|
||||
row.dimensionValues[0].value &&
|
||||
row.dimensionValues[0].value !== '/tools/tankopedia/' &&
|
||||
row.metricValues &&
|
||||
row.metricValues[0].value,
|
||||
)
|
||||
.map((row) => ({
|
||||
id: Number(
|
||||
row.dimensionValues![0].value!.match(
|
||||
/\/tools\/tankopedia\/(\d+)\/?/,
|
||||
)?.[1],
|
||||
),
|
||||
views: Number(row.metricValues![0].value!),
|
||||
}))
|
||||
.filter((row) => {
|
||||
if (!(row.id in tankDefinitions.tanks)) {
|
||||
// console.log('Unknown tank', row.id);
|
||||
}
|
||||
|
||||
return row.id in tankDefinitions.tanks;
|
||||
})
|
||||
.slice(0, 16)
|
||||
.map(({ id, views }) => ({
|
||||
tank: tankDefinitions.tanks[id],
|
||||
views,
|
||||
}));
|
||||
|
||||
export function HomePageHotTanks() {
|
||||
return (
|
||||
@@ -36,15 +73,26 @@ export function HomePageHotTanks() {
|
||||
|
||||
function Content() {
|
||||
return (
|
||||
<Flex direction="column" gap="5" pt="4" pb="8">
|
||||
<Flex direction="column" pt="4" pb="8">
|
||||
<Heading align="center" size="5">
|
||||
Popular tanks
|
||||
</Heading>
|
||||
<Text color="gray" align="center" mb="5" size="2">
|
||||
Last 7 days
|
||||
</Text>
|
||||
|
||||
<Flex justify="center" gap="4" wrap="wrap">
|
||||
{popularTanks.map((tank) => (
|
||||
<Box width="7rem" key={tank.id}>
|
||||
<TankCard tank={tank} />
|
||||
{hotTanks.map((row) => (
|
||||
<Box width="7rem" key={row.tank.id}>
|
||||
<TankCard
|
||||
tank={row.tank}
|
||||
discriminator={
|
||||
<Flex align="center" gap="1" justify="center">
|
||||
<EyeOpenIcon />
|
||||
{Math.round(row.views * (30 / 7)).toLocaleString('en-US')}
|
||||
</Flex>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</Flex>
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
---
|
||||
import { assertSecret } from '@blitzkit/core';
|
||||
---
|
||||
|
||||
{/* google analytics */}
|
||||
<script
|
||||
is:inline
|
||||
async
|
||||
src="https://www.googletagmanager.com/gtag/js?id=G-GL2JVHCGPQ"></script>
|
||||
src={`https://www.googletagmanager.com/gtag/js?id=${assertSecret(
|
||||
import.meta.env.PUBLIC_GOOGLE_TAG_MANAGER_ID,
|
||||
)}`}></script>
|
||||
<script is:inline>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
|
||||
@@ -10,7 +16,7 @@
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-GL2JVHCGPQ');
|
||||
gtag('config', assertSecret(import.meta.env.PUBLIC_GOOGLE_TAG_MANAGER_ID));
|
||||
</script>
|
||||
|
||||
{/* nitro ads */}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { asset, tankIcon, TankType, type TankDefinition } from '@blitzkit/core';
|
||||
import { Flex, Link, Text, type TextProps } from '@radix-ui/themes';
|
||||
import { uniq } from 'lodash-es';
|
||||
import { forwardRef } from 'react';
|
||||
import { forwardRef, type ReactNode } from 'react';
|
||||
import { TankopediaPersistent } from '../../stores/tankopediaPersistent';
|
||||
import { classIcons } from '../ClassIcon';
|
||||
import { MAX_RECENTLY_VIEWED } from '../TankSearch/constants';
|
||||
@@ -10,7 +10,7 @@ import './index.css';
|
||||
type TankCardProps = TextProps & {
|
||||
tank: TankDefinition;
|
||||
onTankSelect?: (tank: TankDefinition) => void;
|
||||
discriminator?: string;
|
||||
discriminator: ReactNode;
|
||||
noLink?: boolean;
|
||||
};
|
||||
|
||||
|
||||
+13
-14
@@ -108,7 +108,6 @@ export function SceneProps() {
|
||||
let { dispersion } = characteristics;
|
||||
const turretRotationSpeed = degToRad(characteristics.turretTraverseSpeed);
|
||||
const gunRotationSpeed = degToRad(characteristics.gunTraverseSpeed);
|
||||
|
||||
const mockTank = useLoader(GLTFLoader, asset(`3d/tanks/models/12305.glb`));
|
||||
const texture = useLoader(TextureLoader, imgur('C28Z8nU'));
|
||||
texture.anisotropy = 2;
|
||||
@@ -119,6 +118,7 @@ export function SceneProps() {
|
||||
});
|
||||
|
||||
let lastTime = 0;
|
||||
let lastPenalty = 0;
|
||||
let DEBUG_disable = false;
|
||||
|
||||
useFrame(({ raycaster, camera, clock, gl }) => {
|
||||
@@ -160,22 +160,22 @@ export function SceneProps() {
|
||||
const desiredYaw = Math.atan2(-direction.x, -direction.z);
|
||||
const deltaPitch = desiredPitch - lastPitch;
|
||||
const deltaYaw = desiredYaw - lastYaw;
|
||||
const maxDeltaPitch =
|
||||
Math.sign(deltaPitch) *
|
||||
Math.min(gunRotationSpeed * deltaT, Math.abs(deltaPitch));
|
||||
const maxDeltaYaw =
|
||||
Math.sign(deltaYaw) *
|
||||
Math.min(turretRotationSpeed * deltaT, Math.abs(deltaYaw));
|
||||
const deltaAngle = Math.sqrt(deltaPitch ** 2 + deltaYaw ** 2);
|
||||
const clampedDeltaAngle = Math.min(gunRotationSpeed * deltaT, deltaAngle);
|
||||
const clampedDeltaPitch = deltaPitch * (deltaAngle / clampedDeltaAngle);
|
||||
const clampedDeltaYaw = deltaYaw * (deltaAngle / clampedDeltaAngle);
|
||||
|
||||
const [pitch, yaw] = applyPitchYawLimits(
|
||||
lastPitch + maxDeltaPitch,
|
||||
lastYaw + maxDeltaYaw,
|
||||
lastPitch + clampedDeltaPitch,
|
||||
lastYaw + clampedDeltaYaw,
|
||||
gunModelDefinition.pitch,
|
||||
turretModelDefinition.yaw,
|
||||
);
|
||||
|
||||
const deltaRotation = Math.abs(maxDeltaPitch) + Math.abs(maxDeltaYaw);
|
||||
const rotationSpeed = deltaRotation;
|
||||
const actualDeltaPitch = pitch - lastPitch;
|
||||
const actualDeltaYaw = yaw - lastYaw;
|
||||
const deltaRotation = Math.abs(actualDeltaPitch) + Math.abs(actualDeltaYaw);
|
||||
const rotationSpeed = deltaRotation / deltaT;
|
||||
const turretTraversePenalty =
|
||||
characteristics.dispersionTurretTraversing * radToDeg(rotationSpeed);
|
||||
|
||||
@@ -186,9 +186,8 @@ export function SceneProps() {
|
||||
dispersionMod * Math.exp(-deltaT / characteristics.aimTime) -
|
||||
dispersionMod;
|
||||
|
||||
dispersion += penalty + dispersionModDischarged;
|
||||
|
||||
document.title = `${dispersion.toFixed(3)} (${penalty.toFixed(3)})`;
|
||||
dispersion += penalty - lastPenalty + dispersionModDischarged;
|
||||
lastPenalty = penalty;
|
||||
|
||||
modelTransformEvent.emit({ pitch, yaw });
|
||||
lastPitch = pitch;
|
||||
|
||||
Reference in new issue
Block a user