Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion apps/discord-bot/src/commands/bedwars/bedwars.profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@
* https://github.com/Statsify/statsify/blob/main/LICENSE
*/

import { BedWarsModes, FormattedGame, type GameMode } from "@statsify/schemas";
import {
Badge,
BarChart,
Container,
Footer,
Graph,
Header,
Historical,
SidebarItem,
StatDelta,
Table,
formatProgression,
} from "#components";
import { BedWarsModes, FormattedGame, type GameMode } from "@statsify/schemas";
import type { BaseProfileProps } from "#commands/base.hypixel-command";

export interface BedWarsProfileProps extends BaseProfileProps {
Expand All @@ -35,6 +39,22 @@ export const BedWarsProfile = ({
}: BedWarsProfileProps) => {
const { bedwars } = player.stats;
const stats = bedwars[mode.api];
const modeWins = [
{ label: "Solo", value: bedwars.solo.wins || 0, color: "#4e79a7" },
{ label: "Doubles", value: bedwars.doubles.wins || 0, color: "#76b7b2" },
{ label: "3s", value: bedwars.threes.wins || 0, color: "#f28e2b" },
{ label: "4s", value: bedwars.fours.wins || 0, color: "#e15759" },
{ label: "4v4", value: bedwars["4v4"].wins || 0, color: "#9ca3af" },
];
const ratioPoints = [
{ label: t("stats.wlr"), value: stats.wlr || 0 },
{ label: t("stats.fkdr"), value: stats.fkdr || 0 },
{ label: t("stats.kdr"), value: stats.kdr || 0 },
{ label: t("stats.bblr"), value: stats.bblr || 0 },
];
const ratioAverage =
ratioPoints.reduce((total, point) => total + point.value, 0) / ratioPoints.length;
const fkdrDelta = (stats.fkdr || 0) - (bedwars.overall.fkdr || 0);

const sidebar: SidebarItem[] = [
[t("stats.tokens"), t(bedwars.tokens), "§2"],
Expand Down Expand Up @@ -113,6 +133,32 @@ export const BedWarsProfile = ({
exp={bedwars.exp}
/>
</Table.table>
<div width="100%">
<box width="1/2" direction="column" location="left">
<div width="100%">
<text>§lMode Wins</text>
<div width="remaining" />
<Badge>§bVisual</Badge>
</div>
<BarChart items={modeWins} />
</box>
<box width="1/2" direction="column" location="left">
<div width="100%">
<text>§lRatio Profile</text>
<div width="remaining" />
<StatDelta value={fkdrDelta} />
</div>
<Graph
points={ratioPoints}
height={112}
min={0}
max={Math.max(1, ...ratioPoints.map((point) => point.value))}
referenceValue={ratioAverage}
color="#9ca3af"
fillColor="rgba(156, 163, 175, 0.12)"
/>
</box>
</div>
<Footer logo={logo} user={user} />
</Container>
);
Expand Down
78 changes: 78 additions & 0 deletions apps/discord-bot/src/commands/demo/demo.command.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* Copyright (c) Statsify
*
* This source code is licensed under the GNU GPL v3 license found in the
* LICENSE file in the root directory of this source tree.
* https://github.com/Statsify/statsify/blob/main/LICENSE
*/

import {
ApiService,
Command,
CommandContext,
PaginateService,
PlayerArgument,
} from "@statsify/discord";
import { Container } from "typedi";
import { getBackground, getLogo } from "@statsify/assets";
import { renderChartsPage } from "./pages/charts-graphs.js";
import { renderComparisonPage } from "./pages/comparison-ranking.js";
import { renderRadialPage } from "./pages/radial-distribution.js";
import { renderStatPrimitivesPage } from "./pages/stat-primitives.js";
import { renderThemingPage } from "./pages/theming-kitchen.js";
import type { Image } from "skia-canvas";
import type { Player, User } from "@statsify/schemas";

export interface DemoPageProps {
player: Player;
skin: Image;
logo: Image;
badge?: Image;
background: Image;
user: User | null;
}

const DEFAULT_PLAYER = "hypixel";

@Command({
description: () => "Component showcase (rendering demo)",
args: [new PlayerArgument("player", false)],
cooldown: 10,
})
export class DemoCommand {
private readonly apiService: ApiService;
private readonly paginateService: PaginateService;

public constructor() {
this.apiService = Container.get(ApiService);
this.paginateService = Container.get(PaginateService);
}

public async run(context: CommandContext) {
const user = context.getUser();
const playerTag = context.option<string | undefined>("player") ?? DEFAULT_PLAYER;

const [player, background, logo] = await Promise.all([
this.apiService.getPlayer(playerTag, user),
getBackground("bedwars", "overall"),
getLogo(user),
]);

const [skin, badge] = await Promise.all([
this.apiService.getPlayerSkin(player.uuid, user),
this.apiService.getUserBadge(player.uuid),
]);

const props: DemoPageProps = { player, skin, logo, badge, background, user };

const pages = [
{ label: () => "Charts & Graphs", generator: () => renderChartsPage(props) },
{ label: () => "Radial & Distribution", generator: () => renderRadialPage(props) },
{ label: () => "Stat Primitives", generator: () => renderStatPrimitivesPage(props) },
{ label: () => "Comparison & Ranking", generator: () => renderComparisonPage(props) },
{ label: () => "Theming & Kitchen Sink", generator: () => renderThemingPage(props) },
];

return this.paginateService.paginate(context, pages);
}
}
104 changes: 104 additions & 0 deletions apps/discord-bot/src/commands/demo/pages/charts-graphs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* Copyright (c) Statsify
*
* This source code is licensed under the GNU GPL v3 license found in the
* LICENSE file in the root directory of this source tree.
* https://github.com/Statsify/statsify/blob/main/LICENSE
*/

import { BarChart, Container, Footer, GAME_COLORS, Graph, Header } from "#components";
import { getTheme } from "#themes";
import { render } from "@statsify/rendering";
import type { DemoPageProps } from "../demo.command.js";

const seededPoints = (uuid: string, base: number, count: number) => {
const hex = uuid.replace(/-/g, "");
return Array.from({ length: count }, (_, i) => {
const h = hex.slice((i * 2) % 32, (i * 2) % 32 + 2);
const jitter = (Number.parseInt(h, 16) / 255 - 0.5) * 0.4;
return { label: `W${i + 1}`, value: Math.max(0, base * (1 + jitter)) };
});
};

export const renderChartsPage = ({
player,
skin,
logo,
badge,
background,
user,
}: DemoPageProps) => {
const bw = player.stats.bedwars;
const overall = bw.overall;

const fkdrPoints = seededPoints(player.uuid, overall.fkdr, 10);
const wlrPoints = seededPoints(player.uuid, overall.wlr, 10);

const modeWins = [
{ label: "Solo", value: bw.solo.wins || 0, color: GAME_COLORS.bedwars },
{ label: "Doubles", value: bw.doubles.wins || 0, color: "#f97316" },
{ label: "3s", value: bw.threes.wins || 0, color: "#facc15" },
{ label: "4s", value: bw.fours.wins || 0, color: "#a78bfa" },
{ label: "4v4", value: bw["4v4"].wins || 0, color: "#38bdf8" },
];

const activityData = Array.from({ length: 12 }, (_, i) => {
const h = player.uuid.replace(/-/g, "").slice(i * 2, i * 2 + 2);
return Math.round((Number.parseInt(h, 16) / 255) * 80);
});

const canvas = render(
<Container background={background}>
<Header
skin={skin}
name={player.prefixName}
badge={badge}
title="§lCharts §f& §lGraphs"
description="§7Showcasing §fGraph§7, §fBarChart§7, §fsparkbar"
time="LIVE"
sidebar={[]}
/>
<div width="100%" direction="row">
<div width="1/2" direction="column">
<box width="100%" direction="column" padding={{ top: 5, bottom: 5, left: 8, right: 8 }}>
<text margin={{ top: 0, bottom: 4, left: 0, right: 0 }}>§lFKDR §8vs §7WLR Trend</text>
<Graph
points={fkdrPoints}
series={[{ points: wlrPoints, color: "#4ade80", fillColor: "rgba(74,222,128,0.12)" }]}
color={GAME_COLORS.bedwars}
fillColor="rgba(239,68,68,0.14)"
smooth
height={80}
showLastValue
/>
</box>
<box width="100%" direction="column" padding={{ top: 5, bottom: 5, left: 8, right: 8 }}>
<text margin={{ top: 0, bottom: 4, left: 0, right: 0 }}>§lRecent Activity</text>
<sparkbar
data={activityData}
height={36}
color={GAME_COLORS.bedwars}
highlightLast
highlightColor="#fbbf24"
width="100%"
/>
</box>
</div>
<div width="1/2" direction="column">
<box width="100%" direction="column" padding={{ top: 5, bottom: 5, left: 8, right: 8 }}>
<text margin={{ top: 0, bottom: 4, left: 0, right: 0 }}>§lMode Wins (Vertical)</text>
<BarChart items={modeWins} sort={false} />
</box>
<box width="100%" direction="column" padding={{ top: 5, bottom: 5, left: 8, right: 8 }}>
<text margin={{ top: 0, bottom: 4, left: 0, right: 0 }}>§lMode Wins (Horizontal)</text>
<BarChart items={modeWins} sort={false} orientation="horizontal" gradientFill />
</box>
</div>
</div>
<Footer logo={logo} user={user} />
</Container>,
getTheme(user)
);

return canvas;
};
127 changes: 127 additions & 0 deletions apps/discord-bot/src/commands/demo/pages/comparison-ranking.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/**
* Copyright (c) Statsify
*
* This source code is licensed under the GNU GPL v3 license found in the
* LICENSE file in the root directory of this source tree.
* https://github.com/Statsify/statsify/blob/main/LICENSE
*/

import {
Container,
Footer,
GAME_COLORS,
Header,
HeatmapChart,
LeaderboardRow,
VersusPanel,
} from "#components";
import { getTheme } from "#themes";
import { render } from "@statsify/rendering";
import type { DemoPageProps } from "../demo.command.js";

const HYPIXEL_BW_AVERAGES = {
fkdr: 1.2,
wlr: 0.8,
kdr: 0.9,
bblr: 0.7,
};

const DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];

export const renderComparisonPage = ({
player,
skin,
logo,
badge,
background,
user,
}: DemoPageProps) => {
const bw = player.stats.bedwars;
const overall = bw.overall;

const heatCells = Array.from({ length: 7 * 8 }, (_, i) => {
const hex = player.uuid.replace(/-/g, "");
const h = hex.slice(i % 32, i % 32 + 2);
return Math.round((Number.parseInt(h, 16) / 255) * 60);
});

const leaderboardEntries = [
{ rank: 1, name: "§6§lHyperion_Max", value: 28.4 },
{ rank: 2, name: "§b§lSkyblaze", value: 21.7 },
{ rank: 3, name: "§a§lVineBreaker", value: 18.3 },
{ rank: 4, name: player.username, value: overall.fkdr },
{ rank: 5, name: "§7VoidStrider", value: Math.max(0, overall.fkdr - 0.8) },
].sort((a, b) => b.value - a.value).map((e, i) => ({ ...e, rank: i + 1 }));

const leftPlayer = {
name: player.prefixName,
stats: [
{ label: "FKDR", leftValue: overall.fkdr, rightValue: HYPIXEL_BW_AVERAGES.fkdr },
{ label: "WLR", leftValue: overall.wlr, rightValue: HYPIXEL_BW_AVERAGES.wlr },
{ label: "KDR", leftValue: overall.kdr, rightValue: HYPIXEL_BW_AVERAGES.kdr },
{ label: "BBLR", leftValue: overall.bblr, rightValue: HYPIXEL_BW_AVERAGES.bblr },
],
};

const rightPlayer = {
name: "§7Hypixel Average",
stats: [
{ label: "FKDR", leftValue: overall.fkdr, rightValue: HYPIXEL_BW_AVERAGES.fkdr },
{ label: "WLR", leftValue: overall.wlr, rightValue: HYPIXEL_BW_AVERAGES.wlr },
{ label: "KDR", leftValue: overall.kdr, rightValue: HYPIXEL_BW_AVERAGES.kdr },
{ label: "BBLR", leftValue: overall.bblr, rightValue: HYPIXEL_BW_AVERAGES.bblr },
],
};

const canvas = render(
<Container background={background}>
<Header
skin={skin}
name={player.prefixName}
badge={badge}
title="§lComparison §f& §lRanking"
description="§7Showcasing §fVersusPanel§7, §fLeaderboardRow§7, §fHeatmapChart"
time="LIVE"
sidebar={[]}
/>
<div width="100%" direction="row">
<div width="1/2" direction="column">
<box width="100%" direction="column" padding={{ top: 5, bottom: 5, left: 8, right: 8 }}>
<text margin={{ top: 0, bottom: 4, left: 0, right: 0 }}>§lvs Hypixel Average</text>
<VersusPanel left={leftPlayer} right={rightPlayer} />
</box>
<box width="100%" direction="column" padding={{ top: 5, bottom: 5, left: 8, right: 8 }}>
{[
<text margin={{ top: 0, bottom: 4, left: 0, right: 0 }}>§lFKDR Leaderboard (Simulated)</text>,
...leaderboardEntries.map((e) => (
<LeaderboardRow
rank={e.rank}
name={e.name}
value={e.value.toFixed(2)}
accentColor={GAME_COLORS.bedwars}
/>
)),
]}
</box>
</div>
<div width="1/2" direction="column">
<box width="100%" direction="column" padding={{ top: 5, bottom: 5, left: 8, right: 8 }}>
<text margin={{ top: 0, bottom: 4, left: 0, right: 0 }}>§lWeekly Activity Heatmap §8(seeded)</text>
<HeatmapChart
cells={heatCells}
cols={8}
rowLabels={DAYS}
highColor={GAME_COLORS.bedwars}
cellSize={14}
cellGap={3}
/>
</box>
</div>
</div>
<Footer logo={logo} user={user} />
</Container>,
getTheme(user)
);

return canvas;
};
Loading
Loading