From 1aa4f58e467aaef65469125c204b99de9383cb0e Mon Sep 17 00:00:00 2001 From: "Wei-Cheng Yeh (IID)" Date: Tue, 10 Dec 2024 04:32:20 +0800 Subject: [PATCH] 0.6.0.27 - support stretching bar roll as in TaikoJiro (#757) - [Feat] Make bar drumrolls stretchable - [Feat] Detect and hide screen-obscuring bar drumrolls when any tips are out of screen - [Fix] fix bar drumroll end stuck at judgement mark if occurred at 0ms since #START - [Fix] fix fuze rolls stretched back when exploded - [Fix] fix balloon-type notes not stayed on judgement mark vertically during their duration - [Fix] fix wrong drawn position of rotated bar-type roll tails --- OpenTaiko/src/Songs/CTja.cs | 4 + OpenTaiko/src/Songs/TJA/CChip.cs | 10 +- ...73\351\235\242\345\205\261\351\200\232.cs" | 21 ++- ...51\343\203\240\347\224\273\351\235\242.cs" | 113 +++++++++++--- .../src/Stages/07.Game/Taiko/NotesManager.cs | 145 ++++++++---------- 5 files changed, 181 insertions(+), 112 deletions(-) diff --git a/OpenTaiko/src/Songs/CTja.cs b/OpenTaiko/src/Songs/CTja.cs index ff2c658f..52e5b403 100644 --- a/OpenTaiko/src/Songs/CTja.cs +++ b/OpenTaiko/src/Songs/CTja.cs @@ -5016,6 +5016,10 @@ private void InsertNoteAtDefCursor(int noteType, int iDiv, int divsPerMeasure, E chipHead.nNoteEndPosition = chip.nNoteEndPosition = chip.n発声位置; chipHead.nNoteEndTimems = chip.nNoteEndTimems = chip.n発声時刻ms; chipHead.fBMSCROLLTime_end = chip.fBMSCROLLTime_end = chip.fBMSCROLLTime; + chipHead.dbBPM_end = chip.dbBPM_end = chip.dbBPM; + chipHead.dbSCROLL_end = chip.dbSCROLL_end = chip.dbSCROLL; + chipHead.dbSCROLL_Y_end = chip.dbSCROLL_Y_end = chip.dbSCROLL_Y; + chipHead.eScrollMode_end = chip.eScrollMode_end = chip.eScrollMode; chip.nノーツ出現時刻ms = chipHead.nノーツ出現時刻ms; chip.nノーツ移動開始時刻ms = chipHead.nノーツ移動開始時刻ms; diff --git a/OpenTaiko/src/Songs/TJA/CChip.cs b/OpenTaiko/src/Songs/TJA/CChip.cs index 7d378503..a56438de 100644 --- a/OpenTaiko/src/Songs/TJA/CChip.cs +++ b/OpenTaiko/src/Songs/TJA/CChip.cs @@ -5,6 +5,7 @@ namespace OpenTaiko; internal class CChip : IComparable, ICloneable { public EScrollMode eScrollMode; + public EScrollMode eScrollMode_end; public bool bHit; public bool bVisible = true; public bool bHideBarLine = true; @@ -15,11 +16,14 @@ internal class CChip : IComparable, ICloneable { public double dbChipSizeRatio = 1.0; public double db実数値; public double dbBPM; + public double dbBPM_end; public float fNow_Measure_s = 4.0f;//強制分岐のために追加.2020.04.21.akasoko26 public float fNow_Measure_m = 4.0f;//強制分岐のために追加.2020.04.21.akasoko26 public bool IsEndedBranching = false;//分岐が終わった時の連打譜面が非可視化になってしまうためフラグを追加.2020.04.21.akasoko26 public double dbSCROLL; public double dbSCROLL_Y; + public double dbSCROLL_end; + public double dbSCROLL_Y_end; public ECourse nBranch; public int nSenote; public int nState; @@ -173,12 +177,12 @@ public void t初期化() { this.nHorizontalChipDistance = 0; this.nNoteTipDistance_X = 0; this.nNoteTipDistance_Y = 0; - this.dbBPM = 120.0; + this.dbBPM_end = this.dbBPM = 120.0; this.fNow_Measure_m = 4.0f; this.fNow_Measure_s = 4.0f; this.nScrollDirection = 0; - this.dbSCROLL = 1.0; - this.dbSCROLL_Y = 0.0f; + this.dbSCROLL_end = this.dbSCROLL = 1.0; + this.dbSCROLL_Y_end = this.dbSCROLL_Y = 0.0f; } public override string ToString() { diff --git "a/OpenTaiko/src/Stages/07.Game/CStage\346\274\224\345\245\217\347\224\273\351\235\242\345\205\261\351\200\232.cs" "b/OpenTaiko/src/Stages/07.Game/CStage\346\274\224\345\245\217\347\224\273\351\235\242\345\205\261\351\200\232.cs" index ce68f44e..07968066 100755 --- "a/OpenTaiko/src/Stages/07.Game/CStage\346\274\224\345\245\217\347\224\273\351\235\242\345\205\261\351\200\232.cs" +++ "b/OpenTaiko/src/Stages/07.Game/CStage\346\274\224\345\245\217\347\224\273\351\235\242\345\205\261\351\200\232.cs" @@ -2868,13 +2868,19 @@ protected bool t進行描画_チップ(EInstrumentPad ePlayMode, int nPlayer) { CChip pChip = dTX.listChip[nCurrentTopChip]; //Debug.WriteLine( "nCurrentTopChip=" + nCurrentTopChip + ", ch=" + pChip.nチャンネル番号.ToString("x2") + ", 発音位置=" + pChip.n発声位置 + ", 発声時刻ms=" + pChip.n発声時刻ms ); long time = pChip.n発声時刻ms - n現在時刻ms; - - double _scrollSpeed = pChip.dbSCROLL * (dbCurrentScrollSpeed[nPlayer] + 1.0) / 10.0; - double _scrollSpeed_Y = pChip.dbSCROLL_Y * (dbCurrentScrollSpeed[nPlayer] + 1.0) / 10.0; - pChip.nHorizontalChipDistance = NotesManager.GetNoteX(pChip, time * pChip.dbBPM, _scrollSpeed, OpenTaiko.Skin.Game_Notes_Interval, play_bpm_time, pChip.eScrollMode, false); - if (pChip.nNoteEndTimems != 0) { - pChip.nNoteTipDistance_X = NotesManager.GetNoteX(pChip, (pChip.nNoteEndTimems - n現在時刻ms) * pChip.dbBPM, _scrollSpeed, OpenTaiko.Skin.Game_Notes_Interval, play_bpm_time, pChip.eScrollMode, true); - pChip.nNoteTipDistance_Y = NotesManager.GetNoteY(pChip, (pChip.nNoteEndTimems - n現在時刻ms) * pChip.dbBPM, _scrollSpeed_Y, OpenTaiko.Skin.Game_Notes_Interval, play_bpm_time, pChip.eScrollMode, true); + double th16DBeat = pChip.fBMSCROLLTime - play_bpm_time; + double _scroll_rate = (dbCurrentScrollSpeed[nPlayer] + 1.0) / 10.0; + + double _scrollSpeed = pChip.dbSCROLL * _scroll_rate; + double _scrollSpeed_Y = pChip.dbSCROLL_Y * _scroll_rate; + pChip.nHorizontalChipDistance = NotesManager.GetNoteX(time, th16DBeat, pChip.dbBPM, _scrollSpeed, pChip.eScrollMode); + if (NotesManager.IsGenericRoll(pChip)) { + long msDTime_end = pChip.nNoteEndTimems - n現在時刻ms; + double th16DBeat_end = pChip.fBMSCROLLTime_end - play_bpm_time; + double _scrollSpeed_end = pChip.dbSCROLL_end * _scroll_rate; + double _scrollSpeed_Y_end = pChip.dbSCROLL_Y_end * _scroll_rate; + pChip.nNoteTipDistance_X = NotesManager.GetNoteX(msDTime_end, th16DBeat_end, pChip.dbBPM_end, _scrollSpeed_end, pChip.eScrollMode_end); + pChip.nNoteTipDistance_Y = NotesManager.GetNoteY(msDTime_end, th16DBeat_end, pChip.dbBPM_end, _scrollSpeed_Y_end, pChip.eScrollMode_end); } @@ -4498,6 +4504,7 @@ public void t演奏位置の変更(int nStartBar, int nPlayer) { dTX.listChip[i].bHit = false; dTX.listChip[i].bShow = true; + dTX.listChip[i].bShowRoll = true; dTX.listChip[i].bProcessed = false; dTX.listChip[i].bVisible = true; dTX.listChip[i].IsHitted = false; diff --git "a/OpenTaiko/src/Stages/07.Game/Taiko/CStage\346\274\224\345\245\217\343\203\211\343\203\251\343\203\240\347\224\273\351\235\242.cs" "b/OpenTaiko/src/Stages/07.Game/Taiko/CStage\346\274\224\345\245\217\343\203\211\343\203\251\343\203\240\347\224\273\351\235\242.cs" index 84160977..d61fee4d 100644 --- "a/OpenTaiko/src/Stages/07.Game/Taiko/CStage\346\274\224\345\245\217\343\203\211\343\203\251\343\203\240\347\224\273\351\235\242.cs" +++ "b/OpenTaiko/src/Stages/07.Game/Taiko/CStage\346\274\224\345\245\217\343\203\211\343\203\251\343\203\240\347\224\273\351\235\242.cs" @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Numerics; using System.Runtime.InteropServices; using System.Text; using DiscordRPC; @@ -1474,8 +1475,9 @@ protected override void t進行描画_チップ_Taiko(CConfigIni configIni, ref double _scrollSpeed = pChip.dbSCROLL_Y * (this.actScrollSpeed.dbConfigScrollSpeed[nPlayer] + 1.0) / 10.0; float play_bpm_time = this.GetNowPBMTime(dTX, 0); + double th16DBeat = pChip.fBMSCROLLTime - play_bpm_time; - y += NotesManager.GetNoteY(pChip, time * pChip.dbBPM, _scrollSpeed, OpenTaiko.Skin.Game_Notes_Interval, play_bpm_time, pChip.eScrollMode, false); + y += NotesManager.GetNoteY(time, th16DBeat, pChip.dbBPM, _scrollSpeed, pChip.eScrollMode); } if (bSplitLane[nPlayer] || OpenTaiko.Tx.Puchichara[PuchiChara.tGetPuchiCharaIndexByName(OpenTaiko.GetActualPlayer(nPlayer))].effect.SplitLane) { @@ -1659,6 +1661,8 @@ protected override void t進行描画_チップ_Taiko連打(CConfigIni configIni #region[ 作り直したもの ] if (pChip.bVisible) { + bool pHasBar = (NotesManager.IsRoll(pChip) || NotesManager.IsFuzeRoll(pChip)); + if (NotesManager.IsGenericRoll(pChip)) { if (pChip.nノーツ出現時刻ms != 0 && (nowTime < pChip.n発声時刻ms - pChip.nノーツ出現時刻ms)) pChip.bShow = false; @@ -1691,7 +1695,18 @@ protected override void t進行描画_チップ_Taiko連打(CConfigIni configIni long __dbt = nowTime; long time = pChip.n発声時刻ms - __dbt; float play_bpm_time = this.GetNowPBMTime(dTX, 0); - y += NotesManager.GetNoteY(pChip, time * pChip.dbBPM, _scrollSpeed, OpenTaiko.Skin.Game_Notes_Interval, play_bpm_time, pChip.eScrollMode, false); + double th16DBeat = pChip.fBMSCROLLTime - play_bpm_time; + y += NotesManager.GetNoteY(time, th16DBeat, pChip.dbBPM, _scrollSpeed, pChip.eScrollMode); + } + + if (NotesManager.IsGenericBalloon(pChip)) { + if (nowTime >= pChip.n発声時刻ms && nowTime < pChip.nNoteEndTimems) { + x = NoteOriginX[nPlayer]; + y = NoteOriginY[nPlayer]; + } else if (nowTime >= pChip.nNoteEndTimems) { + x = x末端; + y = y末端; + } } if (bSplitLane[nPlayer] || OpenTaiko.Tx.Puchichara[PuchiChara.tGetPuchiCharaIndexByName(OpenTaiko.GetActualPlayer(nPlayer))].effect.SplitLane) { @@ -1705,6 +1720,11 @@ protected override void t進行描画_チップ_Taiko連打(CConfigIni configIni y末端 -= OpenTaiko.Skin.Game_Notes_Size[1] / 2; } } + } + + bool isBodyXInScreen = (Math.Min(x, x末端) < OpenTaiko.Skin.Resolution[0] && Math.Max(x, x末端) > 0 - OpenTaiko.Skin.Game_Notes_Size[0]); + if (pHasBar) { + this.HideObscuringRoll(nPlayer, pChip, x, y, x末端, y末端, isBodyXInScreen, nowTime); } #region[ HIDSUD & STEALTH ] @@ -1718,8 +1738,7 @@ protected override void t進行描画_チップ_Taiko連打(CConfigIni configIni //if( CDTXMania.ConfigIni.eScrollMode != EScrollMode.Normal ) //x -= 10; - //if(x末端 > 0 - TJAPlayer3.Skin.Game_Notes_Size[0] && x < TJAPlayer3.Skin.Resolution[0]) - if ((Math.Min(x, x末端) < OpenTaiko.Skin.Resolution[0] && Math.Max(x, x末端) > 0 - OpenTaiko.Skin.Game_Notes_Size[0])) { + if (isBodyXInScreen) { if (OpenTaiko.Tx.Notes[(int)_gt] != null) { //int num9 = this.actCombo.n現在のコンボ数.Drums >= 50 ? this.ctチップ模様アニメ.Drums.n現在の値 * 130 : 0; //int num9 = this.actCombo.n現在のコンボ数.Drums >= 50 ? base.n現在の音符の顔番号 * 130 : 0; @@ -1797,23 +1816,18 @@ protected override void t進行描画_チップ_Taiko連打(CConfigIni configIni int _78_cut = 78 * _size[0] / 136; if (NotesManager.IsRoll(pChip) || NotesManager.IsFuzeRoll(pChip)) { - if (NotesManager.IsFuzeRoll(pChip) - && nowTime >= pChip.n発声時刻ms && nowTime < pChip.nNoteEndTimems) { - x = NoteOriginX[nPlayer]; - y = NoteOriginY[nPlayer]; - } - - NotesManager.DisplayRoll(nPlayer, x, y, pChip, num9, normalColor, effectedColor, x末端, y末端); if (OpenTaiko.Tx.SENotes[(int)_gt] != null) { int _shift = NotesManager.IsBigRoll(pChip) ? 26 : 0; if (!NotesManager.IsFuzeRoll(pChip)) { - OpenTaiko.Tx.SENotes[(int)_gt].vcScaleRatio.X = x末端 - x - 44 - _shift; - OpenTaiko.Tx.SENotes[(int)_gt].t2D描画(x + 90 + _shift, y + nSenotesY, new Rectangle(_60_cut, 8 * _size[1], 1, _size[1])); - OpenTaiko.Tx.SENotes[(int)_gt].vcScaleRatio.X = 1.0f; - OpenTaiko.Tx.SENotes[(int)_gt].t2D描画(x + 30 + _shift, y + nSenotesY, new Rectangle(0, 8 * _size[1], _60_cut, _size[1])); + if (pChip.bShowRoll) { + OpenTaiko.Tx.SENotes[(int)_gt].vcScaleRatio.X = x末端 - x - 44 - _shift; + OpenTaiko.Tx.SENotes[(int)_gt].t2D描画(x + 90 + _shift, y + nSenotesY, new Rectangle(_60_cut, 8 * _size[1], 1, _size[1])); + OpenTaiko.Tx.SENotes[(int)_gt].vcScaleRatio.X = 1.0f; + OpenTaiko.Tx.SENotes[(int)_gt].t2D描画(x + 30 + _shift, y + nSenotesY, new Rectangle(0, 8 * _size[1], _60_cut, _size[1])); + } OpenTaiko.Tx.SENotes[(int)_gt].t2D描画(x - (_shift / 13), y + nSenotesY, new Rectangle(0, _size[1] * pChip.nSenote, _size[0], _size[1])); } else { NotesManager.DisplaySENotes(nPlayer, x + nSenotesX, y + nSenotesY, pChip); @@ -1825,11 +1839,6 @@ protected override void t進行描画_チップ_Taiko連打(CConfigIni configIni if (NotesManager.IsBalloon(pChip) || NotesManager.IsKusudama(pChip)) { if (pChip.bShow) { - if (nowTime >= pChip.n発声時刻ms && nowTime < pChip.nNoteEndTimems) - x = NoteOriginX[nPlayer]; - else if (nowTime >= pChip.nNoteEndTimems) - x = (NoteOriginX[nPlayer] + pChip.nNoteTipDistance_X); - NotesManager.DisplayNote(nPlayer, x, y, pChip, num9, OpenTaiko.Skin.Game_Notes_Size[0] * 2); NotesManager.DisplaySENotes(nPlayer, x + nSenotesX, y + nSenotesY, pChip); @@ -1881,6 +1890,65 @@ protected override void t進行描画_チップ_Taiko連打(CConfigIni configIni #endregion } + /// Detect and hide screen-obscuring rolls when any tips are out of screen + private void HideObscuringRoll(int iPlayer, CChip pChip, int xHead, int yHead, int xEnd, int yEnd, bool isBodyXInScreen, long nowTime) { + // display judging rolls + if (nowTime >= pChip.n発声時刻ms && nowTime <= pChip.nNoteEndTimems) { + pChip.bShowRoll = true; + return; + } + + // ignore already out-of-screen rolls + bool isBodyYInScreen = (Math.Min(yHead, yEnd) < OpenTaiko.Skin.Resolution[1] && Math.Max(yHead, yEnd) > 0 - OpenTaiko.Skin.Game_Notes_Size[1]); + if (!(isBodyXInScreen && isBodyYInScreen)) { + return; + } + + // display completely in-screen rolls + bool headInScreen = (xHead > 0 - OpenTaiko.Skin.Game_Notes_Size[0] && xHead < OpenTaiko.Skin.Resolution[0]) + && (yHead > 0 - OpenTaiko.Skin.Game_Notes_Size[1] && yHead < OpenTaiko.Skin.Resolution[1]); + bool endInScreen = (xEnd > 0 - OpenTaiko.Skin.Game_Notes_Size[0] && xEnd < OpenTaiko.Skin.Resolution[0]) + && (yEnd > 0 - OpenTaiko.Skin.Game_Notes_Size[1] && yEnd < OpenTaiko.Skin.Resolution[1]); + if (headInScreen && endInScreen) { + pChip.bShowRoll = true; + return; + } + + // displacement per sec + double th16DBeat = -4 * pChip.dbBPM / 60; + int dxHead = NotesManager.GetNoteX(-1000, th16DBeat, pChip.dbBPM, pChip.dbSCROLL, pChip.eScrollMode); + int dyHead = NotesManager.GetNoteY(-1000, th16DBeat, pChip.dbBPM, pChip.dbSCROLL_Y, pChip.eScrollMode); + int dxEnd = NotesManager.GetNoteX(-1000, th16DBeat, pChip.dbBPM_end, pChip.dbSCROLL_end, pChip.eScrollMode_end); + int dyEnd = NotesManager.GetNoteY(-1000, th16DBeat, pChip.dbBPM_end, pChip.dbSCROLL_Y_end, pChip.eScrollMode_end); + + // get move speed near the judgement mark + + var head = new Vector2(xHead, yHead); + var end = new Vector2(xEnd, yEnd); + var origin = new Vector2(this.NoteOriginX[iPlayer], this.NoteOriginY[iPlayer]); + float pos = NearestLineSegRelPos(head, end, origin); + + Vector2 dr = Vector2.Lerp(new(dxHead, dyHead), new(dxEnd, dyEnd), pos); + Vector2 rollNorm = Vector2.Normalize(new(yEnd - yHead, -(xEnd - xHead))); + + int drCanMoveAwayMin = (OpenTaiko.Skin.Game_Notes_Size.Max() + 1) / 2; + // If the nearest point is roll tip, all moves may prevent obscuring. + // If the nearest point is roll body, only orthogonal moves may prevent obscuring. + float drAway = (pos > 0 && pos < 1) ? Math.Abs(Vector2.Dot(dr, rollNorm)) : dr.Length(); + bool canMoveAway = drAway >= drCanMoveAwayMin; + pChip.bShowRoll = canMoveAway; + } + + private static float NearestLineSegRelPos(Vector2 head, Vector2 end, Vector2 target) { + Vector2 body = end - head; + float len = body.Length(); + Vector2 bodyUnit = Vector2.Normalize(body); + + Vector2 dHead = target - head; + float dHeadProj = Vector2.Dot(dHead, bodyUnit); + return Math.Clamp(dHeadProj, 0f, len) / len; + } + protected override void t進行描画_チップ_ドラムス(CConfigIni configIni, ref CTja dTX, ref CChip pChip) { } protected override void t進行描画_チップ本体_ドラムス(CConfigIni configIni, ref CTja dTX, ref CChip pChip) { @@ -1900,9 +1968,10 @@ protected override void t進行描画_チップ_小節線(CConfigIni configIni, if (pChip.dbSCROLL_Y != 0.0) { double _scrollSpeed = pChip.dbSCROLL_Y * (this.actScrollSpeed.dbConfigScrollSpeed[nPlayer] + 1.0) / 10.0; long __dbt = (long)(SoundManager.PlayTimer.NowTimeMs * OpenTaiko.ConfigIni.SongPlaybackSpeed); - long time = pChip.n発声時刻ms - __dbt; + long msDTime = pChip.n発声時刻ms - __dbt; float play_bpm_time = this.GetNowPBMTime(dTX, 0); - y += NotesManager.GetNoteY(pChip, time * pChip.dbBPM, _scrollSpeed, OpenTaiko.Skin.Game_Notes_Interval, play_bpm_time, pChip.eScrollMode, false); + double th16DBeat = pChip.fBMSCROLLTime - play_bpm_time; + y += NotesManager.GetNoteY(msDTime, th16DBeat, pChip.dbBPM, _scrollSpeed, pChip.eScrollMode); //y += (int)(((pChip.n発声時刻ms - (CSound管理.rc演奏用タイマ.n現在時刻 * (((double)TJAPlayer3.ConfigIni.n演奏速度) / 20.0))) * pChip.dbBPM * pChip.dbSCROLL_Y * (this.act譜面スクロール速度.db現在の譜面スクロール速度[nPlayer] + 1.5)) / 628.7); } diff --git a/OpenTaiko/src/Stages/07.Game/Taiko/NotesManager.cs b/OpenTaiko/src/Stages/07.Game/Taiko/NotesManager.cs index 055b48b8..5a28d859 100644 --- a/OpenTaiko/src/Stages/07.Game/Taiko/NotesManager.cs +++ b/OpenTaiko/src/Stages/07.Game/Taiko/NotesManager.cs @@ -49,40 +49,33 @@ public static int GetNoteValueFromChar(string chr) { return -1; } - public static int GetNoteX(CChip pChip, double timems, double scroll, int interval, float play_bpm_time, EScrollMode eScrollMode, bool roll) { - double hbtime = ((roll ? pChip.fBMSCROLLTime_end : pChip.fBMSCROLLTime) - (play_bpm_time)); - double screen_ratio = OpenTaiko.Skin.Resolution[0] / 1280.0; - switch (eScrollMode) { - case EScrollMode.Normal: - return (int)((timems / 240000.0) * interval * scroll * screen_ratio); - case EScrollMode.BMScroll: { - return (int)((hbtime / 16.0) * interval * screen_ratio); - } - case EScrollMode.HBScroll: { - return (int)((hbtime / 16.0) * interval * scroll * screen_ratio); - } - default: - return 0; + public static int GetNoteX(double msDTime, double th16DBeat, double bpm, double scroll, EScrollMode eScrollMode) { + if (eScrollMode is EScrollMode.BMScroll) { + scroll = 1.0; } + int pxPer4Beats = OpenTaiko.Skin.Game_Notes_Interval; + double screenScale = OpenTaiko.Skin.Resolution[0] / 1280.0; + double n4Beats = getN4Beats(msDTime, th16DBeat, bpm, eScrollMode); + return (int)(n4Beats * pxPer4Beats * scroll * screenScale); } - public static int GetNoteY(CChip pChip, double timems, double scroll, int interval, float play_bpm_time, EScrollMode eScrollMode, bool roll) { - double hbtime = ((roll ? pChip.fBMSCROLLTime_end : pChip.fBMSCROLLTime) - (play_bpm_time)); - double screen_ratio = OpenTaiko.Skin.Resolution[1] / 720.0; - switch (eScrollMode) { - case EScrollMode.Normal: - return (int)((timems / 240000.0) * interval * scroll * screen_ratio); - case EScrollMode.BMScroll: { - return 0; - } - case EScrollMode.HBScroll: { - return (int)((hbtime / 16.0) * interval * scroll * screen_ratio); - } - default: - return 0; + public static int GetNoteY(double msDTime, double th16DBeat, double bpm, double scroll, EScrollMode eScrollMode) { + if (scroll == 0.0 || eScrollMode is EScrollMode.BMScroll) { + return 0; } + int pxPer4Beats = OpenTaiko.Skin.Game_Notes_Interval; + double screenScale = OpenTaiko.Skin.Resolution[1] / 720.0; + double n4Beats = getN4Beats(msDTime, th16DBeat, bpm, eScrollMode); + return (int)(n4Beats * pxPer4Beats * scroll * screenScale); } + public static double getN4Beats(double msDTime, double th16DBeat, double bpm, EScrollMode eScrollMode) + => eScrollMode switch { + EScrollMode.Normal => msDTime * bpm / 240000.0, + EScrollMode.BMScroll or EScrollMode.HBScroll => th16DBeat / 16.0, + _ => 0, + }; + #endregion #region [Gameplay] @@ -335,8 +328,12 @@ public static void DisplayRoll(int player, int x, int y, CChip chip, int frame, int _offset = 0; var _texarr = OpenTaiko.Tx.Notes[(int)_gt]; int rollOrigin = (OpenTaiko.Skin.Game_Notes_Size[0] * 5); - float _adjust = OpenTaiko.Skin.Game_Notes_Size[0] / 2.0f; - float image_size = OpenTaiko.Skin.Game_Notes_Size[0]; + float wImage = OpenTaiko.Skin.Game_Notes_Size[0]; + float hImage = OpenTaiko.Skin.Game_Notes_Size[1]; + + // Hit-type notes are drawn anchoring to the top-left and are off center, but roll-type notes are not + float xHitNoteOffset = wImage / 2.0f; + float yHitNoteOffset = hImage / 2.0f; if (IsSmallRoll(chip) || (_gt == EGameType.Taiko && IsYellowRoll(chip))) { _offset = 0; @@ -354,58 +351,46 @@ public static void DisplayRoll(int player, int x, int y, CChip chip, int frame, if (_texarr == null) return; - int index = x末端 - x; - - - //var theta = -Math.Atan2(chip.dbSCROLL_Y, chip.dbSCROLL); - var theta = -Math.Atan2(y末端 - y, x末端 - x); - // Temporary patch for odd math bug, to fix later, still bugs on katharsis (negative roll) - if (chip.dbSCROLL_Y == 0)//theta == 0 || theta == -Math.PI) - theta += 0.00000000001; - - - var dist = Math.Sqrt(Math.Pow(x末端 - x, 2) + Math.Pow(y末端 - y, 2)) + 1; - var div = dist / image_size; - //var odiv = (index - _adjust + _adjust + 1) / TJAPlayer3.Skin.Game_Notes_Size[0]; - - if (OpenTaiko.Skin.Game_RollColorMode != CSkin.RollColorMode.None) - _texarr.color4 = effectedColor; - else - _texarr.color4 = normalColor; - - // Body - _texarr.vcScaleRatio.X = (float)div; - _texarr.fZ軸中心回転 = (float)theta; - //var _x0 = x + _adjust; - //var _y0 = y + 0f; - - var _center_x = (x + x末端 + image_size) / 2; - var _center_y = _adjust + (y + y末端) / 2; - //TJAPlayer3.Tx.Notes[(int)_gt].t2D描画(_x0, _y0, new Rectangle(rollOrigin + TJAPlayer3.Skin.Game_Notes_Size[0] + _offset, 0, TJAPlayer3.Skin.Game_Notes_Size[0], TJAPlayer3.Skin.Game_Notes_Size[1])); - _texarr.t2D_DisplayImage_RollNote((int)_center_x, (int)_center_y, new Rectangle(rollOrigin + OpenTaiko.Skin.Game_Notes_Size[0] + _offset, 0, OpenTaiko.Skin.Game_Notes_Size[0], OpenTaiko.Skin.Game_Notes_Size[1])); - //t2D拡大率考慮中央基準描画 t2D中心基準描画 - - // Tail - _texarr.vcScaleRatio.X = 1.0f; - - // Only display the roll tail if the distance is high enough to see the tail texture to avoid math issues - if (dist > 3) { - //var _x0 = x末端 + _adjust; - //var _y0 = y末端 + 0f; - var _d = _adjust; - - var x1 = x + _adjust; - var y1 = y + _adjust; - var x2 = x末端 + _adjust; - var y2 = y末端 + _adjust; - var _xc = x2 + (x2 - x1) * _d / dist; - var _yc = y2 + (y2 - y1) * _d / dist; - //TJAPlayer3.Tx.Notes[(int)_gt].t2D描画((int)_x0, (int)_y0, 0, new Rectangle(rollOrigin + (TJAPlayer3.Skin.Game_Notes_Size[0] * 2) + _offset, frame, TJAPlayer3.Skin.Game_Notes_Size[0], TJAPlayer3.Skin.Game_Notes_Size[1])); - _texarr.t2D中心基準描画((int)_xc, (int)_yc, 0, new Rectangle(rollOrigin + (OpenTaiko.Skin.Game_Notes_Size[0] * 2) + _offset, frame, OpenTaiko.Skin.Game_Notes_Size[0], OpenTaiko.Skin.Game_Notes_Size[1])); + if (chip.bShowRoll) { + var theta = -Math.Atan2(y末端 - y, x末端 - x); + + var dist = Math.Sqrt(Math.Pow(x末端 - x, 2) + Math.Pow(y末端 - y, 2)); + var div = (dist + 2) / wImage; // + 2 (1 for head, 1 for back) to avoid the gap before tail + + if (OpenTaiko.Skin.Game_RollColorMode != CSkin.RollColorMode.None) + _texarr.color4 = effectedColor; + else + _texarr.color4 = normalColor; + + // Body + _texarr.vcScaleRatio.X = (float)div; + _texarr.fZ軸中心回転 = (float)theta; + + var _center_x = (x + x末端) / 2 + xHitNoteOffset; + var _center_y = (y + y末端) / 2 + yHitNoteOffset; + _texarr.t2D_DisplayImage_RollNote((int)_center_x, (int)_center_y, new Rectangle( + rollOrigin + OpenTaiko.Skin.Game_Notes_Size[0] + _offset, + 0, + OpenTaiko.Skin.Game_Notes_Size[0], + OpenTaiko.Skin.Game_Notes_Size[1])); + + // Tail + _texarr.vcScaleRatio.X = 1.0f; + var _xc = x末端 + xHitNoteOffset; + var _yc = y末端 + yHitNoteOffset; + // notice that the texture for bar tail is centered at the mid-left of the image rect + // rotate around image rect center, find bar tail center relative to top-left of image rect + var xTailOrig = (Math.Cos(theta) * -wImage / 2) + wImage / 2; + var yTailOrig = (-Math.Sin(theta) * -wImage / 2) + hImage / 2; + _texarr.t2D描画((int)(_xc - xTailOrig), (int)(_yc - yTailOrig), 0, new Rectangle( + rollOrigin + (OpenTaiko.Skin.Game_Notes_Size[0] * 2) + _offset, + frame, + OpenTaiko.Skin.Game_Notes_Size[0], + OpenTaiko.Skin.Game_Notes_Size[1])); + + _texarr.fZ軸中心回転 = 0; } - _texarr.fZ軸中心回転 = 0; - if (OpenTaiko.Skin.Game_RollColorMode == CSkin.RollColorMode.All) _texarr.color4 = effectedColor; else