feat: evolution command for tanks

This commit is contained in:
TresAbhi committed 2023-06-21 01:52:26 +00:00
1 parent 35f0f614af
commit 70145abe86
18 files changed
+633 -90

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@ import { go } from 'fuzzysort';
import errorWithCause from '../process/errorWithCause.js';
import { TANKS, tankopedia } from './tankopedia.js';
export default async function resolveTankId(tank: string) {
export default function resolveTankId(tank: string) {
const number = Number(tank);
if (Number.isNaN(number)) {
+110
View File
@@ -0,0 +1,110 @@
import { BlitzServer } from '../../constants/servers.js';
import { AllStats } from '../../types/accountInfo.js';
import getTankStats from '../blitz/getTankStats.js';
import { emptyAllStats } from './getTankStatsOverTime.js';
export interface TankHistory {
all: AllStats;
last_battle_time: number;
mark_of_mastery: number;
battle_life_time: number;
account_id: number;
tank_id: number;
}
export const emptyTankHistory: TankHistory = {
all: emptyAllStats,
last_battle_time: -Infinity,
mark_of_mastery: 0,
battle_life_time: 0,
account_id: 0,
tank_id: 0,
};
export type TankHistories = TankHistory[];
export interface GetTankHistoriesOptions {
start: number;
end: number;
includeLatestHistories: boolean;
includePreviousHistories: boolean;
tankId?: number;
}
export const getTankHistoriesDefaultOptions: GetTankHistoriesOptions = {
start: -Infinity,
end: Infinity,
includeLatestHistories: false,
includePreviousHistories: false,
};
export default async function getTankHistories(
server: BlitzServer,
id: number,
options?: Partial<GetTankHistoriesOptions>,
) {
const mergedOptions = { ...getTankHistoriesDefaultOptions, ...options };
const tankHistoriesResponse = await fetch(
`https://www.blitzstars.com/api/tankhistories/for/${id}/`,
);
const tankHistories = (await tankHistoriesResponse.json()) as TankHistories;
const compliantTanks: number[] = [];
const lastBattles: Record<number, number> = {};
const lastNonCompliantIndex: Record<number, number> = {};
const hasCompliedOnce: Record<number, boolean> = {};
const filtered = tankHistories.filter(
({ last_battle_time, tank_id, all: { battles } }, index) => {
const complies =
last_battle_time >= mergedOptions.start &&
last_battle_time <= mergedOptions.end &&
(mergedOptions.tankId === undefined ||
tank_id === mergedOptions.tankId);
if (complies && !hasCompliedOnce[tank_id]) {
hasCompliedOnce[tank_id] = true;
compliantTanks.push(tank_id);
}
if (!hasCompliedOnce[tank_id]) lastNonCompliantIndex[tank_id] = index;
lastBattles[tank_id] = battles;
return complies;
},
);
const latestNodes: TankHistories = [];
if (mergedOptions.includeLatestHistories) {
(await getTankStats(server, id)).forEach((tankHistory) => {
if (
tankHistory.all.battles > (lastBattles[tankHistory.tank_id] ?? 0) &&
(mergedOptions.tankId === undefined ||
tankHistory.tank_id === mergedOptions.tankId)
) {
if (!hasCompliedOnce[tankHistory.tank_id]) {
// was played today for the first time... I think???
hasCompliedOnce[tankHistory.tank_id] = true;
compliantTanks.push(tankHistory.tank_id);
}
latestNodes.push(tankHistory);
}
});
}
const previousNodes: TankHistories = [];
if (mergedOptions.includePreviousHistories) {
compliantTanks.forEach((tankId) => {
if (tankId in lastNonCompliantIndex) {
previousNodes.push(
tankHistories[lastNonCompliantIndex[tankId]] ?? {
...emptyTankHistory,
account_id: id,
tank_id: tankId,
},
);
}
});
}
return [...previousNodes, ...filtered, ...latestNodes];
}
+9 -20
View File
@@ -1,15 +1,9 @@
import { BlitzServer } from '../../constants/servers.js';
import { AllStats } from '../../types/accountInfo.js';
import getTankStats from '../blitz/getTankStats.js';
export interface TankHistoryNode {
all: AllStats;
last_battle_time: number;
mark_of_mastery: number;
battle_life_time: number;
account_id: number;
tank_id: number;
}
import getTankHistories, {
TankHistories,
TankHistory,
} from './getTankHistories.js';
export const emptyAllStats: AllStats = {
battles: 0,
@@ -31,7 +25,7 @@ export const emptyAllStats: AllStats = {
wins: 0,
};
export const emptyTankHistoryNode: TankHistoryNode = {
export const emptyTankHistoryNode: TankHistory = {
account_id: 0,
battle_life_time: 0,
last_battle_time: 0,
@@ -40,23 +34,18 @@ export const emptyTankHistoryNode: TankHistoryNode = {
all: emptyAllStats,
};
export type TankHistory = TankHistoryNode[];
export default async function getTankStatsOverTime(
server: BlitzServer,
id: number,
start: number,
end: number,
) {
const latestTankStatsRaw = await getTankStats(server, id);
const tankHistoriesResponse = await fetch(
`https://www.blitzstars.com/api/tankhistories/for/${id}/`,
);
const tankHistories = (await tankHistoriesResponse.json()) as TankHistory;
const history = [...tankHistories, ...latestTankStatsRaw];
const history = await getTankHistories(server, id, {
includeLatestHistories: true,
});
// sort them even though most won't be used
const tankSortedHistory: Record<number, TankHistory> = {};
const tankSortedHistory: Record<number, TankHistories> = {};
const tankIds: number[] = [];
history.forEach((node) => {
if (!tankSortedHistory[node.tank_id]) {
+7 -6
View File
@@ -2,15 +2,16 @@ import {
SlashCommandSubcommandBuilder,
SlashCommandSubcommandGroupBuilder,
} from 'discord.js';
import { PERIOD_NAMES } from './getPeriodDataFromSubcommand.js';
export type Period = 'today' | '30' | '60' | '90' | 'career' | 'custom';
export const PERIOD_NAMES: Record<Period, string> = {
today: "Today's statistics",
'30': "30 days' statistics",
'60': "60 days' statistics",
'90': "90 days' statistics",
career: 'Career statistics',
export const RELATIVE_PERIOD_NAMES: Record<Period, string> = {
today: 'A day ago',
'30': '30 days ago',
'60': '60 days ago',
'90': '90 days ago',
career: 'Start of career',
custom: 'Custom period',
};
@@ -0,0 +1,52 @@
import { CacheType, ChatInputCommandInteraction } from 'discord.js';
import getPeriodNow from '../blitzstars/getPeriodNow.js';
import getPeriodStart from '../blitzstars/getPeriodStart.js';
import getTimeDaysAgo from '../blitzstars/getTimeDaysAgo.js';
import { Period } from './addPeriodSubCommands.js';
export const PERIOD_NAMES: Record<Period, string> = {
today: "Today's statistics",
'30': "30 days' statistics",
'60': "60 days' statistics",
'90': "90 days' statistics",
career: 'Career statistics',
custom: 'Custom period',
};
export const EVOLUTION_PERIOD_NAMES: Record<Period, string> = {
today: "Today's evolution",
'30': "30 days' evolution",
'60': "60 days' evolution",
'90': "90 days' evolution",
career: 'Career evolution',
custom: 'Custom period',
};
export default function getPeriodDataFromSubcommand(
interaction: ChatInputCommandInteraction<CacheType>,
) {
const period = interaction.options.getSubcommand(true) as Period;
let statsName: string;
let evolutionName: string;
let start: number;
let end: number;
if (period === 'custom') {
const startRaw = interaction.options.getInteger('start')!;
const endRaw = interaction.options.getInteger('end')!;
const startMin = Math.min(startRaw, endRaw);
const endMax = Math.max(startRaw, endRaw);
statsName = `${startMin} to ${endMax} days' statistics`;
evolutionName = `${startMin} to ${endMax} days' evolution`;
start = getTimeDaysAgo(startMin);
end = getTimeDaysAgo(endMax);
} else {
statsName = PERIOD_NAMES[period];
evolutionName = EVOLUTION_PERIOD_NAMES[period];
start = getPeriodStart(period);
end = getPeriodNow();
}
return { statsName, evolutionName, start, end };
}
+1
View File
@@ -41,6 +41,7 @@ export default async function render(element: ReactNode) {
{ data: robotoBlack, name: FONT_NAME, weight: 900 },
],
});
const png = new Resvg(svg).render().asPng();
return png;