mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
feat: player evolution
This commit is contained in:
1 parent
e49bd6aa68
commit
91da65196d
10 files changed
+193
-71
No files matched your search
+2
-1
@@ -7,4 +7,5 @@ package-lock.json
|
|||||||
dist/
|
dist/
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
dist.zip
|
dist.zip
|
||||||
commands.txt
|
commands.txt
|
||||||
|
/test.*
|
||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "yarn build:raw --dev && yarn start",
|
"dev": "yarn build:raw --dev && yarn start --dev",
|
||||||
"start": "node --enable-source-maps --trace-warnings dist/index.cjs",
|
"start": "node --enable-source-maps --trace-warnings dist/index.cjs",
|
||||||
"zip": "zip -r dist.zip dist",
|
"zip": "zip -r dist.zip dist",
|
||||||
"build:raw": "node scripts/build",
|
"build:raw": "node scripts/build",
|
||||||
|
|||||||
+23
-17
@@ -12,6 +12,7 @@ import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
|
|||||||
import resolveTankId from '../core/blitz/resolveTankId.js';
|
import resolveTankId from '../core/blitz/resolveTankId.js';
|
||||||
import resolveTankName from '../core/blitz/resolveTankName.js';
|
import resolveTankName from '../core/blitz/resolveTankName.js';
|
||||||
import { tankopedia } from '../core/blitz/tankopedia.js';
|
import { tankopedia } from '../core/blitz/tankopedia.js';
|
||||||
|
import getPlayerHistories from '../core/blitzstars/getPlayerHistories.js';
|
||||||
import getTankHistories from '../core/blitzstars/getTankHistories.js';
|
import getTankHistories from '../core/blitzstars/getTankHistories.js';
|
||||||
import { supportBlitzStars } from '../core/interaction/supportBlitzStars.js';
|
import { supportBlitzStars } from '../core/interaction/supportBlitzStars.js';
|
||||||
import {
|
import {
|
||||||
@@ -23,6 +24,7 @@ import getPeriodDataFromSubcommand from '../core/options/getPeriodDataFromSubcom
|
|||||||
import { WARGAMING_APPLICATION_ID } from '../core/process/args.js';
|
import { WARGAMING_APPLICATION_ID } from '../core/process/args.js';
|
||||||
import { CommandRegistry } from '../events/interactionCreate.js';
|
import { CommandRegistry } from '../events/interactionCreate.js';
|
||||||
import { AccountInfo } from '../types/accountInfo.js';
|
import { AccountInfo } from '../types/accountInfo.js';
|
||||||
|
import { Histories } from '../types/histories.js';
|
||||||
import { PlayerClanData } from '../types/playerClanData.js';
|
import { PlayerClanData } from '../types/playerClanData.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -61,36 +63,40 @@ export default {
|
|||||||
image = tankopedia[tankId].images.normal;
|
image = tankopedia[tankId].images.normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let histories: Histories;
|
||||||
|
|
||||||
const { evolutionName, start, end } =
|
const { evolutionName, start, end } =
|
||||||
getPeriodDataFromSubcommand(interaction);
|
getPeriodDataFromSubcommand(interaction);
|
||||||
|
|
||||||
let careerWinrate: Graph.Plot;
|
|
||||||
let careerBattles: Graph.Plot;
|
|
||||||
|
|
||||||
if (commandGroup === 'player') {
|
if (commandGroup === 'player') {
|
||||||
|
histories = await getPlayerHistories(server, id, {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
includeLatestHistories: true,
|
||||||
|
includePreviousHistories: true,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const tankId = resolveTankId(tankIdRaw);
|
const tankId = resolveTankId(tankIdRaw);
|
||||||
const histories = await getTankHistories(server, id, {
|
histories = await getTankHistories(server, id, {
|
||||||
tankId,
|
tankId,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
includeLatestHistories: true,
|
includeLatestHistories: true,
|
||||||
includePreviousHistories: true,
|
includePreviousHistories: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
careerWinrate = histories.map(
|
|
||||||
(history) =>
|
|
||||||
[
|
|
||||||
history.last_battle_time,
|
|
||||||
(history.all.wins / history.all.battles) * 100,
|
|
||||||
] as Graph.PlotItem,
|
|
||||||
);
|
|
||||||
careerBattles = histories.map(
|
|
||||||
(history) =>
|
|
||||||
[history.last_battle_time, history.all.battles] as Graph.PlotItem,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const careerWinrate = histories.map(
|
||||||
|
(history) =>
|
||||||
|
[
|
||||||
|
history.last_battle_time,
|
||||||
|
(history.all.wins / history.all.battles) * 100,
|
||||||
|
] as Graph.PlotItem,
|
||||||
|
);
|
||||||
|
const careerBattles = histories.map(
|
||||||
|
(history) =>
|
||||||
|
[history.last_battle_time, history.all.battles] as Graph.PlotItem,
|
||||||
|
);
|
||||||
const winrateYs = careerWinrate.map(([, y]) => y);
|
const winrateYs = careerWinrate.map(([, y]) => y);
|
||||||
const maxWinrate = Math.max(...winrateYs);
|
const maxWinrate = Math.max(...winrateYs);
|
||||||
const minWinrate = Math.min(...winrateYs);
|
const minWinrate = Math.min(...winrateYs);
|
||||||
@@ -121,7 +127,7 @@ export default {
|
|||||||
interaction.options.getSubcommand() as Period
|
interaction.options.getSubcommand() as Period
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
xMaxLabel="Today"
|
xMaxLabel="Latest recorded battle"
|
||||||
leftVerticalMargin={{
|
leftVerticalMargin={{
|
||||||
min: minWinrate,
|
min: minWinrate,
|
||||||
max: maxWinrate,
|
max: maxWinrate,
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { theme } from '../../../stitches.config.js';
|
import { theme } from '../../../stitches.config.js';
|
||||||
import { Stat } from '../index.js';
|
|
||||||
|
|
||||||
import '../../../core/blitz/getWN8Percentile.js';
|
|
||||||
import PercentileIndicator from '../../PercentileIndicator.js';
|
import PercentileIndicator from '../../PercentileIndicator.js';
|
||||||
|
import { Stat } from '../index.js';
|
||||||
|
|
||||||
export interface GenericStatsRowProps {
|
export interface GenericStatsRowProps {
|
||||||
stat: Stat;
|
stat: Stat;
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import { BlitzServer } from '../../constants/servers.js';
|
||||||
|
import { AccountInfo, AllStats } from '../../types/accountInfo.js';
|
||||||
|
import {
|
||||||
|
GetHistoriesOptions,
|
||||||
|
Histories,
|
||||||
|
History,
|
||||||
|
getHistoriesDefaultOptions,
|
||||||
|
} from '../../types/histories.js';
|
||||||
|
import getWargamingResponse from '../blitz/getWargamingResponse.js';
|
||||||
|
import { WARGAMING_APPLICATION_ID } from '../process/args.js';
|
||||||
|
import { emptyAllStats } from './getTankStatsOverTime.js';
|
||||||
|
|
||||||
|
export interface PlayerHistoryRaw {
|
||||||
|
clan: { clan_id: number; name: string; tag: string };
|
||||||
|
statistics: {
|
||||||
|
all: AllStats;
|
||||||
|
wn8: number;
|
||||||
|
wn7: number;
|
||||||
|
average_tier: number;
|
||||||
|
};
|
||||||
|
last_battle_time: number;
|
||||||
|
nickname: string;
|
||||||
|
account_id: number;
|
||||||
|
achievements: {
|
||||||
|
account_id: number;
|
||||||
|
[key: string]: number;
|
||||||
|
};
|
||||||
|
region: BlitzServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PlayerHistoriesRaw = PlayerHistoryRaw[];
|
||||||
|
|
||||||
|
export default async function getPlayerHistories(
|
||||||
|
server: BlitzServer,
|
||||||
|
id: number,
|
||||||
|
options?: Partial<GetHistoriesOptions>,
|
||||||
|
) {
|
||||||
|
const mergedOptions = { ...getHistoriesDefaultOptions, ...options };
|
||||||
|
const response = await fetch(
|
||||||
|
`https://www.blitzstars.com/api/playerstats/${id}`,
|
||||||
|
);
|
||||||
|
const playerHistories = ((await response.json()) as PlayerHistoriesRaw).map(
|
||||||
|
(history) =>
|
||||||
|
({
|
||||||
|
all: history.statistics.all,
|
||||||
|
last_battle_time: history.last_battle_time,
|
||||||
|
} satisfies History),
|
||||||
|
);
|
||||||
|
let lastNonCompliantIndex = -1;
|
||||||
|
let hasCompliedOnce = false;
|
||||||
|
const filtered = playerHistories.filter(({ last_battle_time }, index) => {
|
||||||
|
const complies =
|
||||||
|
last_battle_time >= mergedOptions.start &&
|
||||||
|
last_battle_time <= mergedOptions.end;
|
||||||
|
|
||||||
|
if (complies && !hasCompliedOnce) hasCompliedOnce = true;
|
||||||
|
if (!hasCompliedOnce) lastNonCompliantIndex = index;
|
||||||
|
|
||||||
|
return complies;
|
||||||
|
});
|
||||||
|
|
||||||
|
const latestNodes: Histories = [];
|
||||||
|
|
||||||
|
if (mergedOptions.includeLatestHistories) {
|
||||||
|
const {
|
||||||
|
last_battle_time,
|
||||||
|
statistics: { all },
|
||||||
|
} = (
|
||||||
|
await getWargamingResponse<AccountInfo>(
|
||||||
|
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
|
||||||
|
)
|
||||||
|
)[id];
|
||||||
|
|
||||||
|
latestNodes.push({ all, last_battle_time });
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousNodes: Histories = [];
|
||||||
|
|
||||||
|
if (mergedOptions.includePreviousHistories) {
|
||||||
|
if (lastNonCompliantIndex !== -1) {
|
||||||
|
previousNodes.push(
|
||||||
|
playerHistories[lastNonCompliantIndex] ??
|
||||||
|
({
|
||||||
|
all: emptyAllStats,
|
||||||
|
last_battle_time: -Infinity,
|
||||||
|
} satisfies History),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...latestNodes, ...filtered, ...previousNodes] satisfies Histories;
|
||||||
|
}
|
||||||
@@ -1,41 +1,34 @@
|
|||||||
import { BlitzServer } from '../../constants/servers.js';
|
import { BlitzServer } from '../../constants/servers.js';
|
||||||
import { AllStats } from '../../types/accountInfo.js';
|
import {
|
||||||
|
GetHistoriesOptions,
|
||||||
|
Histories,
|
||||||
|
History,
|
||||||
|
getHistoriesDefaultOptions,
|
||||||
|
} from '../../types/histories.js';
|
||||||
import getTankStats from '../blitz/getTankStats.js';
|
import getTankStats from '../blitz/getTankStats.js';
|
||||||
import { emptyAllStats } from './getTankStatsOverTime.js';
|
import { emptyAllStats } from './getTankStatsOverTime.js';
|
||||||
|
|
||||||
export interface TankHistory {
|
export interface TankHistory extends History {
|
||||||
all: AllStats;
|
|
||||||
last_battle_time: number;
|
|
||||||
mark_of_mastery: number;
|
|
||||||
battle_life_time: number;
|
|
||||||
account_id: number;
|
|
||||||
tank_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 type TankHistories = TankHistory[];
|
||||||
|
|
||||||
export interface GetTankHistoriesOptions {
|
export interface TankHistoryRaw extends TankHistory {
|
||||||
start: number;
|
mark_of_mastery: number;
|
||||||
end: number;
|
battle_life_time: number;
|
||||||
includeLatestHistories: boolean;
|
account_id: number;
|
||||||
includePreviousHistories: boolean;
|
}
|
||||||
|
|
||||||
|
export type TankHistoriesRaw = TankHistoryRaw[];
|
||||||
|
|
||||||
|
export interface GetTankHistoriesOptions extends GetHistoriesOptions {
|
||||||
tankId?: number;
|
tankId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getTankHistoriesDefaultOptions: GetTankHistoriesOptions = {
|
export const getTankHistoriesDefaultOptions: GetTankHistoriesOptions = {
|
||||||
start: -Infinity,
|
...getHistoriesDefaultOptions,
|
||||||
end: Infinity,
|
tankId: undefined,
|
||||||
includeLatestHistories: false,
|
|
||||||
includePreviousHistories: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function getTankHistories(
|
export default async function getTankHistories(
|
||||||
@@ -47,7 +40,16 @@ export default async function getTankHistories(
|
|||||||
const tankHistoriesResponse = await fetch(
|
const tankHistoriesResponse = await fetch(
|
||||||
`https://www.blitzstars.com/api/tankhistories/for/${id}/`,
|
`https://www.blitzstars.com/api/tankhistories/for/${id}/`,
|
||||||
);
|
);
|
||||||
const tankHistories = (await tankHistoriesResponse.json()) as TankHistories;
|
const tankHistories = (
|
||||||
|
(await tankHistoriesResponse.json()) as TankHistoriesRaw
|
||||||
|
).map(
|
||||||
|
(history) =>
|
||||||
|
({
|
||||||
|
all: history.all,
|
||||||
|
last_battle_time: history.last_battle_time,
|
||||||
|
tank_id: history.tank_id,
|
||||||
|
} satisfies TankHistory),
|
||||||
|
);
|
||||||
const compliantTanks: number[] = [];
|
const compliantTanks: number[] = [];
|
||||||
const lastBattles: Record<number, number> = {};
|
const lastBattles: Record<number, number> = {};
|
||||||
const lastNonCompliantIndex: Record<number, number> = {};
|
const lastNonCompliantIndex: Record<number, number> = {};
|
||||||
@@ -96,15 +98,16 @@ export default async function getTankHistories(
|
|||||||
compliantTanks.forEach((tankId) => {
|
compliantTanks.forEach((tankId) => {
|
||||||
if (tankId in lastNonCompliantIndex) {
|
if (tankId in lastNonCompliantIndex) {
|
||||||
previousNodes.push(
|
previousNodes.push(
|
||||||
tankHistories[lastNonCompliantIndex[tankId]] ?? {
|
tankHistories[lastNonCompliantIndex[tankId]] ??
|
||||||
...emptyTankHistory,
|
({
|
||||||
account_id: id,
|
all: emptyAllStats,
|
||||||
tank_id: tankId,
|
last_battle_time: -Infinity,
|
||||||
},
|
tank_id: tankId,
|
||||||
|
} satisfies TankHistory),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return [...previousNodes, ...filtered, ...latestNodes];
|
return [...latestNodes, ...filtered, ...previousNodes] satisfies Histories;
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { BlitzServer } from '../../constants/servers.js';
|
import { BlitzServer } from '../../constants/servers.js';
|
||||||
import { AllStats } from '../../types/accountInfo.js';
|
import { AllStats } from '../../types/accountInfo.js';
|
||||||
import getTankHistories, {
|
import getTankHistories, {
|
||||||
TankHistories,
|
TankHistoriesRaw,
|
||||||
TankHistory,
|
TankHistoryRaw,
|
||||||
} from './getTankHistories.js';
|
} from './getTankHistories.js';
|
||||||
|
|
||||||
export const emptyAllStats: AllStats = {
|
export const emptyAllStats: AllStats = {
|
||||||
@@ -25,7 +25,7 @@ export const emptyAllStats: AllStats = {
|
|||||||
wins: 0,
|
wins: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const emptyTankHistoryNode: TankHistory = {
|
export const emptyTankHistoryNode: TankHistoryRaw = {
|
||||||
account_id: 0,
|
account_id: 0,
|
||||||
battle_life_time: 0,
|
battle_life_time: 0,
|
||||||
last_battle_time: 0,
|
last_battle_time: 0,
|
||||||
@@ -45,7 +45,7 @@ export default async function getTankStatsOverTime(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// sort them even though most won't be used
|
// sort them even though most won't be used
|
||||||
const tankSortedHistory: Record<number, TankHistories> = {};
|
const tankSortedHistory: Record<number, TankHistoriesRaw> = {};
|
||||||
const tankIds: number[] = [];
|
const tankIds: number[] = [];
|
||||||
history.forEach((node) => {
|
history.forEach((node) => {
|
||||||
if (!tankSortedHistory[node.tank_id]) {
|
if (!tankSortedHistory[node.tank_id]) {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const RELATIVE_PERIOD_NAMES: Record<Period, string> = {
|
|||||||
'30': '30 days ago',
|
'30': '30 days ago',
|
||||||
'60': '60 days ago',
|
'60': '60 days ago',
|
||||||
'90': '90 days ago',
|
'90': '90 days ago',
|
||||||
career: 'Start of career',
|
career: 'Extent of data',
|
||||||
custom: 'Custom period',
|
custom: 'Custom period',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -124,20 +124,18 @@ try {
|
|||||||
});
|
});
|
||||||
|
|
||||||
console.log(`Refreshing ${guildCommands.length} guild command(s).`);
|
console.log(`Refreshing ${guildCommands.length} guild command(s).`);
|
||||||
Promise.all([
|
rest
|
||||||
rest.put(
|
.put(
|
||||||
Routes.applicationGuildCommands(getClientId(), discord.sklld_guild_id),
|
|
||||||
{ body: guildCommands },
|
|
||||||
),
|
|
||||||
rest.put(
|
|
||||||
Routes.applicationGuildCommands(getClientId(), discord.tres_guild_id),
|
Routes.applicationGuildCommands(getClientId(), discord.tres_guild_id),
|
||||||
{ body: guildCommands },
|
{ body: guildCommands },
|
||||||
),
|
)
|
||||||
]).then((guildData) => {
|
.then((guildData) => {
|
||||||
console.log(
|
console.log(
|
||||||
`Successfully refreshed ${guildData.flat().length / 2} guild command(s).`,
|
`Successfully refreshed ${
|
||||||
);
|
(guildData as unknown[]).length
|
||||||
});
|
} guild command(s).`,
|
||||||
|
);
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// a cleaner simplification of Blitz Stars' histories because it's a mess there
|
||||||
|
|
||||||
|
import { AllStats } from './accountInfo.js';
|
||||||
|
|
||||||
|
export interface History {
|
||||||
|
all: AllStats;
|
||||||
|
last_battle_time: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Histories = History[];
|
||||||
|
|
||||||
|
export interface GetHistoriesOptions {
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
includeLatestHistories: boolean;
|
||||||
|
includePreviousHistories: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getHistoriesDefaultOptions: GetHistoriesOptions = {
|
||||||
|
start: -Infinity,
|
||||||
|
end: Infinity,
|
||||||
|
includeLatestHistories: false,
|
||||||
|
includePreviousHistories: false,
|
||||||
|
};
|
||||||
Reference in new issue
Block a user