diff --git a/README.md b/README.md index d8f52d033..6c7ae732f 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,42 @@ # blitzkrieg -A Discord bot for anything and everything related to World of Tanks Blitz. +Tools for everything World of Tanks Blitz -## Invite the Bot +## Invite the bot -Join the [Discord server](https://discord.gg/nDt7AjGJQH), click on the bot's profile, and click "Add to Server". +Go to [application directory for Blitzkrieg](https://discord.com/application-directory/1097673957865443370) and click "Add to Server." Voila! You're ready to go. -## Using +![Blitzkrieg application directory](https://i.imgur.com/JlEA0J1.png) -### Installing dependencies +## Visit the website + +### https://blitz-krieg.vercel.app/ + +![Blitzkrieg website](https://i.imgur.com/QOXUP63.png) + +## Visit the documentation website + +### https://tresabhi.github.io/blitzkrieg/ + +![Blitzkrieg documentation website](https://i.imgur.com/mCZCZEI.png) + +## Run the bot + +### Install dependencies ```bash yarn ``` -### Running +### Create `.env` + +```env +GH_TOKEN={YOUR_GITHUB_TOKEN_HERE} +DISCORD_TOKEN={YOUR_DISCORD_TOKE_HERE} +``` + +### Run the command ```bash -yarn start discord-token= wargaming-application-id= +yarn dev:bot ``` diff --git a/docs/changelogs/1.13.1.md b/docs/changelogs/1.13.1.md new file mode 100644 index 000000000..798e5ea1d --- /dev/null +++ b/docs/changelogs/1.13.1.md @@ -0,0 +1,17 @@ +# Blitzkrieg 1.13.1 + +The quickest minor update in town 🤠 + +## Changes + +- Added a title, description, and icon to the website +- Added display options to the session tracker + +## Fixes + +- Center aligned stats in breakdown to work better on low width devices +- Center aligned the Blitzkrieg logo in the navbar + +## Technical Changes + +- Added up-to-date information to `README.md` and embed docs diff --git a/docs/guide/embeds.md b/docs/guide/embeds.md index b663d0bfc..076363e14 100644 --- a/docs/guide/embeds.md +++ b/docs/guide/embeds.md @@ -1,23 +1,23 @@ # Embeds -Embeds are generated by a few commands as a URL that can be loaded in a browser/streaming software. +Embeds are URLs generated by commands or tools that represent the "naked" version of the respective command or tool ## Creating Embeds -1. Use a command of your liking (for example, `/stats`). -2. Customize the command however you like and the URL generated for the embed will match your needs. -3. Send the command and when the bot is "done thinking," an "Embed" button will appear below the bot's reply. +1. Use a command or tool and click on "Embed" if it's available +2. Make sure to tweak the settings of the command or tool because embeds replicate everything ## Using Embeds in Streaming Softwares -1. Click on the "Embed" button. -2. Copy the URL from your browser. -3. Paste the URL into a Browser Source. -4. Set the refresh rate/FPS to something very low like `0.0167` (that's 1 frame every 60 seconds). +1. Click on the "Embed" button + - If this was generated by a command, copy the URL from the browser + - If this was generated by a tool, the URL has been copied to your clipboard already +2. Paste this URL into your streaming service as a "Browser Source" + - Tool embeds use local storage so data won't be transferred from you browser to your streaming software + - Make sure to visit the tool's page in your streaming software (using the "Interact" feature) and then open the embed + - Set your browser source refresh rate to `1Hz` because that's how fast embeds refresh ## How Embeds Behave -- They load like any normal website albeit with a significant delay. -- They auto refresh every `60s`. -- All embeds and images generated by Blitzkrieg have a width of `640px` but the height varies and must be found manually through trial and error. - - It's okay to have a Browser Source that has a height larger than needed as Blitzkrieg embeds are transparent. +- They load like any normal website +- They auto refresh every `1s` diff --git a/package.json b/package.json index 44e41cff3..97e38c2e1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "blitzkrieg", - "version": "1.13.0", - "description": "🎉 All-in-one Discord bot for everything World of Tanks Blitz", + "version": "1.13.1", + "description": "🎉 Tools for everything World of Tanks Blitz", "main": "src/server.ts", "type": "module", "scripts": { @@ -37,6 +37,7 @@ "esbuild": "^0.19.3", "express": "^4.18.2", "fuzzysort": "^2.0.4", + "immer": "^10.0.2", "lodash": "^4.17.21", "markdown-escape": "^2.0.0", "next": "^13.5.2", diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 000000000..f2a815f31 Binary files /dev/null and b/public/favicon.ico differ diff --git a/src/app/embeds/session/page.tsx b/src/app/embeds/session/page.tsx index d04180de6..48d41794d 100644 --- a/src/app/embeds/session/page.tsx +++ b/src/app/embeds/session/page.tsx @@ -12,6 +12,7 @@ import { TankopediaEntry, tankopedia } from '../../../core/blitz/tankopedia'; import { tankAverages } from '../../../core/blitzstars/tankAverages'; import { useSession } from '../../../stores/session'; import { IndividualTankStats, TanksStats } from '../../../types/tanksStats'; +import { CustomColumnDisplay } from '../../tools/session/components/CustomColumn'; const REFRESH_RATE = 1 / 5; @@ -139,136 +140,171 @@ export default function SessionPage() { return () => clearInterval(interval); }, [session]); + const totalRowStats: Record< + CustomColumnDisplay, + Breakdown.RowStatItem | undefined + > = { + battles: { + title: 'Battles', + current: sessionBattles?.toLocaleString(), + career: tankStatsArray + .reduce((accumulator, stats) => accumulator + stats.all.battles, 0) + .toLocaleString(), + }, + winrate: { + title: 'Winrate', + current: sessionBattles ? `${(100 * sessionWinrate).toFixed(2)}%` : '--', + career: `${(100 * careerWinrate).toFixed(2)}%`, + delta: sessionBattles === 0 ? 0 : sessionWinrate - careerWinrate, + }, + wn8: (() => { + if (!diff) return undefined; + + const battlesWithWN8 = diff.list.reduce( + (accumulator, { currentWN8, stats }) => + (currentWN8 === undefined ? 0 : stats.all.battles) + accumulator, + 0, + ); + const currentWN8 = + diff.list.reduce( + (accumulator, { currentWN8, stats }) => + stats.all.battles * (currentWN8 ?? 0) + accumulator, + 0, + ) / battlesWithWN8!; + + return { + title: 'WN8', + current: battlesWithWN8 + ? Math.round(currentWN8!).toLocaleString() + : '--', + percentile: battlesWithWN8 ? getWN8Percentile(currentWN8!) : undefined, + career: Math.round(diff.careerWN8).toLocaleString(), + }; + })(), + damage: { + title: 'Damage', + delta: sessionBattles === 0 ? 0 : currentDamage - careerDamage, + current: sessionBattles + ? Math.round(currentDamage).toLocaleString() + : '--', + career: Math.round(careerDamage).toLocaleString(), + }, + none: undefined, + }; + return ( - {session.isTracking && since !== undefined && diff !== undefined && ( - accumulator + stats.all.battles, - 0, - ) - .toLocaleString(), - }, - { - title: 'Winrate', - current: sessionBattles - ? `${(100 * sessionWinrate).toFixed(2)}%` - : '--', - career: `${(100 * careerWinrate).toFixed(2)}%`, - delta: sessionBattles === 0 ? 0 : sessionWinrate - careerWinrate, - }, - (() => { - const battlesWithWN8 = diff.list.reduce( - (accumulator, { currentWN8, stats }) => - (currentWN8 === undefined ? 0 : stats.all.battles) + - accumulator, - 0, - ); - const currentWN8 = - diff.list.reduce( - (accumulator, { currentWN8, stats }) => - stats.all.battles * (currentWN8 ?? 0) + accumulator, - 0, - ) / battlesWithWN8; + {session.showTotal && + session.isTracking && + since !== undefined && + diff !== undefined && ( + { + const rowStat = totalRowStats[customColumn.display]; + + if (!rowStat) return undefined; return { - title: 'WN8', - current: battlesWithWN8 - ? Math.round(currentWN8).toLocaleString() - : '--', - percentile: battlesWithWN8 - ? getWN8Percentile(currentWN8) - : undefined, - career: Math.round(diff.careerWN8).toLocaleString(), + ...rowStat, + delta: + customColumn.showDelta && rowStat.percentile === undefined + ? rowStat.delta + : undefined, }; - })(), - { - title: 'Damage', - delta: sessionBattles === 0 ? 0 : currentDamage - careerDamage, - current: sessionBattles - ? Math.round(currentDamage).toLocaleString() - : '--', - career: Math.round(careerDamage).toLocaleString(), - }, - ]} - /> - )} + })} + /> + )} {diff?.list .sort((a, b) => b.stats.last_battle_time - a.stats.last_battle_time) - .map(({ stats, tankopedia, career, careerWN8, currentWN8 }) => ( - { - if (tankopedia?.is_collectible) return TreeTypeEnum.Collector; - if (tankopedia?.is_premium) return TreeTypeEnum.Premium; - })()} - stats={[ - { - title: 'Battles', - current: stats.all.battles.toLocaleString(), - career: career.all.battles.toLocaleString(), - }, - { - title: 'Winrate', - current: `${( - 100 * - (stats.all.wins / stats.all.battles) - ).toFixed(2)}%`, - career: `${( - 100 * - (career.all.wins / career.all.battles) - ).toFixed(2)}%`, - delta: - stats.all.wins / stats.all.battles - - career.all.wins / career.all.battles, - }, - { - title: 'WN8', - current: - currentWN8 === undefined - ? undefined - : Math.round(currentWN8).toLocaleString(), - career: - careerWN8 === undefined - ? undefined - : Math.round(careerWN8).toLocaleString(), - percentile: - currentWN8 === undefined - ? undefined - : getWN8Percentile(currentWN8), - }, - { - title: 'Damage', - current: Math.round( - stats.all.damage_dealt / stats.all.battles, - ).toLocaleString(), - career: Math.round( - career.all.damage_dealt / career.all.battles, - ).toLocaleString(), - delta: - stats.all.damage_dealt / stats.all.battles - - career.all.damage_dealt / career.all.battles, - }, - ]} - /> - ))} + .map(({ stats, tankopedia, career, careerWN8, currentWN8 }) => { + const rowStats: Record< + CustomColumnDisplay, + Breakdown.RowStatItem | undefined + > = { + battles: { + title: 'Battles', + current: stats.all.battles.toLocaleString(), + career: career.all.battles.toLocaleString(), + }, + winrate: { + title: 'Winrate', + current: `${(100 * (stats.all.wins / stats.all.battles)).toFixed( + 2, + )}%`, + career: `${(100 * (career.all.wins / career.all.battles)).toFixed( + 2, + )}%`, + delta: + stats.all.wins / stats.all.battles - + career.all.wins / career.all.battles, + }, + wn8: { + title: 'WN8', + current: + currentWN8 === undefined + ? undefined + : Math.round(currentWN8).toLocaleString(), + career: + careerWN8 === undefined + ? undefined + : Math.round(careerWN8).toLocaleString(), + percentile: + currentWN8 === undefined + ? undefined + : getWN8Percentile(currentWN8), + }, + damage: { + title: 'Damage', + current: Math.round( + stats.all.damage_dealt / stats.all.battles, + ).toLocaleString(), + career: Math.round( + career.all.damage_dealt / career.all.battles, + ).toLocaleString(), + delta: + stats.all.damage_dealt / stats.all.battles - + career.all.damage_dealt / career.all.battles, + }, + none: undefined, + }; + + return ( + { + if (tankopedia?.is_collectible) return TreeTypeEnum.Collector; + if (tankopedia?.is_premium) return TreeTypeEnum.Premium; + })()} + stats={session.customColumns.map((customColumn) => { + const rowStat = rowStats[customColumn.display]; + + if (!rowStat) return undefined; + + return { + ...rowStat, + delta: + customColumn.showDelta && rowStat.percentile === undefined + ? rowStat.delta + : undefined, + }; + })} + /> + ); + })} ); } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 1f54bb940..2c03919e2 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -25,6 +25,14 @@ export default function RootLayout({ children }: RootLayoutProps) { return ( + + Blitzkrieg + + + state.customColumns[column]); + + return ( + + Column {column + 1} + + + Display + + + mutateSession((draft) => { + draft.customColumns[column].display = + event as CustomColumnDisplay; + }) + } + > + + Battles + + + Winrate + + WN8 + Damage + None + + + {!NO_DELTA.includes(customColumn.display) && ( + <> + + + + mutateSession((draft) => { + draft.customColumns[column].showDelta = checked; + }) + } + > + Show delta + + + )} + + + ); +} diff --git a/src/app/tools/session/page.tsx b/src/app/tools/session/page.tsx index e267eee47..d11231292 100644 --- a/src/app/tools/session/page.tsx +++ b/src/app/tools/session/page.tsx @@ -1,6 +1,11 @@ 'use client'; -import { CopyIcon, PersonIcon, ReloadIcon } from '@radix-ui/react-icons'; +import { + CaretDownIcon, + CopyIcon, + PersonIcon, + ReloadIcon, +} from '@radix-ui/react-icons'; import { AlertDialog, Button, @@ -23,10 +28,9 @@ import listPlayers, { import { useSession } from '../../../stores/session'; import { NormalizedTankStats, TanksStats } from '../../../types/tanksStats'; import SessionPage from '../../embeds/session/page'; +import { CustomColumn } from './components/CustomColumn'; import * as styles from './page.css'; -// 1 once per 5 seconds - export default function Page() { const input = useRef(null); const [searchResults, setSearchResults] = useState< @@ -150,6 +154,7 @@ export default function Page() { + + + + + + + + + + + + + + + + useSession.setState({ showTotal: checked }) + } + > + Show total + + + useSession.setState({ showCareer: checked }) + } + > + Show career + + + diff --git a/src/components/Breakdown/components/Row.tsx b/src/components/Breakdown/components/Row.tsx index 5acd25cf8..9c4a7281f 100644 --- a/src/components/Breakdown/components/Row.tsx +++ b/src/components/Breakdown/components/Row.tsx @@ -4,6 +4,14 @@ import { theme } from '../../../stitches.config'; import { TREE_TYPE_ICONS, TreeTypeEnum } from '../../Tanks'; import { RowStat } from './RowStat'; +export interface RowStatItem { + title: string; + current?: number | string; + career?: number | string; + delta?: number; + percentile?: Percentile; +} + interface RowProps { type?: 'tank' | 'summary'; title: string; @@ -11,13 +19,7 @@ interface RowProps { treeType?: TreeTypeEnum; tankType?: TankType; - stats: { - title: string; - current?: number | string; - career?: number | string; - delta?: number; - percentile?: Percentile; - }[]; + stats: (RowStatItem | undefined)[]; } export function Row(props: RowProps) { @@ -77,16 +79,19 @@ export function Row(props: RowProps) { paddingBottom: 8, }} > - {props.stats.map((row, index) => ( - - ))} + {props.stats.map( + (row, index) => + row && ( + + ), + )} ); diff --git a/src/components/Breakdown/components/RowStat.tsx b/src/components/Breakdown/components/RowStat.tsx index c77ef784e..3c0cda7c0 100644 --- a/src/components/Breakdown/components/RowStat.tsx +++ b/src/components/Breakdown/components/RowStat.tsx @@ -55,6 +55,7 @@ export function RowStat({ color: theme.colors.textHighContrast, fontWeight: 'bold', fontSize: 16, + textAlign: 'center', }} > {value} @@ -62,7 +63,13 @@ export function RowStat({ {!minimized && ( - + {name} )} diff --git a/src/components/Navbar/index.tsx b/src/components/Navbar/index.tsx index dd5dbaede..c2236e524 100644 --- a/src/components/Navbar/index.tsx +++ b/src/components/Navbar/index.tsx @@ -19,7 +19,15 @@ export default function Navbar() { {/* */} - + {/*