DatabaseBeatmap.h 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. #pragma once
  2. #include "DifficultyCalculator.h"
  3. #include "Osu.h"
  4. #include "Resource.h"
  5. class Osu;
  6. class Beatmap;
  7. class HitObject;
  8. class Database;
  9. class BackgroundImageHandler;
  10. // purpose:
  11. // 1) contain all infos which are ALWAYS kept in memory for beatmaps
  12. // 2) be the data source for Beatmap when starting a difficulty
  13. // 3) allow async calculations/loaders to work on the contained data (e.g. background image loader)
  14. // 4) be a container for difficulties (all top level DatabaseBeatmap objects are containers)
  15. class DatabaseBeatmap {
  16. public:
  17. // raw structs
  18. struct TIMINGPOINT {
  19. long offset;
  20. float msPerBeat;
  21. int sampleType;
  22. int sampleSet;
  23. int volume;
  24. bool timingChange;
  25. bool kiai;
  26. unsigned long long sortHack;
  27. };
  28. struct BREAK {
  29. int startTime;
  30. int endTime;
  31. };
  32. // custom structs
  33. struct LOAD_DIFFOBJ_RESULT {
  34. int errorCode;
  35. std::vector<OsuDifficultyHitObject> diffobjects;
  36. int maxPossibleCombo;
  37. LOAD_DIFFOBJ_RESULT() {
  38. errorCode = 0;
  39. maxPossibleCombo = 0;
  40. }
  41. };
  42. struct LOAD_GAMEPLAY_RESULT {
  43. int errorCode;
  44. std::vector<HitObject *> hitobjects;
  45. std::vector<BREAK> breaks;
  46. std::vector<Color> combocolors;
  47. int randomSeed;
  48. LOAD_GAMEPLAY_RESULT() {
  49. errorCode = 0;
  50. randomSeed = 0;
  51. }
  52. };
  53. struct TIMING_INFO {
  54. long offset;
  55. float beatLengthBase;
  56. float beatLength;
  57. float volume;
  58. int sampleType;
  59. int sampleSet;
  60. bool isNaN;
  61. };
  62. DatabaseBeatmap(Osu *osu, std::string filePath, std::string folder, bool filePathIsInMemoryBeatmap = false);
  63. DatabaseBeatmap(Osu *osu, std::vector<DatabaseBeatmap *> *difficulties);
  64. ~DatabaseBeatmap();
  65. static LOAD_DIFFOBJ_RESULT loadDifficultyHitObjects(const std::string &osuFilePath, float AR, float CS,
  66. float speedMultiplier, bool calculateStarsInaccurately = false);
  67. static LOAD_DIFFOBJ_RESULT loadDifficultyHitObjects(const std::string &osuFilePath, float AR, float CS,
  68. float speedMultiplier, bool calculateStarsInaccurately,
  69. const std::atomic<bool> &dead);
  70. static bool loadMetadata(DatabaseBeatmap *databaseBeatmap);
  71. static LOAD_GAMEPLAY_RESULT loadGameplay(DatabaseBeatmap *databaseBeatmap, Beatmap *beatmap);
  72. void setDifficulties(std::vector<DatabaseBeatmap *> *difficulties);
  73. void setLengthMS(unsigned long lengthMS) { m_iLengthMS = lengthMS; }
  74. void setStarsNoMod(float starsNoMod) { m_fStarsNomod = starsNoMod; }
  75. void setNumObjects(int numObjects) { m_iNumObjects = numObjects; }
  76. void setNumCircles(int numCircles) { m_iNumCircles = numCircles; }
  77. void setNumSliders(int numSliders) { m_iNumSliders = numSliders; }
  78. void setNumSpinners(int numSpinners) { m_iNumSpinners = numSpinners; }
  79. void setLocalOffset(long localOffset) { m_iLocalOffset = localOffset; }
  80. inline Osu *getOsu() const { return m_osu; }
  81. inline std::string getFolder() const { return m_sFolder; }
  82. inline std::string getFilePath() const { return m_sFilePath; }
  83. inline unsigned long long getSortHack() const { return m_iSortHack; }
  84. inline const std::vector<DatabaseBeatmap *> &getDifficulties() const { return *m_difficulties; }
  85. inline const MD5Hash &getMD5Hash() const { return m_sMD5Hash; }
  86. TIMING_INFO getTimingInfoForTime(unsigned long positionMS);
  87. static TIMING_INFO getTimingInfoForTimeAndTimingPoints(unsigned long positionMS,
  88. const zarray<TIMINGPOINT> &timingpoints);
  89. // raw metadata
  90. inline int getVersion() const { return m_iVersion; }
  91. inline int getGameMode() const { return m_iGameMode; }
  92. inline int getID() const { return m_iID; }
  93. inline int getSetID() const { return m_iSetID; }
  94. inline const std::string &getTitle() const { return m_sTitle; }
  95. inline const std::string &getArtist() const { return m_sArtist; }
  96. inline const std::string &getCreator() const { return m_sCreator; }
  97. inline const std::string &getDifficultyName() const { return m_sDifficultyName; }
  98. inline const std::string &getSource() const { return m_sSource; }
  99. inline const std::string &getTags() const { return m_sTags; }
  100. inline const std::string &getBackgroundImageFileName() const { return m_sBackgroundImageFileName; }
  101. inline const std::string &getAudioFileName() const { return m_sAudioFileName; }
  102. inline unsigned long getLengthMS() const { return m_iLengthMS; }
  103. inline int getPreviewTime() const { return m_iPreviewTime; }
  104. inline float getAR() const { return m_fAR; }
  105. inline float getCS() const { return m_fCS; }
  106. inline float getHP() const { return m_fHP; }
  107. inline float getOD() const { return m_fOD; }
  108. inline float getStackLeniency() const { return m_fStackLeniency; }
  109. inline float getSliderTickRate() const { return m_fSliderTickRate; }
  110. inline float getSliderMultiplier() const { return m_fSliderMultiplier; }
  111. inline const zarray<TIMINGPOINT> &getTimingpoints() const { return m_timingpoints; }
  112. std::string getFullSoundFilePath();
  113. // redundant data
  114. inline const std::string &getFullBackgroundImageFilePath() const { return m_sFullBackgroundImageFilePath; }
  115. // precomputed data
  116. inline float getStarsNomod() const { return m_fStarsNomod; }
  117. inline int getMinBPM() const { return m_iMinBPM; }
  118. inline int getMaxBPM() const { return m_iMaxBPM; }
  119. inline int getMostCommonBPM() const { return m_iMostCommonBPM; }
  120. inline int getNumObjects() const { return m_iNumObjects; }
  121. inline int getNumCircles() const { return m_iNumCircles; }
  122. inline int getNumSliders() const { return m_iNumSliders; }
  123. inline int getNumSpinners() const { return m_iNumSpinners; }
  124. // custom data
  125. long long last_modification_time;
  126. inline long getLocalOffset() const { return m_iLocalOffset; }
  127. inline long getOnlineOffset() const { return m_iOnlineOffset; }
  128. bool do_not_store = false;
  129. private:
  130. // raw metadata
  131. int m_iVersion; // e.g. "osu file format v12" -> 12
  132. int m_iGameMode; // 0 = osu!standard, 1 = Taiko, 2 = Catch the Beat, 3 = osu!mania
  133. long m_iID; // online ID, if uploaded
  134. int m_iSetID; // online set ID, if uploaded
  135. std::string m_sTitle;
  136. std::string m_sArtist;
  137. std::string m_sCreator;
  138. std::string m_sDifficultyName; // difficulty name ("Version")
  139. std::string m_sSource; // only used by search
  140. std::string m_sTags; // only used by search
  141. std::string m_sBackgroundImageFileName;
  142. std::string m_sAudioFileName;
  143. unsigned long m_iLengthMS;
  144. int m_iPreviewTime;
  145. float m_fAR;
  146. float m_fCS;
  147. float m_fHP;
  148. float m_fOD;
  149. float m_fStackLeniency;
  150. float m_fSliderTickRate;
  151. float m_fSliderMultiplier;
  152. zarray<TIMINGPOINT> m_timingpoints; // necessary for main menu anim
  153. // redundant data (technically contained in metadata, but precomputed anyway)
  154. std::string m_sFullSoundFilePath;
  155. std::string m_sFullBackgroundImageFilePath;
  156. // precomputed data (can-run-without-but-nice-to-have data)
  157. float m_fStarsNomod;
  158. int m_iMinBPM = 0;
  159. int m_iMaxBPM = 0;
  160. int m_iMostCommonBPM = 0;
  161. int m_iNumObjects;
  162. int m_iNumCircles;
  163. int m_iNumSliders;
  164. int m_iNumSpinners;
  165. // custom data (not necessary, not part of the beatmap file, and not precomputed)
  166. long m_iLocalOffset;
  167. long m_iOnlineOffset;
  168. // primitive objects
  169. struct HITCIRCLE {
  170. int x, y;
  171. unsigned long time;
  172. int sampleType;
  173. int number;
  174. int colorCounter;
  175. int colorOffset;
  176. bool clicked;
  177. };
  178. struct SLIDER {
  179. int x, y;
  180. char type;
  181. int repeat;
  182. float pixelLength;
  183. long time;
  184. int sampleType;
  185. int number;
  186. int colorCounter;
  187. int colorOffset;
  188. std::vector<Vector2> points;
  189. std::vector<int> hitSounds;
  190. float sliderTime;
  191. float sliderTimeWithoutRepeats;
  192. std::vector<float> ticks;
  193. std::vector<OsuDifficultyHitObject::SLIDER_SCORING_TIME> scoringTimesForStarCalc;
  194. };
  195. struct SPINNER {
  196. int x, y;
  197. unsigned long time;
  198. int sampleType;
  199. unsigned long endTime;
  200. };
  201. struct PRIMITIVE_CONTAINER {
  202. int errorCode;
  203. std::vector<HITCIRCLE> hitcircles;
  204. std::vector<SLIDER> sliders;
  205. std::vector<SPINNER> spinners;
  206. std::vector<BREAK> breaks;
  207. zarray<TIMINGPOINT> timingpoints;
  208. std::vector<Color> combocolors;
  209. float stackLeniency;
  210. float sliderMultiplier;
  211. float sliderTickRate;
  212. int version;
  213. };
  214. struct CALCULATE_SLIDER_TIMES_CLICKS_TICKS_RESULT {
  215. int errorCode;
  216. };
  217. // class internal data (custom)
  218. friend class Database;
  219. friend class BackgroundImageHandler;
  220. friend class DatabaseBeatmapStarCalculator;
  221. static unsigned long long sortHackCounter;
  222. static ConVar *m_osu_slider_curve_max_length_ref;
  223. static ConVar *m_osu_stars_xexxar_angles_sliders_ref;
  224. static ConVar *m_osu_stars_stacking_ref;
  225. static ConVar *m_osu_debug_pp_ref;
  226. static ConVar *m_osu_slider_end_inside_check_offset_ref;
  227. static PRIMITIVE_CONTAINER loadPrimitiveObjects(const std::string &osuFilePath,
  228. bool filePathIsInMemoryBeatmap = false);
  229. static PRIMITIVE_CONTAINER loadPrimitiveObjects(const std::string &osuFilePath, bool filePathIsInMemoryBeatmap,
  230. const std::atomic<bool> &dead);
  231. static CALCULATE_SLIDER_TIMES_CLICKS_TICKS_RESULT calculateSliderTimesClicksTicks(int beatmapVersion,
  232. std::vector<SLIDER> &sliders,
  233. zarray<TIMINGPOINT> &timingpoints,
  234. float sliderMultiplier,
  235. float sliderTickRate);
  236. static CALCULATE_SLIDER_TIMES_CLICKS_TICKS_RESULT calculateSliderTimesClicksTicks(
  237. int beatmapVersion, std::vector<SLIDER> &sliders, zarray<TIMINGPOINT> &timingpoints, float sliderMultiplier,
  238. float sliderTickRate, const std::atomic<bool> &dead);
  239. Osu *m_osu;
  240. std::string m_sFolder; // path to folder containing .osu file (e.g. "/path/to/beatmapfolder/")
  241. std::string m_sFilePath; // path to .osu file (e.g. "/path/to/beatmapfolder/beatmap.osu")
  242. bool m_bFilePathIsInMemoryBeatmap;
  243. unsigned long long m_iSortHack;
  244. std::vector<DatabaseBeatmap *> *m_difficulties = nullptr;
  245. MD5Hash m_sMD5Hash;
  246. // helper functions
  247. struct TimingPointSortComparator {
  248. bool operator()(DatabaseBeatmap::TIMINGPOINT const &a, DatabaseBeatmap::TIMINGPOINT const &b) const {
  249. // first condition: offset
  250. // second condition: if offset is the same, non-inherited timingpoints go before inherited timingpoints
  251. // strict weak ordering!
  252. if(a.offset == b.offset && ((a.msPerBeat >= 0 && b.msPerBeat < 0) == (b.msPerBeat >= 0 && a.msPerBeat < 0)))
  253. return a.sortHack < b.sortHack;
  254. else
  255. return (a.offset < b.offset) || (a.offset == b.offset && a.msPerBeat >= 0 && b.msPerBeat < 0);
  256. }
  257. };
  258. };
  259. class DatabaseBeatmapBackgroundImagePathLoader : public Resource {
  260. public:
  261. DatabaseBeatmapBackgroundImagePathLoader(const std::string &filePath);
  262. inline const std::string &getLoadedBackgroundImageFileName() const { return m_sLoadedBackgroundImageFileName; }
  263. private:
  264. virtual void init();
  265. virtual void initAsync();
  266. virtual void destroy() { ; }
  267. std::string m_sFilePath;
  268. std::string m_sLoadedBackgroundImageFileName;
  269. };
  270. class DatabaseBeatmapStarCalculator : public Resource {
  271. public:
  272. DatabaseBeatmapStarCalculator();
  273. bool isDead() const { return m_bDead.load(); }
  274. void kill() { m_bDead = true; }
  275. void revive() { m_bDead = false; }
  276. void setBeatmapDifficulty(DatabaseBeatmap *diff2, float AR, float CS, float OD, float speedMultiplier, bool relax,
  277. bool touchDevice);
  278. inline DatabaseBeatmap *getBeatmapDifficulty() const { return m_diff2; }
  279. inline double getTotalStars() const { return m_totalStars.load(); }
  280. inline double getAimStars() const { return m_aimStars.load(); }
  281. inline double getSpeedStars() const { return m_speedStars.load(); }
  282. inline double getPPv2() const { return m_pp.load(); } // NOTE: pp with currently active mods (runtime mods)
  283. inline long getLengthMS() const { return m_iLengthMS.load(); }
  284. inline const std::vector<double> &getAimStrains() const { return m_aimStrains; }
  285. inline const std::vector<double> &getSpeedStrains() const { return m_speedStrains; }
  286. inline int getNumObjects() const { return m_iNumObjects.load(); }
  287. inline int getNumCircles() const { return m_iNumCircles.load(); }
  288. inline int getNumSpinners() const { return m_iNumSpinners.load(); }
  289. private:
  290. virtual void init();
  291. virtual void initAsync();
  292. virtual void destroy() { ; }
  293. std::atomic<bool> m_bDead;
  294. DatabaseBeatmap *m_diff2;
  295. float m_fAR;
  296. float m_fCS;
  297. float m_fOD;
  298. float m_fSpeedMultiplier;
  299. bool m_bRelax;
  300. bool m_bTouchDevice;
  301. std::atomic<double> m_totalStars;
  302. std::atomic<double> m_aimStars;
  303. std::atomic<double> m_aimSliderFactor;
  304. std::atomic<double> m_speedStars;
  305. std::atomic<double> m_speedNotes;
  306. std::atomic<double> m_pp;
  307. std::atomic<long> m_iLengthMS;
  308. std::vector<double> m_aimStrains;
  309. std::vector<double> m_speedStrains;
  310. // custom
  311. int m_iErrorCode;
  312. std::atomic<int> m_iNumObjects;
  313. std::atomic<int> m_iNumCircles;
  314. std::atomic<int> m_iNumSliders;
  315. std::atomic<int> m_iNumSpinners;
  316. int m_iMaxPossibleCombo;
  317. };
  318. struct BPMInfo {
  319. i32 min;
  320. i32 max;
  321. i32 most_common;
  322. };
  323. template <typename T>
  324. struct BPMInfo getBPM(const zarray<T> &timing_points) {
  325. if(timing_points.empty()) {
  326. return BPMInfo{
  327. .min = 0,
  328. .max = 0,
  329. .most_common = 0,
  330. };
  331. }
  332. struct Tuple {
  333. i32 bpm;
  334. i32 duration;
  335. };
  336. zarray<Tuple> bpms;
  337. bpms.reserve(timing_points.size());
  338. long lastTime = timing_points[timing_points.size() - 1].offset;
  339. for(size_t i = 0; i < timing_points.size(); i++) {
  340. const T &t = timing_points[i];
  341. if(t.offset > lastTime) continue;
  342. if(t.msPerBeat < 0) continue;
  343. // "osu-stable forced the first control point to start at 0."
  344. // "This is reproduced here to maintain compatibility around osu!mania scroll speed and song
  345. // select display."
  346. const long currentTime = (i == 0 ? 0 : t.offset);
  347. const long nextTime = (i == timing_points.size() - 1 ? lastTime : timing_points[i + 1].offset);
  348. i32 bpm = t.msPerBeat / 60000;
  349. i32 duration = std::max(nextTime - currentTime, (long)0);
  350. bool found = false;
  351. for(auto tuple : bpms) {
  352. if(tuple.bpm == bpm) {
  353. tuple.duration += duration;
  354. found = true;
  355. break;
  356. }
  357. }
  358. if(!found) {
  359. bpms.push_back(Tuple{
  360. .bpm = bpm,
  361. .duration = duration,
  362. });
  363. }
  364. }
  365. i32 min = 9001;
  366. i32 max = 0;
  367. i32 mostCommonBPM = 0;
  368. i32 longestDuration = 0;
  369. for(auto tuple : bpms) {
  370. if(tuple.bpm > max) max = tuple.bpm;
  371. if(tuple.bpm < min) min = tuple.bpm;
  372. if(tuple.duration > longestDuration) {
  373. longestDuration = tuple.duration;
  374. mostCommonBPM = tuple.bpm;
  375. }
  376. }
  377. return BPMInfo{
  378. .min = min,
  379. .max = max,
  380. .most_common = mostCommonBPM,
  381. };
  382. }