From e1f49c8888a892c42f35d2e74597fbbd533b2a3d Mon Sep 17 00:00:00 2001
From: ck <21735205+cyperdark@users.noreply.github.com>
Date: Tue, 21 Jan 2025 11:37:52 +0300
Subject: [PATCH 1/6] fix: Support http range for `/files/beatmap/audio`
---
packages/server/scripts/beatmapFile.ts | 46 ++++++++++++++++++++------
1 file changed, 35 insertions(+), 11 deletions(-)
diff --git a/packages/server/scripts/beatmapFile.ts b/packages/server/scripts/beatmapFile.ts
index 16215752..42196c4c 100644
--- a/packages/server/scripts/beatmapFile.ts
+++ b/packages/server/scripts/beatmapFile.ts
@@ -1,17 +1,15 @@
-import { ServerResponse } from 'http';
+import fs from 'fs';
+import type { ServerResponse } from 'http';
import path from 'path';
-import { directoryWalker } from '../utils/directories';
-import { ExtendedIncomingMessage } from '../utils/http';
-import { sendJson } from '../utils/index';
+import type { ExtendedIncomingMessage } from '../utils/http';
+import { getContentType, sendJson } from '../utils/index';
export function beatmapFileShortcut(
req: ExtendedIncomingMessage,
res: ServerResponse,
beatmapFileType: 'audio' | 'background' | 'file'
) {
- const url = req.pathname || '/';
-
const osuInstance: any = req.instanceManager.getInstance(
req.instanceManager.focusedClient
);
@@ -40,10 +38,36 @@ export function beatmapFileShortcut(
});
}
- directoryWalker({
- res,
- baseUrl: url,
- pathname: fileName || '',
- folderPath: folder
+ const filePath = path.join(folder, fileName);
+ const fileStat = fs.statSync(filePath);
+
+ if (req.headers.range) {
+ const range = req.headers.range.replace('bytes=', '').split('-');
+ const start = parseInt(range[0]);
+ const end = range[1] ? parseInt(range[1]) : fileStat.size - 1;
+
+ if (start >= fileStat.size || end >= fileStat.size) {
+ res.writeHead(416, {
+ 'Content-Range': `bytes */${fileStat.size}`
+ });
+ return res.end();
+ }
+
+ res.writeHead(206, {
+ 'Accept-Ranges': 'bytes',
+ 'Content-Type': getContentType(fileName),
+ 'Content-Range': `bytes ${start}-${end}/${fileStat.size}`,
+ 'Content-Length': end - start + 1
+ });
+
+ fs.createReadStream(filePath, { start, end }).pipe(res);
+ return;
+ }
+
+ res.writeHead(200, {
+ 'Content-Type': getContentType(fileName),
+ 'Content-Length': fileStat.size
});
+
+ fs.createReadStream(filePath).pipe(res);
}
From 175cbad0a19ed0e4178172758cb8400ba98c4e9d Mon Sep 17 00:00:00 2001
From: ck <21735205+cyperdark@users.noreply.github.com>
Date: Tue, 21 Jan 2025 11:41:19 +0300
Subject: [PATCH 2/6] chore: remove error for `favicon.ico` not found
---
packages/server/router/index.ts | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/packages/server/router/index.ts b/packages/server/router/index.ts
index 623a22c5..5229ac2b 100644
--- a/packages/server/router/index.ts
+++ b/packages/server/router/index.ts
@@ -436,6 +436,24 @@ export default function buildBaseApi(server: Server) {
);
});
+ server.app.route('/favicon.ico', 'GET', (req, res) => {
+ fs.readFile(path.join(pkgAssetsPath, 'favicon.ico'), (err, content) => {
+ if (err) {
+ wLogger.debug(`/${'favicon.ico'}`, err);
+ res.writeHead(404, { 'Content-Type': 'text/html' });
+
+ res.end('page not found');
+ return;
+ }
+
+ res.writeHead(200, {
+ 'Content-Type': 'image/vnd.microsoft.icon; charset=utf-8'
+ });
+
+ res.end(content);
+ });
+ });
+
server.app.route(/.*/, 'GET', (req, res) => {
const url = req.pathname || '/';
const staticPath = getStaticPath();
From 24d9e70079f90bf3be8545e0b5c16bf4cdef4c59 Mon Sep 17 00:00:00 2001
From: ck <21735205+cyperdark@users.noreply.github.com>
Date: Tue, 21 Jan 2025 12:43:32 +0300
Subject: [PATCH 3/6] fix: Use lazer grade system for lazer client
---
packages/common/utils/downloader.ts | 4 +-
packages/server/utils/directories.ts | 4 +-
packages/tosu/src/api/utils/buildResult.ts | 1 +
packages/tosu/src/api/utils/buildResultV2.ts | 12 +-
packages/tosu/src/states/gameplay.ts | 3 +
packages/tosu/src/states/resultScreen.ts | 2 +
packages/tosu/src/utils/calculators.ts | 206 +++++++++++--------
7 files changed, 134 insertions(+), 98 deletions(-)
diff --git a/packages/common/utils/downloader.ts b/packages/common/utils/downloader.ts
index 072ea60d..08da77e1 100644
--- a/packages/common/utils/downloader.ts
+++ b/packages/common/utils/downloader.ts
@@ -18,9 +18,7 @@ export const updateProgressBar = (
const progressBar = '█'.repeat(filledWidth) + '░'.repeat(emptyWidth);
process.stdout.write(
- `${coloredText} ${title}: [${progressBar}] ${(progress * 100).toFixed(
- 2
- )}%${message}\r`
+ `${coloredText} ${title}: [${progressBar}] ${(progress * 100).toFixed(2)}%${message}\r`
);
if (progress === 1) {
diff --git a/packages/server/utils/directories.ts b/packages/server/utils/directories.ts
index 42198815..ae0c14c5 100644
--- a/packages/server/utils/directories.ts
+++ b/packages/server/utils/directories.ts
@@ -120,9 +120,7 @@ export function readDirectory(
const html = folders.map((r) => {
const slashAtTheEnd = getContentType(r) === '' ? '/' : '';
- return `
${r}`;
+ return `${r}`;
});
return callback(
diff --git a/packages/tosu/src/api/utils/buildResult.ts b/packages/tosu/src/api/utils/buildResult.ts
index ef4b093f..60b7263f 100644
--- a/packages/tosu/src/api/utils/buildResult.ts
+++ b/packages/tosu/src/api/utils/buildResult.ts
@@ -230,6 +230,7 @@ export const buildResult = (instanceManager: InstanceManager): ApiAnswer => {
name: resultScreen.playerName,
score: resultScreen.score,
accuracy: calculateAccuracy({
+ _round: true,
hits: resultScreenHits,
mode: gameplay.mode
}),
diff --git a/packages/tosu/src/api/utils/buildResultV2.ts b/packages/tosu/src/api/utils/buildResultV2.ts
index ba6c3b54..d195403c 100644
--- a/packages/tosu/src/api/utils/buildResultV2.ts
+++ b/packages/tosu/src/api/utils/buildResultV2.ts
@@ -32,7 +32,8 @@ import { CalculateMods } from '@/utils/osuMods.types';
const convertMemoryPlayerToResult = (
memoryPlayer: MemoryLeaderboardPlayer,
- gameMode: any
+ gameMode: any,
+ client: ClientType
): Leaderboard => {
const hits = {
300: memoryPlayer.h300,
@@ -53,7 +54,7 @@ const convertMemoryPlayerToResult = (
name: memoryPlayer.name,
score: memoryPlayer.score,
- accuracy: calculateAccuracy({ hits, mode: gameMode }),
+ accuracy: calculateAccuracy({ _round: true, hits, mode: gameMode }),
hits,
@@ -69,6 +70,7 @@ const convertMemoryPlayerToResult = (
rate: memoryPlayer.mods.rate
},
rank: calculateGrade({
+ _lazer: client === ClientType.lazer,
mods: memoryPlayer.mods.number,
mode: gameMode,
hits
@@ -263,7 +265,11 @@ export const buildResult = (instanceManager: InstanceManager): ApiAnswer => {
},
play: buildPlay(gameplay, beatmapPP, currentMods),
leaderboard: gameplay.leaderboardScores.map((slot) =>
- convertMemoryPlayerToResult(slot, Rulesets[gameplay.mode])
+ convertMemoryPlayerToResult(
+ slot,
+ Rulesets[gameplay.mode],
+ osuInstance.client
+ )
),
performance: {
accuracy: beatmapPP.ppAcc,
diff --git a/packages/tosu/src/states/gameplay.ts b/packages/tosu/src/states/gameplay.ts
index 394c5e44..ed35df64 100644
--- a/packages/tosu/src/states/gameplay.ts
+++ b/packages/tosu/src/states/gameplay.ts
@@ -104,6 +104,7 @@ export class Gameplay extends AbstractState {
this.accuracy = 100.0;
this.unstableRate = 0;
this.gradeCurrent = calculateGrade({
+ _lazer: this.game.client === ClientType.lazer,
mods: this.mods.number,
mode: this.mode,
hits: {
@@ -410,6 +411,7 @@ export class Gameplay extends AbstractState {
objectCount - this.hit300 - this.hit100 - this.hit50 - this.hitMiss;
this.gradeCurrent = calculateGrade({
+ _lazer: this.game.client === ClientType.lazer,
mods: this.mods.number,
mode: this.mode,
hits: {
@@ -423,6 +425,7 @@ export class Gameplay extends AbstractState {
});
this.gradeExpected = calculateGrade({
+ _lazer: this.game.client === ClientType.lazer,
mods: this.mods.number,
mode: this.mode,
hits: {
diff --git a/packages/tosu/src/states/resultScreen.ts b/packages/tosu/src/states/resultScreen.ts
index c7dac79a..ddb1a0fe 100644
--- a/packages/tosu/src/states/resultScreen.ts
+++ b/packages/tosu/src/states/resultScreen.ts
@@ -109,12 +109,14 @@ export class ResultScreen extends AbstractState {
};
this.grade = calculateGrade({
+ _lazer: this.game.client === ClientType.lazer,
mods: this.mods.number,
mode: this.mode,
hits
});
this.accuracy = calculateAccuracy({
+ _round: true,
mode: this.mode,
hits
});
diff --git a/packages/tosu/src/utils/calculators.ts b/packages/tosu/src/utils/calculators.ts
index 4d45d809..68b4d993 100644
--- a/packages/tosu/src/utils/calculators.ts
+++ b/packages/tosu/src/utils/calculators.ts
@@ -5,8 +5,10 @@ import { OsuMods } from '@/utils/osuMods.types';
*/
export const calculateAccuracy = ({
hits,
- mode
+ mode,
+ _round
}: {
+ _round: boolean;
hits: {
300: any;
100: any;
@@ -53,17 +55,20 @@ export const calculateAccuracy = ({
break;
}
- return parseFloat(acc.toFixed(2));
+ if (_round === true) return parseFloat(acc.toFixed(2));
+ return acc;
};
/**
* Used to calculate grade out of hits
*/
export const calculateGrade = ({
+ _lazer,
hits,
mods,
mode
}: {
+ _lazer: boolean;
hits: {
300: number;
100: number;
@@ -78,10 +83,7 @@ export const calculateGrade = ({
let silver = false;
if (typeof mods === 'string') {
- silver =
- mods.toLowerCase().indexOf('hd') > -1
- ? true
- : mods.toLowerCase().indexOf('fl') > -1;
+ silver = /hd|fl/i.test(mods.toLowerCase());
}
if (typeof mods === 'number') {
@@ -90,104 +92,130 @@ export const calculateGrade = ({
(mods & OsuMods.Flashlight) === OsuMods.Flashlight;
}
- let total = 0;
- let acc = 0.0;
+ const acc = calculateAccuracy({ hits, mode, _round: false }) / 100;
+ let rank = '';
- let r300 = 0;
- let r50 = 0;
+ if (_lazer === true) {
+ switch (mode) {
+ case 0:
+ case 1: {
+ if (acc === 1) rank = silver ? 'XH' : 'X';
+ else if (acc >= 0.95 && hits[0] === 0)
+ rank = silver ? 'SH' : 'S';
+ else if (acc >= 0.9) rank = 'A';
+ else if (acc >= 0.8) rank = 'B';
+ else if (acc >= 0.7) rank = 'C';
+ else rank = 'D';
- let rank = '';
+ break;
+ }
- switch (mode) {
- case 0:
- total = hits[300] + hits[100] + hits[50] + hits[0];
- acc =
- total > 0
- ? (hits[50] * 50 + hits[100] * 100 + hits[300] * 300) /
- (total * 300)
- : 1;
-
- r300 = hits[300] / total;
- r50 = hits[50] / total;
-
- if (r300 === 1) rank = silver ? 'XH' : 'X';
- else if (r300 > 0.9 && r50 < 0.01 && hits[0] === 0) {
- rank = silver ? 'SH' : 'S';
- } else if ((r300 > 0.8 && hits[0] === 0) || r300 > 0.9) rank = 'A';
- else if ((r300 > 0.7 && hits[0] === 0) || r300 > 0.8) rank = 'B';
- else if (r300 > 0.6) rank = 'C';
- else rank = 'D';
+ case 2: {
+ if (acc === 1) rank = silver ? 'XH' : 'X';
+ else if (acc >= 0.98) rank = silver ? 'SH' : 'S';
+ else if (acc >= 0.94) rank = 'A';
+ else if (acc >= 0.9) rank = 'B';
+ else if (acc >= 0.85) rank = 'C';
+ else rank = 'D';
- break;
+ break;
+ }
- case 1:
- total = hits[300] + hits[100] + hits[50] + hits[0];
- acc =
- total > 0
- ? (hits[100] * 150 + hits[300] * 300) / (total * 300)
- : 1;
+ case 3: {
+ if (acc === 1) rank = silver ? 'XH' : 'X';
+ else if (acc >= 0.95) rank = silver ? 'SH' : 'S';
+ else if (acc >= 0.9) rank = 'A';
+ else if (acc >= 0.8) rank = 'B';
+ else if (acc >= 0.7) rank = 'C';
+ else rank = 'D';
- r300 = hits[300] / total;
- r50 = hits[50] / total;
+ break;
+ }
+ }
+ } else {
+ switch (mode) {
+ case 0: {
+ const total = hits[300] + hits[100] + hits[50] + hits[0];
+ if (total === 0) {
+ rank = silver ? 'XH' : 'X';
+ break;
+ }
- if (r300 === 1) rank = silver ? 'XH' : 'X';
- else if (r300 > 0.9 && r50 < 0.01 && hits[0] === 0) {
- rank = silver ? 'SH' : 'S';
- } else if ((r300 > 0.8 && hits[0] === 0) || r300 > 0.9) rank = 'A';
- else if ((r300 > 0.7 && hits[0] === 0) || r300 > 0.8) rank = 'B';
- else if (r300 > 0.6) rank = 'C';
- else rank = 'D';
+ let r300 = 0;
+ let r50 = 0;
- break;
+ r300 = hits[300] / total;
+ r50 = hits[50] / total;
- case 2:
- total = hits[300] + hits[100] + hits[50] + hits[0] + hits.katu;
- acc = total > 0 ? (hits[50] + hits[100] + hits[300]) / total : 1;
+ if (r300 === 1) {
+ rank = silver ? 'XH' : 'X';
+ } else if (r300 > 0.9 && r50 < 0.01 && hits[0] === 0) {
+ rank = silver ? 'SH' : 'S';
+ } else if ((r300 > 0.8 && hits[0] === 0) || r300 > 0.9) {
+ rank = 'A';
+ } else if ((r300 > 0.7 && hits[0] === 0) || r300 > 0.8) {
+ rank = 'B';
+ } else if (r300 > 0.6) {
+ rank = 'C';
+ } else {
+ rank = 'D';
+ }
- r300 = hits[300] / total;
- r50 = hits[50] / total;
+ break;
+ }
- if (acc === 1) rank = silver ? 'XH' : 'X';
- else if (acc > 0.98) rank = silver ? 'SH' : 'S';
- else if (acc > 0.94) rank = 'A';
- else if (acc > 0.9) rank = 'B';
- else if (acc > 0.85) rank = 'C';
- else rank = 'D';
+ case 1: {
+ const total = hits[300] + hits[100] + hits[50] + hits[0];
+ if (total === 0) {
+ rank = silver ? 'XH' : 'X';
+ break;
+ }
- break;
+ let r300 = 0;
+ let r50 = 0;
- case 3:
- total =
- hits[300] +
- hits[100] +
- hits[50] +
- hits[0] +
- hits.geki +
- hits.katu;
- acc =
- total > 0
- ? (hits[50] * 50 +
- hits[100] * 100 +
- hits.katu * 200 +
- (hits[300] + hits.geki) * 300) /
- (total * 300)
- : 1;
-
- r300 = hits[300] / total;
- r50 = hits[50] / total;
-
- if (acc === 1) rank = silver ? 'XH' : 'X';
- else if (acc > 0.95) rank = silver ? 'SH' : 'S';
- else if (acc > 0.9) rank = 'A';
- else if (acc > 0.8) rank = 'B';
- else if (acc > 0.7) rank = 'C';
- else rank = 'D';
+ r300 = hits[300] / total;
+ r50 = hits[50] / total;
- break;
- }
+ if (r300 === 1) {
+ rank = silver ? 'XH' : 'X';
+ } else if (r300 > 0.9 && r50 < 0.01 && hits[0] === 0) {
+ rank = silver ? 'SH' : 'S';
+ } else if ((r300 > 0.8 && hits[0] === 0) || r300 > 0.9) {
+ rank = 'A';
+ } else if ((r300 > 0.7 && hits[0] === 0) || r300 > 0.8) {
+ rank = 'B';
+ } else if (r300 > 0.6) {
+ rank = 'C';
+ } else {
+ rank = 'D';
+ }
+
+ break;
+ }
+
+ case 2: {
+ if (acc === 1) rank = silver ? 'XH' : 'X';
+ else if (acc > 0.98) rank = silver ? 'SH' : 'S';
+ else if (acc > 0.94) rank = 'A';
+ else if (acc > 0.9) rank = 'B';
+ else if (acc > 0.85) rank = 'C';
+ else rank = 'D';
+
+ break;
+ }
+
+ case 3: {
+ if (acc === 1) rank = silver ? 'XH' : 'X';
+ else if (acc > 0.95) rank = silver ? 'SH' : 'S';
+ else if (acc > 0.9) rank = 'A';
+ else if (acc > 0.8) rank = 'B';
+ else if (acc > 0.7) rank = 'C';
+ else rank = 'D';
- if (total === 0) {
- rank = silver ? 'XH' : 'X';
+ break;
+ }
+ }
}
return rank;
From 703162a3a0e0b848e28ace2ae9cf08613659426c Mon Sep 17 00:00:00 2001
From: ck <21735205+cyperdark@users.noreply.github.com>
Date: Tue, 21 Jan 2025 13:42:18 +0300
Subject: [PATCH 4/6] feat: New values for `/websocket/v2`: `isBreak` &
`isKiai`
---
packages/tosu/src/api/types/v2.ts | 2 +
packages/tosu/src/api/utils/buildResultSC.ts | 6 ++-
packages/tosu/src/api/utils/buildResultV2.ts | 2 +
packages/tosu/src/instances/lazerInstance.ts | 2 +-
packages/tosu/src/instances/osuInstance.ts | 2 +-
packages/tosu/src/states/beatmap.ts | 56 ++++++++++++++++----
6 files changed, 57 insertions(+), 13 deletions(-)
diff --git a/packages/tosu/src/api/types/v2.ts b/packages/tosu/src/api/types/v2.ts
index 8b97b6a2..e9416e82 100644
--- a/packages/tosu/src/api/types/v2.ts
+++ b/packages/tosu/src/api/types/v2.ts
@@ -117,6 +117,8 @@ export interface Profile {
}
export interface Beatmap {
+ isKiai: boolean;
+ isBreak: boolean;
isConvert: boolean;
time: BeatmapTime;
status: NumberName;
diff --git a/packages/tosu/src/api/utils/buildResultSC.ts b/packages/tosu/src/api/utils/buildResultSC.ts
index 0bb9107f..5d3d8ff8 100644
--- a/packages/tosu/src/api/utils/buildResultSC.ts
+++ b/packages/tosu/src/api/utils/buildResultSC.ts
@@ -123,7 +123,11 @@ export const buildResult = (instanceManager: InstanceManager): ApiAnswer => {
acc[v] = value <= 0 ? 0 : value;
return acc;
}, {}),
- mapBreaks: beatmapPP.breaks,
+ mapBreaks: beatmapPP.breaks.map((r) => ({
+ startTime: r.start,
+ endTime: r.end,
+ hasEffect: r.hasEffect
+ })),
mapKiaiPoints: [], // TODO: add
mapPosition: formatMilliseconds(global.playTime), // convert to osu format
mapTimingPoints: beatmapPP.timingPoints.map((r) => ({
diff --git a/packages/tosu/src/api/utils/buildResultV2.ts b/packages/tosu/src/api/utils/buildResultV2.ts
index d195403c..b20f6266 100644
--- a/packages/tosu/src/api/utils/buildResultV2.ts
+++ b/packages/tosu/src/api/utils/buildResultV2.ts
@@ -230,6 +230,8 @@ export const buildResult = (instanceManager: InstanceManager): ApiAnswer => {
backgroundColour: user.backgroundColour?.toString(16)
},
beatmap: {
+ isKiai: beatmapPP.isKiai,
+ isBreak: beatmapPP.isBreak,
isConvert:
beatmapPP.mode === 0 ? beatmapPP.mode !== currentMode : false,
time: {
diff --git a/packages/tosu/src/instances/lazerInstance.ts b/packages/tosu/src/instances/lazerInstance.ts
index da5f645a..23c6e91f 100644
--- a/packages/tosu/src/instances/lazerInstance.ts
+++ b/packages/tosu/src/instances/lazerInstance.ts
@@ -108,7 +108,7 @@ export class LazerInstance extends AbstractInstance {
this.previousMP3Length = menu.mp3Length;
}
- beatmapPP.updateRealTimeBPM(global.playTime, currentMods.rate);
+ beatmapPP.updateEventsStatus(global.playTime, currentMods.rate);
switch (global.status) {
case GameState.menu:
diff --git a/packages/tosu/src/instances/osuInstance.ts b/packages/tosu/src/instances/osuInstance.ts
index 56b68d55..771baf13 100644
--- a/packages/tosu/src/instances/osuInstance.ts
+++ b/packages/tosu/src/instances/osuInstance.ts
@@ -123,7 +123,7 @@ export class OsuInstance extends AbstractInstance {
this.previousMP3Length = menu.mp3Length;
}
- beatmapPP.updateRealTimeBPM(global.playTime, currentMods.rate);
+ beatmapPP.updateEventsStatus(global.playTime, currentMods.rate);
switch (global.status) {
case GameState.menu:
diff --git a/packages/tosu/src/states/beatmap.ts b/packages/tosu/src/states/beatmap.ts
index 70f1774d..4e134c68 100644
--- a/packages/tosu/src/states/beatmap.ts
+++ b/packages/tosu/src/states/beatmap.ts
@@ -1,11 +1,7 @@
import rosu from '@kotrikd/rosu-pp';
import { ClientType, config, wLogger } from '@tosu/common';
import fs from 'fs';
-import {
- BeatmapBreakEvent,
- Beatmap as ParsedBeatmap,
- TimingPoint
-} from 'osu-classes';
+import { Beatmap as ParsedBeatmap, TimingPoint } from 'osu-classes';
import { BeatmapDecoder } from 'osu-parsers';
import path from 'path';
@@ -77,7 +73,21 @@ interface BeatmapPPTimings {
full: number;
}
+interface BreakPoint {
+ hasEffect: boolean;
+ start: number;
+ end: number;
+}
+
+interface KiaiPoint {
+ start: number;
+ end: number;
+}
+
export class BeatmapPP extends AbstractState {
+ isKiai: boolean;
+ isBreak: boolean;
+
beatmap?: rosu.Beatmap;
lazerBeatmap?: ParsedBeatmap;
performanceAttributes?: rosu.PerformanceAttributes;
@@ -123,7 +133,8 @@ export class BeatmapPP extends AbstractState {
};
timingPoints: TimingPoint[] = [];
- breaks: BeatmapBreakEvent[] = [];
+ breaks: BreakPoint[] = [];
+ kiais: KiaiPoint[] = [];
constructor(game: AbstractInstance) {
super(game);
@@ -132,6 +143,9 @@ export class BeatmapPP extends AbstractState {
}
init() {
+ this.isKiai = false;
+ this.isBreak = false;
+
this.strains = [];
this.strainsAll = {
series: [],
@@ -208,6 +222,7 @@ export class BeatmapPP extends AbstractState {
};
this.timingPoints = [];
this.breaks = [];
+ this.kiais = [];
}
updatePPAttributes(
@@ -466,7 +481,11 @@ export class BeatmapPP extends AbstractState {
this.minBPM = Math.round(bpmMin * this.clockRate);
this.maxBPM = Math.round(bpmMax * this.clockRate);
- this.breaks = this.lazerBeatmap.events.breaks;
+ this.breaks = this.lazerBeatmap.events.breaks.map((r) => ({
+ hasEffect: r.hasEffect,
+ start: r.startTime,
+ end: r.endTime
+ }));
const firstObj = Math.round(
this.lazerBeatmap.hitObjects.at(0)?.startTime ?? 0
@@ -481,6 +500,20 @@ export class BeatmapPP extends AbstractState {
this.timingPoints =
this.lazerBeatmap.controlPoints.timingPoints;
+ const kiais: KiaiPoint[] = [];
+ const points = this.lazerBeatmap.controlPoints.effectPoints;
+ for (let i = 0; i < points.length; i++) {
+ const point = points[i];
+ if (point.kiai === false) {
+ kiais[kiais.length - 1].end = point.startTime;
+ continue;
+ }
+
+ kiais.push({ start: point.startTime, end: -1 });
+ }
+
+ this.kiais = kiais;
+
this.resetReportCount('beatmapPP updateMapMetadataTimings');
} catch (exc) {
this.reportError(
@@ -799,17 +832,20 @@ export class BeatmapPP extends AbstractState {
}
}
- updateRealTimeBPM(timeMS: number, multiply: number) {
+ updateEventsStatus(ms: number, multiply: number) {
if (!this.lazerBeatmap) return;
const bpm =
this.lazerBeatmap.controlPoints.timingPoints
- // @ts-ignore
+ // @ts-expect-error
.toReversed()
- .find((r) => r.startTime <= timeMS && r.bpm !== 0)?.bpm ||
+ .find((r) => r.startTime <= ms && r.bpm !== 0)?.bpm ||
this.lazerBeatmap.controlPoints.timingPoints[0]?.bpm ||
0.0;
this.realtimeBPM = Math.round(bpm * multiply);
+
+ this.isKiai = this.kiais.some((r) => ms >= r.start && ms <= r.end);
+ this.isBreak = this.breaks.some((r) => ms >= r.start && ms <= r.end);
}
}
From 25077eff00fb115d02c02190fb8c65c5fd67391e Mon Sep 17 00:00:00 2001
From: ck <21735205+cyperdark@users.noreply.github.com>
Date: Tue, 21 Jan 2025 15:21:57 +0300
Subject: [PATCH 5/6] feat: Allow wildcard ip matching in `ALLOWED_IPS`
---
packages/server/index.ts | 1 -
packages/server/utils/counters.ts | 2 +-
packages/server/utils/index.ts | 29 +++++++++++++++++++++++------
3 files changed, 24 insertions(+), 8 deletions(-)
diff --git a/packages/server/index.ts b/packages/server/index.ts
index 708cb495..ca96c20d 100644
--- a/packages/server/index.ts
+++ b/packages/server/index.ts
@@ -121,7 +121,6 @@ export class Server {
}
wLogger.warn('[request]', 'Unallowed request', req.url, {
- address: req.socket.remoteAddress,
origin: req.headers.origin,
referer: req.headers.referer
});
diff --git a/packages/server/utils/counters.ts b/packages/server/utils/counters.ts
index 5ae1d2c4..79244eab 100644
--- a/packages/server/utils/counters.ts
+++ b/packages/server/utils/counters.ts
@@ -712,7 +712,7 @@ export function buildSettings(res: http.ServerResponse) {
.replace('{NAME}', 'ALLOWED_IPS')
.replace(
'{DESCRIPTION}',
- `Specify IP's which allowed to change and access tosu API's`
+ `IP's or domain names, which allowed to change and access tosu API's.
- Supports wildcard syntax: 192.*.60.*
- Compares against values printed out in «Unallowed request» message in console`
)
.replace(
'{INPUT}',
diff --git a/packages/server/utils/index.ts b/packages/server/utils/index.ts
index 121a8a0c..32cbab83 100644
--- a/packages/server/utils/index.ts
+++ b/packages/server/utils/index.ts
@@ -116,12 +116,29 @@ function isAllowedIP(url: string | undefined) {
const allowedIPs = config.allowedIPs.split(',');
try {
- const parseURL = new URL(url);
- return allowedIPs.some(
- (r) =>
- r.toLowerCase().trim() ===
- parseURL.hostname.toLowerCase().trim()
- );
+ const hostname = new URL(url).hostname.toLowerCase().trim();
+ return allowedIPs.some((pattern) => {
+ // compare IP's length and match wildcard like comparision
+ if (pattern.includes('*') && pattern.includes('.')) {
+ const patternLength = pattern.match(/\./g)?.length || 0;
+ const hostnameLength = hostname.match(/\./g)?.length || 0;
+
+ if (patternLength !== 3 || hostnameLength !== 3) return false;
+
+ const patternParts = pattern.split('.');
+ const hostnameParts = hostname.split('.');
+
+ const matches = hostnameParts.filter((r, index) => {
+ if (patternParts[index] === '*') return true;
+
+ return patternParts[index] === r;
+ });
+
+ return matches.length === 4;
+ }
+
+ return pattern.toLowerCase().trim() === hostname;
+ });
} catch (error) {
return allowedIPs.some(
(r) => r.toLowerCase().trim() === url.toLowerCase().trim()
From 3f6518322f10edf4a06bc19b6ae0c61f872adc38 Mon Sep 17 00:00:00 2001
From: ck <21735205+cyperdark@users.noreply.github.com>
Date: Wed, 22 Jan 2025 11:12:43 +0300
Subject: [PATCH 6/6] chore: fix pr issues
---
packages/tosu/src/api/utils/buildResult.ts | 2 +-
packages/tosu/src/api/utils/buildResultV2.ts | 4 ++--
packages/tosu/src/states/gameplay.ts | 6 +++---
packages/tosu/src/states/resultScreen.ts | 4 ++--
packages/tosu/src/utils/calculators.ts | 14 +++++++-------
5 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/packages/tosu/src/api/utils/buildResult.ts b/packages/tosu/src/api/utils/buildResult.ts
index 60b7263f..50d99082 100644
--- a/packages/tosu/src/api/utils/buildResult.ts
+++ b/packages/tosu/src/api/utils/buildResult.ts
@@ -230,7 +230,7 @@ export const buildResult = (instanceManager: InstanceManager): ApiAnswer => {
name: resultScreen.playerName,
score: resultScreen.score,
accuracy: calculateAccuracy({
- _round: true,
+ isRound: true,
hits: resultScreenHits,
mode: gameplay.mode
}),
diff --git a/packages/tosu/src/api/utils/buildResultV2.ts b/packages/tosu/src/api/utils/buildResultV2.ts
index b20f6266..7b4f577f 100644
--- a/packages/tosu/src/api/utils/buildResultV2.ts
+++ b/packages/tosu/src/api/utils/buildResultV2.ts
@@ -54,7 +54,7 @@ const convertMemoryPlayerToResult = (
name: memoryPlayer.name,
score: memoryPlayer.score,
- accuracy: calculateAccuracy({ _round: true, hits, mode: gameMode }),
+ accuracy: calculateAccuracy({ isRound: true, hits, mode: gameMode }),
hits,
@@ -70,7 +70,7 @@ const convertMemoryPlayerToResult = (
rate: memoryPlayer.mods.rate
},
rank: calculateGrade({
- _lazer: client === ClientType.lazer,
+ isLazer: client === ClientType.lazer,
mods: memoryPlayer.mods.number,
mode: gameMode,
hits
diff --git a/packages/tosu/src/states/gameplay.ts b/packages/tosu/src/states/gameplay.ts
index ed35df64..38541dcd 100644
--- a/packages/tosu/src/states/gameplay.ts
+++ b/packages/tosu/src/states/gameplay.ts
@@ -104,7 +104,7 @@ export class Gameplay extends AbstractState {
this.accuracy = 100.0;
this.unstableRate = 0;
this.gradeCurrent = calculateGrade({
- _lazer: this.game.client === ClientType.lazer,
+ isLazer: this.game.client === ClientType.lazer,
mods: this.mods.number,
mode: this.mode,
hits: {
@@ -411,7 +411,7 @@ export class Gameplay extends AbstractState {
objectCount - this.hit300 - this.hit100 - this.hit50 - this.hitMiss;
this.gradeCurrent = calculateGrade({
- _lazer: this.game.client === ClientType.lazer,
+ isLazer: this.game.client === ClientType.lazer,
mods: this.mods.number,
mode: this.mode,
hits: {
@@ -425,7 +425,7 @@ export class Gameplay extends AbstractState {
});
this.gradeExpected = calculateGrade({
- _lazer: this.game.client === ClientType.lazer,
+ isLazer: this.game.client === ClientType.lazer,
mods: this.mods.number,
mode: this.mode,
hits: {
diff --git a/packages/tosu/src/states/resultScreen.ts b/packages/tosu/src/states/resultScreen.ts
index ddb1a0fe..6c070756 100644
--- a/packages/tosu/src/states/resultScreen.ts
+++ b/packages/tosu/src/states/resultScreen.ts
@@ -109,14 +109,14 @@ export class ResultScreen extends AbstractState {
};
this.grade = calculateGrade({
- _lazer: this.game.client === ClientType.lazer,
+ isLazer: this.game.client === ClientType.lazer,
mods: this.mods.number,
mode: this.mode,
hits
});
this.accuracy = calculateAccuracy({
- _round: true,
+ isRound: true,
mode: this.mode,
hits
});
diff --git a/packages/tosu/src/utils/calculators.ts b/packages/tosu/src/utils/calculators.ts
index 68b4d993..c7a4a862 100644
--- a/packages/tosu/src/utils/calculators.ts
+++ b/packages/tosu/src/utils/calculators.ts
@@ -6,9 +6,9 @@ import { OsuMods } from '@/utils/osuMods.types';
export const calculateAccuracy = ({
hits,
mode,
- _round
+ isRound
}: {
- _round: boolean;
+ isRound: boolean;
hits: {
300: any;
100: any;
@@ -55,7 +55,7 @@ export const calculateAccuracy = ({
break;
}
- if (_round === true) return parseFloat(acc.toFixed(2));
+ if (isRound === true) return parseFloat(acc.toFixed(2));
return acc;
};
@@ -63,12 +63,12 @@ export const calculateAccuracy = ({
* Used to calculate grade out of hits
*/
export const calculateGrade = ({
- _lazer,
+ isLazer,
hits,
mods,
mode
}: {
- _lazer: boolean;
+ isLazer: boolean;
hits: {
300: number;
100: number;
@@ -92,10 +92,10 @@ export const calculateGrade = ({
(mods & OsuMods.Flashlight) === OsuMods.Flashlight;
}
- const acc = calculateAccuracy({ hits, mode, _round: false }) / 100;
+ const acc = calculateAccuracy({ hits, mode, isRound: false }) / 100;
let rank = '';
- if (_lazer === true) {
+ if (isLazer === true) {
switch (mode) {
case 0:
case 1: {