From f49481e308b68176e560a283870d6dbcbd43c581 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 26 Mar 2021 12:48:21 +0300 Subject: [PATCH 1/5] Add old skin spinner SPM background for testing --- .../Resources/old-skin/spinner-rpm.png | Bin 0 -> 10583 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/spinner-rpm.png diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/spinner-rpm.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/spinner-rpm.png new file mode 100644 index 0000000000000000000000000000000000000000..73753554f74fab988c7e5d8c7722d22928120682 GIT binary patch literal 10583 zcmW++1yob-8@_`9BNT?TfG|-yq+4PmrF-N+Kp4{9AmB(TL6i;!VT3S3Qba;Z5Qa!8 z5-Kq|9P#J(zvtZh-a6;I=RNm(-sgRuFVRq6^BNT!6#xL&w6);I003gXI+ulzUma)c zI-Xnwke{)p8c;jN{^#m~+)-B(eig5_Q}y$7{E4snt^tTxuxj+T znAzO+qdn2}l@j6@jS3Ag`ylD|Qr%mhb&&|dQtCyw^18zCGjG2i0XNO&zRx7EIAZK= z2VbUE>($QT?>sxSyGfV$wiyDD0AHs4Y}6&AvSMiO_0iq(azp_;zz?8@B)aP22n}(y z9bO5gf!qN{vjFr!rA3tH^oINB`k&j1FSrv}mVc=V)hAdA0SrJoNCm01#yD{z#WYHF zaIMSKVg(9PAq6x*#gxFSx9hmS=R5L(>t#I8xQA%&^$k3=A@9p1QK1Epz!&o9>%f6v zW&`8UVCo>*OzVAIlBqOl4;b(ujphY@pnz}2ozhd*SL1onPK{234}n43ltD01HL*7Epf^xPSo4w17M& z)0}07+23^IE!Rk5HUdV@15#z(p`t3~8cywhzb1ORGkj95t^Dr#>~gy6({I2^?}HKL zoo3AA3~4?5t9nE;rH<;c9B@Jayc=No6;ezzXvGwx z;c_(ZVs#)^twrn?Jb_1|Z{VoQ5-Bpc$WM6iXurw4ykNGby-9dOX!}NnsA|=~1u@Bx z+=zz)D6QtlmZU&ZfgT1K_@v!r%g}A8QN{g29q1mzf!uVpIf8;gz^rI}HXX3gs}~3;U7p z$^_yE#l^*0UL@48A;!YE1(uwc9ISA)5DRSw=Ow9~LNdyGG?H%E^mL#>(PiB3Tcaf* z9{3dpE!o^i_xQz9E4f$9;F<~@$n~~vsb@n6d%nE1)fGG0n zyFP z8ix0LNu)HT8v5njUy|sc*~UFIBb%S@H6Ghh{hGBLhH$~bC^`qaM^t6e3{U}>DUv&k zX(K;)d^z70T@Z|d=1h94L}z-i#8LcGdr(?mexHfXL9D+aAPq{VMJj-(WZ;Iyc)K}- zjH_YN;oSW`l_XegC^x@aJh+e*3|DMqacbie5>y+a`juDbca6+zS?=19Gv--?)3X)r zcy{>btjV10W-hKx+w|sjTC@H^U7(%@SokdUj?|a3c*Kb{UR*$u2|lD|d@C;*ooXK8 zv7va~hUvpJQ4I8>$6Yo0L)@fJNg9kfUUi~!rU|A7>s3Kh#!gZ}p^h#?dunk~?prFW zFd#Yx&q5vpL$GLq+$ug(>cuTK#(}g^LKIZ|iT-YRvZC<~%HyfDO)#t)d#gYk6`X~E z|3Rh}h~(hwytCUj6dF*RPB#%UzS!93Qw(9OKQ1OsMOo74n^S!hyf{@@@Pb|N1*&!v|5}tq`rn?oEsaj{sg)-G`yZ3%2smXKy!o|A$2C%~b z*wp&ZT?bas!nw%R`wJ96aoNAjG_);aUz?bP%%YTB(36hN4>eYP#}!;$2uuvV>G4Le z?@G)^>lP2TYBHoCqJp{?);qG?u`X!RxaH*4-BRB=CDLSWs!Jc>O{mQvZY#*L{!u9b zitn06_ZTB@7h1_L*zG0<8H@|#C3*@4z$`bj~NRAdIfS$CW#FbrxC`Vq%V4}$_A)8k5h5au7kh{G7 zzYLUu$5+-yZ%gqY-Yb&t8o#$#&U>@Q6sEZp?iv`{@4_UUPIA!9!z?N!(`b`GLZG{; zwdOqa{cmLsnRX{f-{Geu8_vp;+qEf-Y~_2jnQQKVHw8?d1FU4MnubZ6y-lL!R!#$F z?Aw^qR&h{;)9un}mbg#-`tjfAtXp*NooZ^Z9ll4pc3|~etgG+TT^y{=)QL5Euswzw zCz z_ke-sHJPq|2e;>%J!khWPIYr5&(5j|91$CxZN13x!cVe-LASokTnsFoV8=;;vmb4_ z_LujUE{BM1_a0z&20tD@zQhwEPj`6|OAq_MZXXg4Wy4q7$2)=-Ed^~#*)~Rnb2F`j z2SwC97{vG=XQzpCz-{6FFkj6(SU7qp-a$%GhCEuHzX0m?1m2Zx5!{31Q!%DY_bsEO z#S~LQ5#BYkkZpygD57PVoh#w|%(s11hz7z3d!QoO6|jHusBuEEDeAayw`H+l<4s%j zod!G-`|w;u1czK~)C50-8Wlg?zUnSZa*b|V|?leB^e|c#9=^j1k zRgr^|1F}s)$J-4Wor{&cUfq#r1*}X^QJUL_NLg7fEW`9Np9&x&ji({VwYpko!ZR>~ zBg=dAi4Yh}g4zw+?8!Md^=N8?c)ZCz!zL;~hJgXShHS`-oW6SxbGW5*$eujVu`+(o z#rSYBuOI0M0@%}sxe`SHAYSV_z=E)Rf%x_W;DTwfsvtU)mx?Stj;oMBdQ>2$b)TRl zJr#ly5~7`!Yw?OvaTp+hwE%k5HinDy?!_bH|+G{$)c z1{6Qp>!<1Ev^wkY0F{fYecG50V}w#s^1-Oe47FD^Q`4l`j@jqiM-yGi;3>`zNTK=4S(Z(JwIo`E)jk zyrDpnIs#M70Oii4h^Ef1)&HYXt)$TWnD{rhFm&^3qMHmD!&RdO6`>#gmdX@xD6tP7EfS z_1|xD-wie==TE*yXwG2EFbi{FD2C;{%EP7UFKP8 zny0e3d}x_1GqONG$JwjH=d4c%CaE|NJb`A5~+*5EBwvOz&yD?*=*r0I-0r&}clmo-hQ!d?JC810zcpMbFK)L&lq*gHP05x$OD#&8{e zHIb~J7)&$a#W!)~@9eYPVe2PzgnXnHG0(4`l4vIL^Y3PFEDn$QQ-SShXqx)ldGNhq ze_vNOL^u(LO9KHcbe)ZkSZ4spTD5y?HO40Vd3|WqBetd7?8m9j!Tr_*V64ry&upLhX)$m{sFY1Rq2O zPY1SQ0e@2%+|O4En|&8zlIfDyCaKg|4!t*7VvOV;A3^ftLWX~9v4BDD2q79gi~iRs zRx~@HlCyex9ChgO1U%XV;*9~i@nwEIy4GyOYaB8Zw`g%SEUzrvH)ghH&W%*7q z=*ff34nB|SX%YE_E?XPVf+8!+&8VNd2DcWKBg`zR$!w!r{NP z_zJOvGBFxKY7*wP+@#1txoK|VvhRj3y5xD}?w+n$z&KCNa(E?UZ|R#9g!b6mptsZZ0>(|g*A)_BY1Q)p-@q& zJ%YG$GBtIuJhEwn-}V=xUmiKI2|Qya-6ml z^8Q?25W?lEKludzJ!m^U{Zs)Pr(Sv-6c%+lZ(yK_<9jhLu8efI#F%XvTX{zV7H?c~ z?Ly8^ruADk_7q+1oZ5n;Ze~(lzao~`G(<>;ooy|;J?iSO0Y@Y~oxo0=DV=a%>V$-m zxOjD}SgQNzC)Jkmi`>FD~QGd+L@IZF19mhDcfg` z92y{gY&xjNiWgzWsowszVIlIbd_k>lRewg=xi^c1MuCy#p1$5yg{opI0) z4nUR`@SABzRGTM5Fu`=YmO9dAeUBWPYJRcNa3*&+!20yxtfw?g5{_+I5^7TjFq(%} z>-i56KleL!3YEC%!Kx~oN@9Wl6N*4K+#zL}lrB_k{FcUy;w4E1PbWIug(Md|MiknRiGJGf(lv&Q4Hi>spZb?q;E)5Ak5 zVN`?J<5qEZXh`19&~pqx5k3!IPU$#T?CU4>)(6<-`)wB7P>WM>ql%4=RY4V#QuRic z^6}_VVFTDTGpL$izn>U9UhBk7293G8t$~18u3}@}UB`kOD1Jd)UPWb1scJUFtMTUV zdT$?0>N?Yq-U6(1(YC3{rhG)4%+8H9S|+IR^Lh1Mnvm@_z9hzbs{HXWc(@LVbqH!y zE=EOatb@XG3i80|$SV@TteDRa{?U;NDfEcNA)l;kWg}COC2BNfD*P%Bk5=glf(bgt z^eD=v*LxHYZEn8OcbdAz5P0-#3^RoexBRdg_(fldPU8r3$j?;VW+!t61360N3Lqg* zj)jsSZGZXw7KU0oFDp^>3v@S)zpMs$CI3q>r#4ODS#<}^thLSe+nRfNY&RY|rtYHe ze}<>WapT<^zT^BfxBR+~z8z&`xWr*9pTisgF3YNfv3Dbd%6_#07n7@=-9_)WvaGWV zI77VAjkB0+V~iOyFAo?kq(Er|M*1bM@tttExMXWi$T24u*MS6lo4CS;Z_?V~PodFyNn zwr_>}zQ8VB;AILSA61q-mX-H64W8L|`5uBXC+*wcWsb03ky{hOzg}s2NzWJ*Dn_7C zs9i?1(7ILs6J*INzdm~}qqve{g}wyReZ6bfBGkWE4PsOQK~xZKn&gsRz8AZcm<30a zyyl@aD>J92=$kb7syLop`VXZF_mKXJhV^|Z4m-l&rN{qWP2&h8RQ@hO{xZ{G9bTE$t`?T8ti^FzKc&AqqQruzHO{Mmvx z;^^+B-Ddn$B0!n3x_;HsMI3FmAymMw1v5UC&lH0%Ii9hH>~KLduS+3Nl?>1jf)zhP zqR~%xOP?3FsM|6k{QD!ZM^!cZd74|T_(2bxExjVS3+EIA}1&o#B466EW{wg@R4 zVf*LD21QnBrYbcBd@vp;q{+@GHpruBzFa%&I4y>*px&eC1xr=9^g{yKiw`Wf?~YsH zA5MPOY!#XRBFyIaF@t&u>1Xxy`R9VT)sOsLQ6EFRQ>mk4;TV#R$%R{VyFV@>f@)eW zeXAF?4nxN*KN?DVUWo>QZ@vmrIRQYUkm%ZLR|Rl1Qm~gsQGwi$1dn8GYUywqI7(Cq z^eAebAo$A{Tr4jed)7REcggs~ChPU(M8@!cLzBK6)|1kT_&RJ0uTNq!<1`BC?(d#0 zM=Efks@~c7Nt;<~1JUNy3mdLhA|t&vp+QNQf9FD%r{nhRVgUhfi(K1u8>gVqdlHwM z&u0vZ&VTe8+pR?Ugvb@WvL9S$X4?Do$(fWy?7f?$STo(akUB!E)9(JpG11DVZlEkR z6dNjBP*CJ`zKjj3eK0=a@>*=OBP)i#RZaK=nwd; zT~$u_)|(FPw03|?rwz`M2g?kk|F*W zC{27cKDJ=3adWhT>=@kzDJ38i#f6+}7gFI*#t0&l{R=a;=54j);pZa)R)tneu}x8F$IEMa$FV z%X$482@MHPwUWOVJ#XgR0mI7oxO*sa6NY@3Dw$@F+-HEp-h4}r^<7b_rL9Ao?;8rg z6q!T1j4jGZQxWFDwiUEXegxb0nVa8f3r{C@FVE~R=Gr?w*J_UGHO!tjVVaPSB@`51 ziHHc5cVD#e(#{7hErdl82p2~u+nw!W;g@;03apUG{Cz=3kxLwwhQa3ejAo&fTbjKM zs9Wj9YKr)hCh>^|GVxhE?o;{IbI~pIW#`VZO-qrEB-^Bav2XQm2-^Ji>J}!tV}EQY zKABnca9?rV^Lvoh{r$x-l*<<#6kR;aBRyBqe6Q}ngWrF4M_+B&NEIU`L{veW2k-w> znY>|^-%1BH^{(e0Z?^fV z30Ay3)c9RwmFv?wWl$KUl!_9-;0bI}aNtE2+UOUUxP-P`V;aiBqxH%OnIa zE{UdZ{aYc`)ro_ef1q)yS||qNvI#>Y7GoB09NeJ>Md*C!)^C*)awQeo2ToWZQJFkb zk0cMY8cSY{Ce+Ss$iKZ)u2s@vo`Um8Sydbjck#7fA*21w#sK3on%!iAmLT(t$X-^` zMC2+FXLXGj=C}L{0`#ezzOj8oOXbepiML;CRHtcx{=l;05buYXX3sYl-kVL_ddCaL zdv?^0mu0JQJn9oD>LmL~U^NTV(;55CYYwKtOlJtobM29~ z@Gy7$924R?K@B4WQ;~u?rCQwd+2B%s8a08MpLTWY?Oc6T@Q4xq{{!(8}q*^(ReIG0?vY9!mYmqitF?@x_!$A?XG{vK-7~EKf81_A&Mz z!8Lm=kC}<GDk(|4 z3PV*6P%7=7xbn()EwUHM?W=C|wG*1NLQ9h+kNepI$A)Tc6|%;a4p*9GlMWOJg*Ngb zf6vp7g?#A$hHbY89mEg`H;amj#tS2k2EJ17Q)`gL^^)L_HOjkRo;W>IKKlKzeOe^) zqS3;oz|Z$&r)1*eQ`*!|sLEcxNgYA9gZ+T9FcY(YaS@@1UR|fjWZ-CuTcHl&11k@0 z1@+1qtK1`WEm#P7vvRzy$|nw|6!*_2{Eb#>a4_^Rfz|gj-A&7hc-3FTIiKX6&;_z| z-E)w|ISd3~ht2VG&3bpEb`F=?k4v6%&aVRwz8m*hYSjfwvULO14oaC zS}re^!h**a6?n%B74Dy}N9`^I*#+-b%y!t-3kr$ia<)2;7Zi@(MD2}7D$$UfkJu{_ z@lhwwF8{zq^=unwX}XT4m*h{Liz=W0*qlGx8_FV_*Uxz>aDX3SpCdb8N1js`EgcRl z{rfoHq)>2iFg0b5`~H}C=ykTJR1|TL(e4?5q;{I`K5e<&wnB46ouj*nJzbq)%?q3R z>tpsWBG)v>BfW5(>Uc{*+5ZSxtLKF9;HtXb`{6S=a)7M#L|yM$FISH&6@Qv~uRG54 z8eo4h=`}y|=P-%cF4#vPE%$M;q~~0h@CCMe|2gUZ(<4@?fqkv{YE(sqCnc6K19siX zwr*My>R4t+d&L<7F1tGCE+<+pRm<$^I4(~G>&6&D#tt(Q7Mt9J*yb;A=8}_-$T7en zQqN?X>V_p39u#kM2T$e4t|3sl$PBHTcK1WcD9{8?<1Lo_@ zuYcIHhxsW}!49X^i{?-B29zTl0-}0&wEeM0GccGByI^gO!jw zXYh5y+O6>YjiUMY0m~~JUhD<7HZ~(NsVVR$7hduZK5fUf)CNDgZ9D6n*%T z#Dy#VMPCNh6XOuZE5_KXXmF~qbiVkB5P2NN{HpAB==u={PV`D>IX6(eKnJya-?}I| zAHO_bYolfkEASjBKkrh9pY>(j#(k*T@|Qoasy5N>58GYbUro-tk_f>uJ0TW{lIZ0G9$Pw^ish zsjxq_1m9*ZubiDyOSDN{6skWBjNA(9`d3w;K#<-4TBIDMtnH{b9ZK?2i%eC;huw%J z&Yh0_F5E+s?ZfMd32$!!4GoQ0z9a?!pf5>t8YM}0&2xg%!GAwH>hxO`NQ;KJ6%y@Q z(CyeOaxwBryR@Q79{?OS@Yq+)o(u2YybE6+6YN8GKNVf+5OUxX0Duhtw-$i#>18Ye zeAeS=rG8hI!T;$u%!;w_@6g6a(B5k^BJ_N@A#XCR@!_QOvF2>?Wa-0d8ejmO8b<7E zx8OXV;{6yAcIL6x>8b>>?6N## zQ<1{oI^2ZV=@6afUy&B~oS0DPS-If?^EnW}hj{U(?WU;#DfJ7*<8!;2RjajqA!n7H zMI>kbt5QCX_JwUTJV)fthgmCt9IVQthE0r~&84uWr>SlARBw5Nxwg){FzNf@uqIcW zMi`jE*8iT331aH-Gk($QCk3)n8H@4`wCYeGu9z5bMy_NSaDai6Pu5-qQN+H)JcIlH zeit86q)ItrQ5ql<>@JPr(QLqCQiH|=l%;@hY@V}EB=_Y9`^d}9`4JC7)tkcBjqQ(5 z10OF*x5+_BQh{hYa^2_Nbl?sbMs**2LyMh1JN->wX&Db{YlMeolH_s0YR+REa?EPL z>g6d9#sLR3ZxMJY%&(8&^$CZe`8n5P7*<@U%w8uKQCD73K}oCa#K=< zgMWRETv|P!64ZO)oJR&!PHzmE7$`SXebTWi&Pev|1baPMc zJuxA4cb_pYoo+M0qG50fv^iBes_79XwW@7iKS8ASmywyw6k3>BEq;t?o)8d`a}<%^ z2Le5ZGbdyJ1lN0XCh6S<$#dg&Q`?EbS#nwe__m;UoS8Zv=5m9;EuNhCp9~)V>PVxQ$ z*8JI|-QRs7*Y$i1ZIkc4e>!@3qPki` z4|R_AbVa*!coL~%)88-D{`b`k1I)r@!1*Kv-c@Y@@64Z_bEczbSB`O7ebJ z7NlFqtjqb7sIYL}j9u9IS)l^KU9SLn5r`zYe9wP1G&0b0``fL^{a5FS ziNdKZOUn0JIyxSP71TP>1)o;rx)PNAfA@8r6Px*9Nb7^!&+leAyY2oeIy;!}JhS+YD`~?&3fPDP@ zyONXRCUqu{xe-eSe%1Y1F{Hz(wvyKK>KpE7G$~QddbrJ(EXLT)gzEn?brc zxbW@ehq<{eKpNwW;J<$T`fzd*Z0RNK&wwbWQx(+1TZ(vmK-hT~u67kwbS=2g1-{w( zDtTBPUx5DOiQ$&cww4s=ZHJ#k(zUsn9pD^2+g@v#iJkqVzOPHS7_kZVAXZMN;C4&oa;(YpG;EPU)F&h?V^Fr+f zqDHC|*Dth1)UU?&^1*>+E8NiuG%Cngluf+(oe@n2PMW ze_|OPv(j!HIsT_asL4__ldNU({VXq&-G7V-s=0Ey8?g~znIdm_njhi(CeA8GZ$$=`sB|i(Zu=e zh_}JPsn;7y{;ayhZl_jhlFPm4YWd7*`#93y`h$kR6FMf2~&{N{1UXai9oj}RE;^y0ZfUtJs;+!C1V>AySNFF*G4A%`R88TG+q zSpyp22B3m&zL`xOK8RymA@{?bZd}+A3u{6;B86_bjW7PU Date: Fri, 26 Mar 2021 13:09:44 +0300 Subject: [PATCH 2/5] Refactor spinner SPM counter for skinning purposes --- .../Mods/TestSceneOsuModAutoplay.cs | 10 +-- .../Mods/TestSceneOsuModSpunOut.cs | 4 +- .../TestSceneSpinnerRotation.cs | 10 +-- .../Objects/Drawables/DrawableSpinner.cs | 48 +++++------- .../Skinning/Default/DefaultSpinner.cs | 64 ++++++++++++++++ ...rSpmCounter.cs => SpinnerSpmCalculator.cs} | 74 +++++-------------- 6 files changed, 114 insertions(+), 96 deletions(-) rename osu.Game.Rulesets.Osu/Skinning/Default/{SpinnerSpmCounter.cs => SpinnerSpmCalculator.cs} (61%) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs index 856b6554b9..0ba775e5c7 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods private void runSpmTest(Mod mod) { - SpinnerSpmCounter spmCounter = null; + SpinnerSpmCalculator spmCalculator = null; CreateModTest(new ModTestData { @@ -53,13 +53,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods PassCondition = () => Player.ScoreProcessor.JudgedHits >= 1 }); - AddUntilStep("fetch SPM counter", () => + AddUntilStep("fetch SPM calculator", () => { - spmCounter = this.ChildrenOfType().SingleOrDefault(); - return spmCounter != null; + spmCalculator = this.ChildrenOfType().SingleOrDefault(); + return spmCalculator != null; }); - AddUntilStep("SPM is correct", () => Precision.AlmostEquals(spmCounter.SpinsPerMinute, 477, 5)); + AddUntilStep("SPM is correct", () => Precision.AlmostEquals(spmCalculator.Result.Value, 477, 5)); } } } diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index 7df5ca0f7c..24e69703a6 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -47,8 +47,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Beatmap = singleSpinnerBeatmap, PassCondition = () => { - var counter = Player.ChildrenOfType().SingleOrDefault(); - return counter != null && Precision.AlmostEquals(counter.SpinsPerMinute, 286, 1); + var counter = Player.ChildrenOfType().SingleOrDefault(); + return counter != null && Precision.AlmostEquals(counter.Result.Value, 286, 1); } }); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index ac8d5c81bc..14c709cae1 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -168,13 +168,13 @@ namespace osu.Game.Rulesets.Osu.Tests double estimatedSpm = 0; addSeekStep(1000); - AddStep("retrieve spm", () => estimatedSpm = drawableSpinner.SpmCounter.SpinsPerMinute); + AddStep("retrieve spm", () => estimatedSpm = drawableSpinner.SpinsPerMinute.Value); addSeekStep(2000); - AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpmCounter.SpinsPerMinute, estimatedSpm, 1.0)); + AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpinsPerMinute.Value, estimatedSpm, 1.0)); addSeekStep(1000); - AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpmCounter.SpinsPerMinute, estimatedSpm, 1.0)); + AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpinsPerMinute.Value, estimatedSpm, 1.0)); } [TestCase(0.5)] @@ -188,7 +188,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("retrieve spinner state", () => { expectedProgress = drawableSpinner.Progress; - expectedSpm = drawableSpinner.SpmCounter.SpinsPerMinute; + expectedSpm = drawableSpinner.SpinsPerMinute.Value; }); addSeekStep(0); @@ -197,7 +197,7 @@ namespace osu.Game.Rulesets.Osu.Tests addSeekStep(1000); AddAssert("progress almost same", () => Precision.AlmostEquals(expectedProgress, drawableSpinner.Progress, 0.05)); - AddAssert("spm almost same", () => Precision.AlmostEquals(expectedSpm, drawableSpinner.SpmCounter.SpinsPerMinute, 2.0)); + AddAssert("spm almost same", () => Precision.AlmostEquals(expectedSpm, drawableSpinner.SpinsPerMinute.Value, 2.0)); } private Replay applyRateAdjustment(Replay scoreReplay, double rate) => new Replay diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 39e78a14aa..1a89fc308e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -30,7 +30,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public new OsuSpinnerJudgementResult Result => (OsuSpinnerJudgementResult)base.Result; public SpinnerRotationTracker RotationTracker { get; private set; } - public SpinnerSpmCounter SpmCounter { get; private set; } + + private SpinnerSpmCalculator spmCalculator; private Container ticks; private PausableSkinnableSound spinningSample; @@ -43,7 +44,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// public IBindable GainedBonus => gainedBonus; - private readonly Bindable gainedBonus = new Bindable(); + private readonly Bindable gainedBonus = new BindableDouble(); + + /// + /// The number of spins per minute this spinner is spinning at, for display purposes. + /// + public readonly IBindable SpinsPerMinute = new BindableDouble(); private const double fade_out_duration = 160; @@ -63,7 +69,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Origin = Anchor.Centre; RelativeSizeAxes = Axes.Both; - InternalChildren = new Drawable[] + AddInternal(spmCalculator = new SpinnerSpmCalculator + { + Result = { BindTarget = SpinsPerMinute }, + }); + + AddRangeInternal(new Drawable[] { ticks = new Container(), new AspectContainer @@ -77,20 +88,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables RotationTracker = new SpinnerRotationTracker(this) } }, - SpmCounter = new SpinnerSpmCounter - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Y = 120, - Alpha = 0 - }, spinningSample = new PausableSkinnableSound { Volume = { Value = 0 }, Looping = true, Frequency = { Value = spinning_sample_initial_frequency } } - }; + }); PositionBindable.BindValueChanged(pos => Position = pos.NewValue); } @@ -161,17 +165,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - protected override void UpdateStartTimeStateTransforms() - { - base.UpdateStartTimeStateTransforms(); - - if (Result?.TimeStarted is double startTime) - { - using (BeginAbsoluteSequence(startTime)) - fadeInCounter(); - } - } - protected override void UpdateHitStateTransforms(ArmedState state) { base.UpdateHitStateTransforms(state); @@ -282,22 +275,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateAfterChildren(); - if (!SpmCounter.IsPresent && RotationTracker.Tracking) - { - Result.TimeStarted ??= Time.Current; - fadeInCounter(); - } + if (Result.TimeStarted == null && RotationTracker.Tracking) + Result.TimeStarted = Time.Current; // don't update after end time to avoid the rate display dropping during fade out. // this shouldn't be limited to StartTime as it causes weirdness with the underlying calculation, which is expecting updates during that period. if (Time.Current <= HitObject.EndTime) - SpmCounter.SetRotation(Result.RateAdjustedRotation); + spmCalculator.SetRotation(Result.RateAdjustedRotation); updateBonusScore(); } - private void fadeInCounter() => SpmCounter.FadeIn(HitObject.TimeFadeIn); - private static readonly int score_per_tick = new SpinnerBonusTick.OsuSpinnerBonusTickJudgement().MaxNumericResult; private int wholeSpins; diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs index 891821fe2f..ae8c03dad1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Globalization; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -19,6 +20,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default private OsuSpriteText bonusCounter; + private Container spmContainer; + private OsuSpriteText spmCounter; + public DefaultSpinner() { RelativeSizeAxes = Axes.Both; @@ -46,11 +50,37 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default Origin = Anchor.Centre, Font = OsuFont.Numeric.With(size: 24), Y = -120, + }, + spmContainer = new Container + { + Alpha = 0f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = 120, + Children = new[] + { + spmCounter = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = @"0", + Font = OsuFont.Numeric.With(size: 24) + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = @"SPINS PER MINUTE", + Font = OsuFont.Numeric.With(size: 12), + Y = 30 + } + } } }); } private IBindable gainedBonus; + private IBindable spinsPerMinute; protected override void LoadComplete() { @@ -63,6 +93,40 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default bonusCounter.FadeOutFromOne(1500); bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint); }); + + spinsPerMinute = drawableSpinner.SpinsPerMinute.GetBoundCopy(); + spinsPerMinute.BindValueChanged(spm => + { + spmCounter.Text = Math.Truncate(spm.NewValue).ToString(@"#0"); + }, true); + + drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; + updateStateTransforms(drawableSpinner, drawableSpinner.State.Value); + } + + protected override void Update() + { + base.Update(); + + if (!spmContainer.IsPresent && drawableSpinner.Result?.TimeStarted != null) + fadeCounterOnTimeStart(); + } + + private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) + { + if (!(drawableHitObject is DrawableSpinner)) + return; + + fadeCounterOnTimeStart(); + } + + private void fadeCounterOnTimeStart() + { + if (drawableSpinner.Result?.TimeStarted is double startTime) + { + using (BeginAbsoluteSequence(startTime)) + spmContainer.FadeIn(drawableSpinner.HitObject.TimeFadeIn); + } } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCounter.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCalculator.cs similarity index 61% rename from osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCounter.cs rename to osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCalculator.cs index 69355f624b..a5205bbb8c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCounter.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCalculator.cs @@ -1,77 +1,37 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Utils; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Skinning.Default { - public class SpinnerSpmCounter : Container + public class SpinnerSpmCalculator : Component { + private readonly Queue records = new Queue(); + private const double spm_count_duration = 595; // not using hundreds to avoid frame rounding issues + + /// + /// The resultant spins per minute value, which is updated via . + /// + public IBindable Result => result; + + private readonly Bindable result = new BindableDouble(); + [Resolved] private DrawableHitObject drawableSpinner { get; set; } - private readonly OsuSpriteText spmText; - - public SpinnerSpmCounter() - { - Children = new Drawable[] - { - spmText = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = @"0", - Font = OsuFont.Numeric.With(size: 24) - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = @"SPINS PER MINUTE", - Font = OsuFont.Numeric.With(size: 12), - Y = 30 - } - }; - } - protected override void LoadComplete() { base.LoadComplete(); drawableSpinner.HitObjectApplied += resetState; } - private double spm; - - public double SpinsPerMinute - { - get => spm; - private set - { - if (value == spm) return; - - spm = value; - spmText.Text = Math.Truncate(value).ToString(@"#0"); - } - } - - private struct RotationRecord - { - public float Rotation; - public double Time; - } - - private readonly Queue records = new Queue(); - private const double spm_count_duration = 595; // not using hundreds to avoid frame rounding issues - public void SetRotation(float currentRotation) { // Never calculate SPM by same time of record to avoid 0 / 0 = NaN or X / 0 = Infinity result. @@ -88,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default while (records.Count > 0 && Time.Current - records.Peek().Time > spm_count_duration) record = records.Dequeue(); - SpinsPerMinute = (currentRotation - record.Rotation) / (Time.Current - record.Time) * 1000 * 60 / 360; + result.Value = (currentRotation - record.Rotation) / (Time.Current - record.Time) * 1000 * 60 / 360; } records.Enqueue(new RotationRecord { Rotation = currentRotation, Time = Time.Current }); @@ -96,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default private void resetState(DrawableHitObject hitObject) { - SpinsPerMinute = 0; + result.Value = 0; records.Clear(); } @@ -107,5 +67,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default if (drawableSpinner != null) drawableSpinner.HitObjectApplied -= resetState; } + + private struct RotationRecord + { + public float Rotation; + public double Time; + } } } From f848ef534747babe4c020e3b1a759b2fc42d259d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 26 Mar 2021 13:10:04 +0300 Subject: [PATCH 3/5] Add legacy spinner SPM counter support --- .../Skinning/Legacy/LegacySpinner.cs | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index 064b7a4680..7eb6898abc 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected const float SPRITE_SCALE = 0.625f; + private const float spm_hide_offset = 50f; + protected DrawableSpinner DrawableSpinner { get; private set; } private Sprite spin; @@ -35,6 +37,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private LegacySpriteText bonusCounter; + private Sprite spmBackground; + private LegacySpriteText spmCounter; + [BackgroundDependencyLoader] private void load(DrawableHitObject drawableHitObject, ISkinSource source) { @@ -79,11 +84,27 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Scale = new Vector2(SPRITE_SCALE), Y = SPINNER_TOP_OFFSET + 299, }.With(s => s.Font = s.Font.With(fixedWidth: false)), + spmBackground = new Sprite + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopLeft, + Texture = source.GetTexture("spinner-rpm"), + Scale = new Vector2(SPRITE_SCALE), + Position = new Vector2(-87, 445 + spm_hide_offset), + }, + spmCounter = new LegacySpriteText(source, LegacyFont.Score) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopRight, + Scale = new Vector2(SPRITE_SCALE * 0.9f), + Position = new Vector2(80, 448 + spm_hide_offset), + }.With(s => s.Font = s.Font.With(fixedWidth: false)), } }); } private IBindable gainedBonus; + private IBindable spinsPerMinute; private readonly Bindable completed = new Bindable(); @@ -99,6 +120,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy bonusCounter.ScaleTo(SPRITE_SCALE * 2f).Then().ScaleTo(SPRITE_SCALE * 1.28f, 800, Easing.Out); }); + spinsPerMinute = DrawableSpinner.SpinsPerMinute.GetBoundCopy(); + spinsPerMinute.BindValueChanged(spm => + { + spmCounter.Text = Math.Truncate(spm.NewValue).ToString(@"#0"); + }, true); + completed.BindValueChanged(onCompletedChanged, true); DrawableSpinner.ApplyCustomUpdateState += UpdateStateTransforms; @@ -142,10 +169,16 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy switch (drawableHitObject) { case DrawableSpinner d: - double fadeOutLength = Math.Min(400, d.HitObject.Duration); + using (BeginAbsoluteSequence(d.HitObject.StartTime - d.HitObject.TimeFadeIn)) + { + spmBackground.MoveToOffset(new Vector2(0, -spm_hide_offset), d.HitObject.TimeFadeIn, Easing.Out); + spmCounter.MoveToOffset(new Vector2(0, -spm_hide_offset), d.HitObject.TimeFadeIn, Easing.Out); + } - using (BeginAbsoluteSequence(drawableHitObject.HitStateUpdateTime - fadeOutLength, true)) - spin.FadeOutFromOne(fadeOutLength); + double spinFadeOutLength = Math.Min(400, d.HitObject.Duration); + + using (BeginAbsoluteSequence(drawableHitObject.HitStateUpdateTime - spinFadeOutLength, true)) + spin.FadeOutFromOne(spinFadeOutLength); break; case DrawableSpinnerTick d: From 9504fe3f3c1fb78b8b4f6510ec988933f26bd71d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Mar 2021 13:43:05 +0900 Subject: [PATCH 4/5] Inline add of spm calculation (no need for it to be a separate call) --- .../Objects/Drawables/DrawableSpinner.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 1a89fc308e..3a4753761a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -69,13 +69,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Origin = Anchor.Centre; RelativeSizeAxes = Axes.Both; - AddInternal(spmCalculator = new SpinnerSpmCalculator - { - Result = { BindTarget = SpinsPerMinute }, - }); - AddRangeInternal(new Drawable[] { + spmCalculator = new SpinnerSpmCalculator + { + Result = { BindTarget = SpinsPerMinute }, + }, ticks = new Container(), new AspectContainer { From 905cd7c8eb9e7c3785ffa88e63cce5b4b7f2c024 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Apr 2021 19:22:07 +0900 Subject: [PATCH 5/5] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 196d122a2a..c78dfb6a55 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 71a6f0e5cd..92e05cb4a6 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -30,7 +30,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index a389cc13dd..11124730c9 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - +