koishi-plugin-beatsaber-bot
Version:
一个 用于 BeatSaber 的 koishi Bot 插件
296 lines (295 loc) • 12.6 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.formatNumber = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const bsorReplayAcc_1 = require("../utils/bl/bsorReplayAcc");
const beatleader_1 = require("../utils/bl/beatleader");
const stastic_1 = __importDefault(require("../utils/bl/stastic"));
const auto_1 = __importDefault(require("chart.js/auto"));
const canvas_1 = require("../utils/canvas");
function formatNumber(num, digits = 2, addSign = false, notANumber = null) {
if (!Number.isFinite(num)) {
return notANumber;
}
return ((addSign && num > 0 ? '+' : '') +
num.toLocaleString(navigator.language, {
minimumFractionDigits: digits,
maximumFractionDigits: digits,
}));
}
exports.formatNumber = formatNumber;
function timeToLabel(time) {
const minutes = Math.floor(time / 60);
const seconds = Math.floor(time % 60);
return minutes + ':' + seconds.toString().padStart(2, '0');
}
function processChartData(chartData, resolution, smoothPeriodPercentage, weightFunctionSteepness) {
var data = [];
if (chartData.length === 0 || resolution === 0)
return data;
var songDuration = chartData[chartData.length - 1][1];
const distanceWeightFunction = (0, beatleader_1.createDistanceWeightFunction)(songDuration * smoothPeriodPercentage, weightFunctionSteepness);
for (let i = 0.0; i < resolution; i += 1.0) {
const songTime = (songDuration * i) / (resolution - 1);
var sum = 0;
var divider = 0;
for (let j = 0.0; j < chartData.length; j += 1.0) {
const item = chartData[j];
const weight = distanceWeightFunction.getWeight(item[1] - songTime);
sum += item[0] * weight;
divider += weight;
}
if (divider === 0)
continue;
const value = 100 + (sum / divider) * 15;
data.push(value);
}
return data;
}
// get score Statics
function ScoreGraph({ scoreId, scoreInfo, statistic, bsor }) {
const canvas = (0, canvas_1.createCanvas)(400 * 4, 192 * 4);
const setupChart = (canvas, chartData, underswingsData, beatSavior) => {
if (!canvas || !chartData || !Object.keys(chartData).length)
return;
const title = underswingsData?.noUnderswingsScore > underswingsData?.score
? `Lost by underswings: ${formatNumber(underswingsData.noUnderswingsScore - underswingsData.score, 0)}pts, ${formatNumber(underswingsData.noUnderswingsAcc - underswingsData.acc, 2)}% acc` +
(underswingsData?.noUnderswingsPp && underswingsData?.noUnderswingsPp > underswingsData?.pp
? `, ${formatNumber(underswingsData.noUnderswingsPp - underswingsData.pp, 2)}pp`
: '')
: null;
let labels = chartData.times.map(timeToLabel);
const minMaxCounter = (0, beatleader_1.createMinMaxCounter)(0, 115, 1.0);
var hands = [];
if (beatSavior?.stats?.accLeft) {
hands.push('red');
}
if (beatSavior?.stats?.accRight) {
hands.push('blue');
}
if (hands.length == 2) {
hands.push('total');
}
for (let i = 0; i < chartData.times.length; i++) {
hands.forEach((saberType) => {
minMaxCounter.update(chartData.realScoreBySaber[saberType][i]);
minMaxCounter.update(chartData.fullSwingBySaber[saberType][i]);
});
}
var datasets = [];
if (beatSavior?.stats?.accLeft) {
datasets.push({
yAxisID: 'score',
label: 'Accuracy (left)',
data: chartData.fullSwingBySaber.red,
type: 'line',
borderColor: '#ee5555',
backgroundColor: '#ee5555',
borderWidth: 8,
pointRadius: 0,
cubicInterpolationMode: 'monotone',
tension: 0.4,
spanGaps: true,
order: 3,
});
datasets.push({
yAxisID: 'score',
label: 'Underswing (left)',
data: chartData.realScoreBySaber.red,
type: 'line',
fill: '-1',
borderColor: '#ee555555',
backgroundColor: '#ee555555',
borderWidth: 2,
pointRadius: 0,
cubicInterpolationMode: 'monotone',
tension: 0.4,
spanGaps: true,
order: 4,
});
}
if (beatSavior?.stats?.accRight) {
datasets.push({
yAxisID: 'score',
label: 'Accuracy (right)',
data: chartData.fullSwingBySaber.blue,
type: 'line',
borderColor: '#5555ee',
backgroundColor: '#5555ee',
borderWidth: 8,
pointRadius: 0,
cubicInterpolationMode: 'monotone',
tension: 0.4,
spanGaps: true,
order: 5,
});
datasets.push({
yAxisID: 'score',
label: 'Underswing (right)',
data: chartData.realScoreBySaber.blue,
type: 'line',
fill: '-1',
borderColor: '#5555ee55',
backgroundColor: '#5555ee55',
borderWidth: 2,
pointRadius: 0,
cubicInterpolationMode: 'monotone',
tension: 0.4,
spanGaps: true,
order: 6,
});
}
datasets = datasets.concat([
{
yAxisID: 'score',
label: 'Accuracy',
data: chartData.fullSwingBySaber.total,
type: 'line',
borderColor: 'white',
backgroundColor: 'white',
borderWidth: 8,
pointRadius: 0,
cubicInterpolationMode: 'monotone',
tension: 0.4,
spanGaps: true,
order: 1,
},
{
yAxisID: 'score',
label: 'Underswing',
data: chartData.realScoreBySaber.total,
type: 'line',
fill: '-1',
borderColor: '#aaaaaa55',
backgroundColor: '#aaaaaa55',
borderWidth: 2,
pointRadius: 0,
cubicInterpolationMode: 'monotone',
tension: 0.4,
spanGaps: true,
order: 2,
},
{
yAxisID: 'score',
label: 'Score',
data: chartData.realScoreBySaber.total,
type: 'line',
borderColor: '#aaaaaa',
backgroundColor: '#aaaaaa',
borderWidth: 8,
pointRadius: 0,
cubicInterpolationMode: 'monotone',
tension: 0.4,
spanGaps: true,
order: 0,
},
]);
new auto_1.default(canvas, {
data: { labels, datasets },
options: {
responsive: false,
devicePixelRatio: 1,
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
title: {
display: !!title?.length,
text: title,
color: 'white',
font: {
weight: 'normal',
size: 60
},
position: 'bottom',
padding: { top: 5, bottom: 0 },
},
tooltip: {
callbacks: {
title(tooltipItems) {
const item = tooltipItems[0];
const labels = item.chart.data.labels;
return labels[item.dataIndex] + ' (10 seconds average)';
},
label(ctx) {
const datasetLabel = ctx.dataset.label;
let percentage;
let value;
switch (datasetLabel) {
case 'Underswing (left)':
value = ctx.raw - chartData.fullSwingBySaber.red[ctx.dataIndex];
percentage = value / 115.0;
return `${datasetLabel}: ${formatNumber(value, 1)} (${formatNumber(percentage * 100, 2)}%)`;
case 'Underswing (right)':
value = ctx.raw - chartData.fullSwingBySaber.blue[ctx.dataIndex];
percentage = value / 115.0;
return `${datasetLabel}: ${formatNumber(value, 1)} (${formatNumber(percentage * 100, 2)}%)`;
case 'Underswing':
value = ctx.raw - chartData.fullSwingBySaber.total[ctx.dataIndex];
percentage = value / 115.0;
return `${datasetLabel}: ${formatNumber(value, 1)} (${formatNumber(percentage * 100, 2)}%)`;
case 'Score':
value = ctx.raw;
percentage = value / 115.0;
return `${datasetLabel}: ${formatNumber(value, 1)} (${formatNumber(percentage * 100, 2)}%)`;
default:
value = ctx.raw;
percentage = value / 115.0;
return `${datasetLabel}: ${formatNumber(value - 100.0, 1)} (${formatNumber(percentage * 100, 2)}%)`;
}
},
},
},
},
scales: {
x: {
ticks: {
autoSkip: true,
autoSkipPadding: 4,
color: 'white',
font: {
weight: 'normal',
size: 60
}
},
},
y: {
display: true,
min: minMaxCounter.minValue,
max: minMaxCounter.maxValue,
ticks: {
autoSkip: true,
autoSkipPadding: 4,
color: 'white',
font: {
weight: 'normal',
size: 60
}
},
},
score: {
display: false,
min: minMaxCounter.minValue,
max: minMaxCounter.maxValue,
ticks: {
autoSkip: true,
autoSkipPadding: 4,
color: 'white',
},
}
},
},
});
};
const beatSavior = (0, stastic_1.default)(scoreInfo, scoreId, statistic);
const replayAccGraphs = (0, bsorReplayAcc_1.processAccGraphs)(bsor);
const underswingsData = (0, bsorReplayAcc_1.processUnderswings)(bsor);
setupChart(canvas, replayAccGraphs, underswingsData, beatSavior);
const dataURL = (0, canvas_1.canvasToDataURL)(canvas);
return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsx)("div", { style: { height: '12em' }, children: (0, jsx_runtime_1.jsx)("img", { src: dataURL, width: 400, height: 192 }) }) }));
}
exports.default = ScoreGraph;