From b5a6455d0a0f611f6b9780578ea9c6341c942a61 Mon Sep 17 00:00:00 2001 From: Jeffrey Han Date: Sun, 15 Mar 2015 15:38:04 -0400 Subject: [PATCH] Implemented "Flashlight" mod. - Restricted view area using alpha maps and offscreen drawing. (credits: davedes) - Added silver grades. Signed-off-by: Jeffrey Han --- res/alpha.png | Bin 0 -> 16293 bytes src/itdelatrisu/opsu/GameData.java | 20 +- src/itdelatrisu/opsu/GameImage.java | 4 +- src/itdelatrisu/opsu/GameMod.java | 2 +- src/itdelatrisu/opsu/ScoreData.java | 5 +- src/itdelatrisu/opsu/objects/Slider.java | 20 +- src/itdelatrisu/opsu/states/Game.java | 374 +++++++++++++++-------- 7 files changed, 284 insertions(+), 141 deletions(-) create mode 100644 res/alpha.png diff --git a/res/alpha.png b/res/alpha.png new file mode 100644 index 0000000000000000000000000000000000000000..35f9344ec7de19134ad21b3e0ac9b7d12e1ad8f0 GIT binary patch literal 16293 zcmWk#cRW@98$Wk(t?a$8Eh2m0i_5iFHdzTpl#=aUl&s8>kr6_63E5W^eeKGY5h8nL z-SPALO??K<;Y}0H8L%qpNFS;S=yE;Gs{zeE~yVU4i?70bY0fZUaEbO#TgT z>l=UBG*9M_bd2M1FN_1MSm6RzI!QQ|bWuq`Dh8u?!8g;amfehcdQk5EqIgJ33ND$| zQi?W_dWv#Ius9{QBtG%Ki>2@q---5oy;VZSn!%8VlS6rnxx+us9507Qi>PXdn6aXwe ziW(zH$uw}`)G2WHNW~&MP#+hXqM(x`uMhO7Mxf~LjNRe=|LaEYO;D>%eQ*Fk7e!0f zYjYoUb4cMSc)DS8-82L@r6)SVy`Oa$bf`UXJb`W!)iFBaaYOaIl5R3TQM?LxOP{#R z`&X`gw)u2L^FIB#JG`JbDo|Gb{jyMl7t1m#j{u< zjMEhju3IHq3)mR&9%u%FRi8QQx2CK219CBhxOU9-7ttRqHGk4J>b6uUb3M^7rWy5o zbUinfttf9a@FSOIveKKi(JCMMnN%&^K3V)a2c>+kpX{}-Jv#4Iq^L4)4ZKHG(^h@u zs<*9%v?Uzh8hIZKWs26rQz=1%bp+h`v<>V{?9HYODg=0h6&Pozl&K(3EI*4OGAoQf zincPI^>TQ~U4Le8ASE=xl*%l_5FW4Ft1QfvperZVk_I+>UuylG>AU`Se=DKCQt$E< z47h|czs-31exOu+Ati9N|LIEpO3#Y?3ipcOffIGU2kJrT>5bpwR{z~K;#_4|Ram9C zoh_$rQ<7`-tKy9{qhhL~PR*Mpt2)bP70e1jxvE34Y=*{fYl>g~;PPSjIk{7}{-~Al z*GoRLSEF{{2mWH#U#&xqf@$gESY!L*y~R26I4C*1IfFUsvQ5f3eStD! zO=Z5t<_+!}-tW{ao>h8R1Xv?Wqa26{6b?NmJ?6TlmPPMfm)pMz(A?r|2$2%>rK^ey zidO6ueLplE@mR7go5-B;|7UgVWcK%&6AH%U>NvCC;<# zO8l1KE>kdRxqjQ(w=(NhmVK2$m1PxXvqPzNyF9-Cw%DT6&V+GNw^7uK!%0KKag9=VjsL zdtdK;EfJ*<6%;i#-7wwBwK8oflPFU+Q8(!u8XPJfn#fVi)4V<@x0bh-H<`E4?B*Ea zDAiQa^y#+YhZV;%hn=Q&|EG?s4sY#k9B(%0*W}lTOtjUsl{1#Nm}Qz(6-;>d)bBTa zYkJqrS-(0(F=G9|y<#5^F;dnf-!-Vcm7yLZdO*u(gw1ml>2Yp=Af<#B)g z)!(sV^yv7(&84vK$N6~;d7rl{X6ts8cJAyvn=2etD3mC0kbgMZ)33W|xjKfVDSRpG zyHFivu4^9c-MA(sb4w=Ou4b~n`E79JiTf_Q)<3Pl!ey;x4dr>cc?Z`)Wqfmg^MH9z zK!NMi1zoHV*^?Yb9s|m8A|P)_LbP_Y6ZqmZEJCm=1*tZB``?G@ZSCZu7HwAT)R>x> z)$STWpZ=}18o`sb?BVp`J6E1sM6FMZQo8?fPwI)H6?^*Zsa+g0<$k<9*Vy$}ios)Z zx(^r7=jbf-e4h7pbJaR&BSlHA%4>HuW|UhMT9IF`c{vP@+vnRG`Wf@6UX!d<^7rdp zAZ#B#`AX&kdEONjcERsSU z`kD8$H$rAiX)Jz9*IAO!?7%?MG%RB)y_>25osnwPzV(-%0P3TO1<*600|(Hc8$aH?P{ZdsQ3NAsS}t8|~8lUrcu+$aj;*hI;jBq>i*MwLY>w zw^rKu{;Sb_^3;E9hdaFepTgGotW*05QR`dhtPRM zHD>o7Z1+LFLx_|@SDGeUevb2g*R-}uow&M*xzfkqNy^_1mZnppvw8md>u;g^=+KqF zCsYe3d~|9vkCVfzmneVmKj!u3mAR{vdyuQ2cO&=0$-}ADd{Ap_Vq-pxfSKi|f32Y5 z5NdR2{q4@o-(pHDZ9Ll0AZ7kA?`wPAmRsB6?Ax*v*3lU5b@G?HKzaDrQAg-L z8+l^9KvnZs$6;8&$(NJk1-4&Nfnvnrfwt*jv7MTB^LE!G)VB7t?yqO#NGHn=yO7u(&zbHTj3B@6WC zD-i-%ak9_Z`{Ns<RwXOI~{R5^oCJGO#iQfG`mNz{LW<8ToSj3jjf~ z0I=Z(0BYF)z#fp{`b8fA6h;lv+SVa6TalZO5t<{_TmEu~bL1~OhDF;%?+|3F#mf~6 zrYi@IQn|WCnU7u?-FCRv5ghu}(&%jeMs|oF@8+7(Vn;*a-~0x})6aL49g3jdwJerfSPZ(7Tg=6w>4I#U)jgFv~;UQJJ z(Bs6B0#FxawfLwWIu}A<6V5CyxVmUhPBEk~1QsX=n(2`mwLK4Se_9{dYps|p`ESV) zoSm@3wHSG-+1kpw_Av2*GoZ@4+-}UI&4jPPrxxE4Y>BV|*dZ&)fM*P#u{Q~Vv*LpP*YxZ$RaNT$cjNV19%Ks0Eba&H32Rx_>Mll=&fVezdsk2fG@tJ zZ^9dHN#iIYYm;_YfXdBr2eZ;aImkFjyRzPB#1ivXKqKh00Zw&~jY<~G;Tiw}*Sl1K zSOtw51rX~wY{DQkumHlV#ea}b@5l&814&j4<7a$=5+L;ylfOYIfc6xD;0A@#r0Jn0 z>^GjgqZm0d0f*ek8JRnsUUzvmQU&}R4?zmS%@rfqLFi&taEQE1Lw_;d!gznM<5zbP zap2#hRXV$Asbq!j>yuI! z*&ut#fKn`6X^-<)h(Czh-5NG@7P&AXX3uR=CvbPv`n4rU zer)04QB8kcw=al>P{`~co`t?=IG|yf6Ejw#sQU? z)FF+~a5z4%=Oi#fAIQ@MxI-gEVM#ufZ{X-Xg%Y@oQrI1=TO?%9sjq*l!TDr^0q+51 zYp!+lwH7MEyD|X{rYZ2r`BZqS9`)1Lx|aGS93Q$mN(?`Ba_%|w%3RswTt8lBZLeLp z(LEx2yW0y^dkiW*Lp~%b#{x&2V!9xE;ri&p_bzoC>Xq7HsTzr0AEsIviZGhfTT%9O z!LG<>2ccIz>L`jZvYX3##I3s#QG$0May%#EKmgZ$b7~xIhiQj*#EwEKz(puu3b91W z^T|+f`cbq57||&RiTHdSjM0P2Xj5z^#gJzRE|>)ykR88oeK@rK$_RTA!1fyyqJ{5Z zMJbRmXM{Tz6tgIBGGNw2tc-Uj%-vR*kSrB1#3Nd%zn8u`qMTp{VXb$P_P{(Z`h-U7 zsE?|&q4Bis3fhIb4_I(-!8>mNE=zFMl6Ow1!MszSSS(;hv51*YO-VAM(qBOJx;*mC zc_On+Urj&iu*SF333o}pkv|JY{5v~!W592uX`?x<1Dzp@{HB9Bw?DfaP4?wwUhzYN z1bXQ8!X8J+z38z_?^YsgE9n7Qmi8+jaPemlbL8z)NeLACJp3ycgb4o)0U;4!ecrVD8@cwTV#{O%fV_03qk-z1hD`n9?NXWe;%N8O<(7^zCAWZ7{~vD&?;;T@q94Y zF`lrqye-S{S#3@zquvVq=+?^^`LnJ$JS5GKA7^uZZ+IIGUsTb+^6WG(?aR%{^o%&U zjBpZip-RW<%}cp=&5ZUQ8M#Gz>|unF8E5?+lLw@t6h#n^DAi&;Gr{K`jrxzo=IU9n(jLvz*aWJdmY#V{~S)j^FYQ zMS>&w180m#^CdT2Jfm)+g#Mz~oq>-q4czjcJp?QKWO=XPy0i+|rjZWs8n=5D^*y!Y zN^d%u-W2BC|v;EVUUOX?t5K^z2AOk110A!91Jwt)Nw93kbjzOo;+8 z$IVTA{Hq`OeT5g8Kh?JGJ3{0|ZQfOsi}Qr7u;6{a3&w07TL#T@rTybyxo!ne_Q>ox zlZ}#*!Y5U8P0}i@0(Ez2=D~NOZGmTt)%f*uUwYJ4+~M**Im#A_dC8QFmMx>mLNN45 z{ghn1Q${^W3DtOb>M)$jw$5Yl6XQnC7J!xl`rvvtpoZz{VdP_Q{0o}GUKfs1szrt) zp!h}|M(o%3Nf&u5@GrBQD6YLUdFUo{Y@yBG`wFrRghZM}9oVqPHW?RTp4eED?P(elnt_PZMdDC~QGp zlmJLiI8ut>S~XeUSBW1l7_!r7!l5Xgw>fPv6!ryWgB1JFGT_!I_54Ukqu3r$rzbrH<2KukzjWYm{hfvR68y94XuFZ0~2W&+p(7|2&GW!|e4|4tGbQs9ap% z6uwHB&K|PVlWIIqe~R9WjpkQftcdsr@Q~=A0Z}mcqEOmlxbJz@Ey*`(q)MEPaFf%% zNok=Vm8jF$U6q?x>(uwGS@&yn5J^zBy%0r>=#r`JS+)#t2vMNs-ty>zMX=1Zlg!c?;jXQSsFC&GCrsigGW8rI z?A>%~LE!-}*pi=EJo^Z&9;dTr`vrS%Vjl+5j+?#sk;+C>Ll2MgC3a@bRAh`K9(2V( zakR*)-bvq;=%y2<`w#TqMz$a_7OXmg4)T+wsP`GBjCAwa>=*eq&lIOpQ>amB%csg# z61_cKg#n2_`Qo4x{Ll{evsb7iOa}RtHn=N!Qgb@B@*>;4_wK3pLqbOjQIiFCGObx; z@CaHR*wR%SHz8ll*v^(|J(Q>-6^CluD5nBG@dh7WQS9Yhc`r)3x0}P*840;$Tn=?3MF+X zoW=uEFb0W(G2T?fes2HfvPvpU-03?0(^161Zo9y zMhwsc#@dmHGxh{&j`!CrctHa+KxCcF*cTbQ9gk+YhIR< zie;V-V8nGkUx{h`XWQcU-C^}{&8K;~Q~5hO?DM^z?XJmd*-H@_1JB($M5dSs*v;qq znEK%5lX2dJKI(667og32{0OyBdw(i*xJ5es01<1dLbBDwdg@JS-aX2MZ^gUDcm1Se zt^aS;+C4p_!N$wSF|c#{2-FIS5KJsba)DmpX8HgAxh1{z9z}MF)-nRZY757YFdL@< zV+mrEqTr9iD9pKkc69?N@vXn!A7uyD7U%WSag<^_&c<&3HFW1Cm_-({*tq|J?YQWS z$VkXqX$zh0iTdv1Fe#cEo?C&>8CFB6%-I{w}EW4mJ!cg&|XQrTR z@Teur%M<;L3oN%;*D7dzSgT+QgRhfop7zowUT{6t+zTEjo>zY@_>!>W_&{J(2vM(w z*i`6tt1_DeR*hqaO68ziVCLa_dJF4kJ4Z z9dvF!pV=M`SKP}hp{|~%REwN?@``Q2nr?0eQlHggiy#X{URb>FR<>PTTP4_LUdiGX z-NRj?o`GmPm+_H^HN4sJJF0h%M^ zfp2HWA1ng$?h!9;r;r=aZg0UizY!!~eLoi)}>x}Cy4R;J5#a* zIR0q&WCbaCtT&{Q%^6P}d8KzxH;`X(E#6s0HA#OK_ zsh9Wa?@G}By8hQ|9v=n~)j#WMJpa#Wb_D;t3?w~0@^2&Q6Cu2LVt8Mrv?y3_qIY!V z!`!Q(L*-{0?fw3dE)Zlx#fh@cV>^jxr?-H3-3Ke6NsNDhIPFXpzX*y%LdZ-&lo4s| zh_c$?-H#vBS~PwG_i47C&ndrG7qofrRQiTQZTA)!csW*h@77a9XQT1RGv-w+WiqPn zztr)QR|1T}r-eQI*~G4!Z7Nqi;6`}ac)0W~_B_M{9<_T}6C!abT($ zkdFWK@SsAU%|kzGRtone+0X8!KCN#e zBBmL~v8ox^8*WJE&)RxeMwei`5>Lw=YKA*&&)H6;VwC^#yd`5j*v|=~AIOVn?8Ikd zCPKg+27T@i`Uzh$iHN3InHXA0gmWC>qcA7yf)MT}qIlPK(7JK1KWz;x5Z2sYh$(aKq zzXagRO#jhcY-2@;iewGe??qRUDsNreXgSZ}zDl8E{Pas`=UgUW|1)*sw4{}^@O$zi z{ES>&tmS+7`UBEXAmdty<;#1ClFVqgbQGj8v|+e=0DAf*{epFcH1>zjuLk?^r}Fo+ zagReNHVizriJxWpl6P>c5m@g)L`9EXW34wKe6~$JhOJ!m@7)?BBXz(PZzr`06ThfP zJk0{p{S;#Pb541Mj%Yz(Jxd~<<-Z{>kiBt0&1G2MNqxM_*-b9o^Wbmt=HFZv%bbD$MsI*EvAX_dij zadjzaGN8a}8_Hq}%~{M&M%p{?fa*Y3#?Dm!yBpu;$YfIIzXb*&(%{JZx#rh(8IVYt z&@+{2@X!_eJKADeI9hbQgq2Tu+Z^xMGeo0bwZRf2U^a1Yl^2BPJC>ODX zVgmtQ@}*pqbqih1S^}-Ia6Q5wj_8TAPoMxQImE1&LmSC=6nK4S|LLFeMy~f|1SW#& zzucJh^L?c=6a`<#S=QD9v`!qYg+HV$`FzP2^zao-6-@2yEhC^IF-e!#+ zgrbjuAxrw|yRs4ali!dOh$ni44w6)=8>{nJxlp8KpNkCAaBuCC6|87;p_Np<`Li(dEy`cF<#;myxgs`1;i1JVH>y-1StC6AD6sIz9!BO*|b{%0d@jP#e zH#WC-Ht~mo8y!00I<5A{8s*LN0e|tX)PMV#3dF#kEr&+z3-{lpJAKQ` zd-KfrSUrT#c!}x+5YqlDGM#{g28h$y!*+iC72E7TWU0qH1QnCc6VO5uJ5m*jeDnY5fFbuo4hGbGX>yQH{zF(0HlQ zCd6i(TC=#$8Lgn_l0VnwoO2=1S`RcU<1U7~P}OYy6acxSV-=aXkt44)ar zAtU?^Wz;mPt{WgUM=eL_aP@v4hglop3xJix@uX%U)@xQafzdJgQ6{qI3Tb({#Mvx8 zfN5clA}5c1Rw$G^QVw#TZk{cunzehdGm`W8A%SIG$V)X(4as32@6rF+RoJEzZ%Rjq z34h3A{Ck}Rzwx`r00kaHCJ6LtMvqu|J;2wc+EMbmgl&Nq=+bOXnRGs0Dav@w{}w2a zjQ3lyls{h!abv~R+64q}bxiF~^wblj+f)K-?Xq#{lT#N)0_Wai<|&2Npz14MTBGW| z^m5@{V4w7~e?705j}iDNrLqB&HM|i`l`YgK#0>4BGR`TP&zU4$`%b<48L~Ge1tw@J z*Nyg7oex~9h|vZ`<^1F1bIP+N>~Q$&=8smuK(cOLG&)M~W2MB*e@>T6fiD}%AR)Uo z$N-*&(JZmN1tKr={B*JkX)*-tLY>BnRV{cafC%@Vr&dnS0X>wBy=R_NVh8J=ICuJ@ zHPP}fnWV~R0b75n%_eAn`)E-8kb1Rv^*~nIp31n$6ot5h>vOZENA9w(%T3~iF)4)t zPek8y<)*_ou881eTU0-3rP@-hOaG~O0xfPN6wHn^9kkvR>g7@0sQ{y|g;VNZy_?RD zgGVok$1Gz#6*CmJJcU-1#5F4fVDU$L=176Qk~N~vAby{#8MW>H?B#g4_PB5j*Qv;; z?V{TG2nWyn-0jlOGMDBNsstVw;C(-Ecc<9tjFf@Ns$Gg{o_otMLEw9qw;kh(g5TuL+sq=69$>~TBaM*vXYM&g1ZK+Mdeb%jK zt#+^mvB<#c%~-}cHW6_?JB&37gAvHSsxxr-eTMy|Y6<-pCwW0YmPWlb2%;<}^Y7#F z4%Io2i~&V{1u(+!aqx(#_vO=fAFY@y%=dFojO}XwzR4Y+wTY84`BTC< z$mCs*M7NE{FWj`1uEHw^u|cW;Vjz=tig(fx;xNA{Y0p7HX7yUMz{N=D&!V5N1Qva9 zQlrIi!_l=6cUFt|K3kslpN0fOSw)<5_fdjVZAb8jq(hny4sj-s!1iK-%*qVL-a#~0 z(znn~117+%1>6$M$}CcHr8?!=U8j5j*k%drv=dyVU~c(-kM8Q6P}@W8RP`hYOh>9N z`Lqbu*a}CsCBKoc0-_+aNn(G3zEMO+eiZrxn?T<~46)y|mgn*h$ouE=M{ z(lhX8V2nmzUKuo9i^uYHXa{hU*`l{FE6);OG5{5Ck{_;wh}^aSOW>T}0K!aj-i|SP z-F0j7<-&%SVk^g=TMkO6fIq3xRp4KbQoGB5?8i-iM|j5|``Xfg#KAG^HVRpzt_C&L z_;}ZVWPEC_V91S>L4$AU%bcs7DwAy@L`A{DbAT24KpvJ2axHD?E_i*p7=lF-UMac5a!AP{U?e5wiHu({H>IJ_M~1e#^Il!mHz!iKD^2MVcL1b z^Pp>R{A@(S)~AHQ=CgmL#wk2?ixBie7X~)yS4q=MdE%+tDv;?%7EGC8hB{3h!-j*T&M=lO|Zv{^a||{urZY=tN(x|7 zc$!b>{xf@RMej!cF!YJ+RpSg)U^@ioJ-#;tV6TGLd+0iLh+A7Dm#(^1vWrKrtetmI z2~eLx@2Bvbh^6H*>U6|Jdd)8c*Sn;mgiusBaKfAUF3P`Pz#y_I?WTK@w@iKDAO zo08hp(({6Ky&f%>R-dp1eixxa^l^jR)7%{4e}r*g>L)EAVpFl-OVy<&VrgFq^{KBO zGDaK~f!9HDjH$qIiE4}bty0dvh0wI+UsM9Q)MyqQDVfa_gb@2d#rP;thw){a5a!PX zhnVH-p~XtOy)ROQ@dY3I9Dl^=rSQm5gi!tVG?(4JQcEre!qT8#{w|daFENI2RTu3) zyDdAK8bgZ=^OK7SBN2vT$->NkbuKNNdx)tQjz+1`L`_1n^Y3QcfT%7LC4pthHss-r zUA6Sdl1#rvlCA#b2X?_>`cTH#dRA#E8o>2Vi_47NY-ZMl##Hc)ifGH%?oe{f<#~HfA#l=hE>&*{mWu(-(vgzYJ_L z25=F8pkH4CT8RDhK^8U}Q^Etgo+M`YZ_q|***S0}#Fkaows+^<`+X~a8Pni$&;Z*1 z^9Cpzt;*@youv@=ltndABbIOy=4+1|57Bw@|8Y9mjv0Poomgq{gdjkG$WKv+I5VO<&GIU_Z7{9)gr&8Y{pIo?y0&ljg;5 z@0n-8aYbAjel*F}zjo`>(TiRQ8!4C_en+mg1d4pe6WBYXcSYG@i<9Y+- zqM>i(8s_IF5dA#v6{X**1==8%1m&>=6G#CMt)Pki{hRhqd+_j3t5pNfBr{O!lJ#B6 zAOfVEx_@7M4BLLqLCl3Ng6=>(s{I{)FQ!d#(Nn)1;l*jPP)Trgx8mHXaY(%MEp56Y z3bp25(-#bbZ#ID^D;JvLWOL9h22(-(nP;~jSfpT*Nb3bKs~0Djy$01O?M__0C3Uh{ zSlp1Adcc@YoB{X#Ww1q3Mg9CUaJ95e@iBs^ND6ip=?r-TOK{60z}gcFGNo);7?y z<3A2m53QRkxO^z69_dg}5qOY#(0nwOj-ISdx2?mPA@5|`rBe00>?e-4?r*w;PKpyZ z)?^3MdLi3KR(Jy*J#bAwRa)|*9!+R(vWnhkzlr<8)zsSvy$EN)am3}>0v_l{-fw*sT^u8S`?3;I3w1A_Wf9+j1D%a2?tO~V z_!|Hs@^m1dt=9>W%dLPy$qoK!MxS);q>OHG!Q}S^2x;o;EF$)4DaaV7LM;fUnNz$G z&7=I{@~_hKK;@!Gm}&A`pf14RUut1O8MuTfUj|2kU2s4>0%vy+A%eCN4J02y0_TKw z1B4F(gwLJuAf_605AFTAR zry6-%cBd+S305d2Tk4(52yHa52+Zgid-^}jmjwwdCNbAoI$Fftx?5`Mw7+Q z^f|ttWbU?ieGe-MyWi>E=J&?*k$)M`3(6`0OF>T=k+_by%UW-)*N`d$I?qjDgyD=L z`wX5-lB9}C<`x_wMI~g>cFP@8FHo_hn-u>22IvV8oJu{qAow{cruaszA$V8fLUYep z;2aApA+JGCgTCz={!zRAt%eE}zmkQM!k}a_p#mF0(3RSG`2tsSCWF0!+iweDQXM6V zj=%c;Tp%GI3Z>y5ie$@}Hcn9ePeAVv*B&K;6_+(w2l8NA=J~;FFfGGWb)(%esFXh>hc;P#it0xD4E#lO7icg1-d<|l{wLIZHm&FsM0~O9P z{=kcpuM6eO#2|9l`sJNDc<-{eXG|+svc4sYU` z$6T55uX-y7w3$y#c#Li+b!wc9^1G*@QliVJ~U(sq1wa1-O6&rgn&Q)u<7do zjl`6#G*(NkU=iQ}q9)$;t)1ua)UD9=SC#0u9nD__5*FW!kWHA)DI$bVIvR;sQTU=T z>0gMC*zc;@IcYn>mft*2PVFtLe7*hO*(rYOM8+Eb&#E@3?O$CXs$KC515D|F@A^K5 zkc+LG>brX0VtCY*1xU$0>SK9Ai|KK+Td6m3_ORcD$WVY`^C{z9C&7%v<*mUt@rBjkWP{$mya^H(Pb(LoY= ziF^3pOo?5~9KR*=tG8DI13q71A~vx*iihzE2R}tRF+w94Ml$_%BBG5L5=6R*E?odV zAtHS1eMxuJJd{`+lo?i!XMm4vsQ9}aGEa1hpRCpvIAsLA1Y0MVav~50OrP$h1Q2Cz zBNi}mg)a|NYg#Q*$QTfr1|8n?OL=1xdsu-r2fJ}6H=*WJ)fkdWs1fYYja;>Pw&gRf zBcUgyO7AHA^VS;3Z2vT&#*l5vU0zk9gx~sh?g93^^nRK(u%qJjK9YS zYw5qu8XnKQPe1JaceiFk(_ zA1KZM|KFFs?Js(7AqCNKYQ&2 zn|VKwlQ<%J{+3kvD@vcWt(DJ{=srN=+-WxVbLf%6M8M zT3~RFP1q)2Ipg>%5AqTS^Yc-`oSVGl7FhqjXh`RRF=gu;sH4E{LVQo9OtH;d=r04% zBO0$N_dCkhT$-FLgOk75B_7z8%KRtAcC3=IiFqA0@pU$k=^ana1nzy8>*bb0HMTW$Oa=> zGFl7qawNFj5Uz(MJbzb+5sB5>*9dINgG6nTr(!aYzfXPLFv|OTY^Uh+i%6$=ay~T* zgS@P7(xMO#pE9KD(=-09AZuR@A%E)Ghm%Cx4HNZJo({U8sp>U=sgg05-h}}^>A3fS zk!ZknCG`){)vWiV^}K$4r0hH~;s%L)Qv!FJf=!oXbDO}zM5W$hZ;`St0T2n=nl8~? z939b072v9PAB_oYs!o3qC4PR_03Nv(UOXQDyVQH)`rYv%wX z1(z)}mx31lg3t!2l@I^sTeBNeaR_xsP#jyFefmm~ftv|UQ&*qqyXdk(ou3>*!fE}< zs2mG`I7}X?Xz5EC%bHlnP6MIgNnu}S%cp)s6|PKWTwDg61Zb8)&TexIS)B?m1Q~$e z71n{Ln|h$r=@{@7*F=dk&z5MCPfYd5dYT2(eGKC)A<~0b9nhe{B8%IeUgF2rLoD|p z@xhOj>vGt{g|W$u^NEqaKYy>F$vOSxnq@J#fDU*WmP(Rk?CB3ooqnp&`A_iGkJu70 z$B!62QVpw5;Ped7cB$XEqWv+Wlt^O67imNMW_LN&j9fS;s7NiUu8f~E3X{=;RjcDQ zTp?RYqFs&YX-K=GfYMdB8a^ojUX|34-$g6YU0iVZ zSoK=Zg)}{ItXsc9{jXXzp5eo@9RR~8&p_=}&bG97ne3qf>7sGSBJ3`; z6-@N3W8${_*PB1*nD^~&CQb!$70d1S2_%eco|}WfZui=&=`_6B&Pd{W{35V= zKBoqVX$XV?O+9$sKW=)G0$)gxR`_!{9X`=jSEFxK^aJcGw$gCdEw7@O_j!lc+wh3! z9Ii;Er)xDo*gewjMgQ$ENFOPmIwWCq0E}ECd(Q7e>QO!l0@4sD@M}!gW^_5Y$|S>l zFIuA1HHKTCk7AUcg7tnH*-6_c9bb$?veD@M)5sRVJ*a}t-F6${8zrasLNYw!vgmGo3PqJgO zzI{}qc1tFR?VYNKM~_jCqAi~F1zAwM9AvR~diyIDz5|M=lVoOfR}`tMi9mXsJEV5? zhgXm2F(O+_PnNpL22fdDa7nZ^-J)HBO_oE3{9Ps9ONmA914gKY6t^?P}fT^8oneRrA=SC^0BqR?9yP|&QnfZHCB%uIBA`5c?QxmMT4(g@-(6+I56nL%vzzWJdi=Ymub@S71D7eZ+tuqoGL#$a_vELAqa)wYK9&u(xZPi3 zgZH^|SI!>3$NqjTu*Rd1am$uk*7&k98fJ3_6{_g?C{PSNs?<}+oia6U-)_vAn>1BZ z7Fsy5J<_AX8HU$ACVPf3^e?5F9KHpA%0w{8>EaUIztTM7EFMFA+IsYQSe@>G)Dusj zRHSiZ1P&pOYVc?FFLcC(^|Ftl#gf3y#i8p?BUob)MebV1Ydp(b0`4Y;V7f)8dgR@V z`Hz%==yxby_$4nL z&WM}es8KcRY6g7Uc@w}>1}n`6=cd5Mly$>H@YjcMQ0SJLA9^JoE@Yo!dxs#jTF<`0YF9uo37NErdfj<4)mEN+BP#mt4X|&EmhQFx{ zcD&K;YmLKkGE*_26wZU+SN*YI(PETSfhBb3n)#XaHn-EHpT!iACZKx)mlTbwj&tvh zO6kSv%cYoiiGTvvvs2K!fOIE2m@*BT5V3cuB;Qb`=jtal1f&f!q|h2+8@q z;CRh9rHX8^d724qamM%$LgS5K)ojkU7NXOEN-!}SY<(8|CUuy2$+ovRi@DXXeT2I` zz+(eocmubG@FEC5py=5M2U}KbmUeto{)d@|CEH!O zR?vR23JSwZ^j!tOTSz{(Kn?4sW-#gw&bwL>GRQT0*(l7_7P7tAPOVP`bPdovw{h{U z&jGK{7Y(W9O_^$56^Lal5_`W%$XV*!$vIl60;U$c^g(E4XZMq_4z66t`5yqQaNYYR z@tTZ^ZRpU_8DxfqM_FwAF8=PWraxU%w88f3VHM-FbWea1nd}eVivR{V{NgUh*!U0e zD<|Rq1z#lnUb#($Kcvhyj6dIht`#i``?XuAcEEcKaf2@E5!PFAoM49iO>z#x4LZsL zc1yLqm2Mzbe94)~(39-M0A6AVdGN}?x<__$UOJzBIMfk(AFPT6#T8y7>Ot$3$qVw# zZrhV$G0I2lpRdBkHrk$Zq##IUZPL-?0w_KnznRcQ<&S@JBJD!~^1G5P?GVsWuJRo# zjN{yUg=>3mPz19Af9!bG>>8`bHDsus{V@Q9KM97A+8FWGhT?H{4AYB0j9^MFTNh%$ zqP+fs6sUfAU7w#1G|2U&74ViX(4|w60=ub3f{|2sMXE6p=S5p{7j6#`@;yxh_z<}qs~D%+i}}UNH}U z0$-;h3FGqK!kDaKzkutra}DY^FDB&7nYRomWbu{+81ks?cLVdOA%OJa$a1U1d2um- zWG$M3Q6bANXAsGNr64CUN&krRPGONb`1G+2?lL!XF}@VSgr9}ik_t3ILg_!8*!cyi z&=lc_h)dNjx+mb?fge~UbO41fiDN_BLVt12pg){$<%8znkldppMvqZs5>HswXa%#t zNIy}Is;mQ#&TCEX`;tx&&;Rd1tYv3Q;lj3A`-^f_ng!K_nBQ z8p(dqHwSNi2XxvA7Uie3TZ0%eSO7eg`Kn~n(dF8LI*AN?p5}cY%gID{xep33)H6fZ I>bS-H4+lq?Q~&?~ literal 0 HcmV?d00001 diff --git a/src/itdelatrisu/opsu/GameData.java b/src/itdelatrisu/opsu/GameData.java index c0674719..33ab2ff9 100644 --- a/src/itdelatrisu/opsu/GameData.java +++ b/src/itdelatrisu/opsu/GameData.java @@ -917,22 +917,22 @@ public class GameData { * @param hit100 the number of 100s * @param hit50 the number of 50s * @param miss the number of misses + * @param silver whether or not a silver SS/S should be awarded (if applicable) * @return the current Grade */ - public static Grade getGrade(int hit300, int hit100, int hit50, int miss) { + public static Grade getGrade(int hit300, int hit100, int hit50, int miss, boolean silver) { int objectCount = hit300 + hit100 + hit50 + miss; if (objectCount < 1) // avoid division by zero return Grade.NULL; - // TODO: silvers float percent = getScorePercent(hit300, hit100, hit50, miss); float hit300ratio = hit300 * 100f / objectCount; - float hit50ratio = hit50 * 100f / objectCount; - boolean noMiss = (miss == 0); + float hit50ratio = hit50 * 100f / objectCount; + boolean noMiss = (miss == 0); if (percent >= 100f) - return Grade.SS; + return (silver) ? Grade.SSH : Grade.SS; else if (hit300ratio >= 90f && hit50ratio < 1.0f && noMiss) - return Grade.S; + return (silver) ? Grade.SH : Grade.S; else if ((hit300ratio >= 80f && noMiss) || hit300ratio >= 90f) return Grade.A; else if ((hit300ratio >= 70f && noMiss) || hit300ratio >= 80f) @@ -950,7 +950,8 @@ public class GameData { private Grade getGrade() { return getGrade( hitResultCount[HIT_300], hitResultCount[HIT_100], - hitResultCount[HIT_50], hitResultCount[HIT_MISS] + hitResultCount[HIT_50], hitResultCount[HIT_MISS], + (GameMod.HIDDEN.isActive() || GameMod.FLASHLIGHT.isActive()) ); } @@ -1031,6 +1032,11 @@ public class GameData { } } + /** + * Returns the current combo streak. + */ + public int getComboStreak() { return combo; } + /** * Increases the combo streak by one. */ diff --git a/src/itdelatrisu/opsu/GameImage.java b/src/itdelatrisu/opsu/GameImage.java index 74592995..e8de7423 100644 --- a/src/itdelatrisu/opsu/GameImage.java +++ b/src/itdelatrisu/opsu/GameImage.java @@ -327,7 +327,9 @@ public enum GameImage { protected Image process_sub(Image img, int w, int h) { return REPOSITORY.process_sub(img, w, h); } - }; + }, + // TODO: ensure this image hasn't been modified (checksum?) + ALPHA_MAP ("alpha", "png", false, false); /** Image file types. */ private static final byte diff --git a/src/itdelatrisu/opsu/GameMod.java b/src/itdelatrisu/opsu/GameMod.java index dc7b1146..d29ac60a 100644 --- a/src/itdelatrisu/opsu/GameMod.java +++ b/src/itdelatrisu/opsu/GameMod.java @@ -47,7 +47,7 @@ public enum GameMod { // "Nightcore", "uguuuuuuuu"), HIDDEN (Category.HARD, 3, GameImage.MOD_HIDDEN, "HD", 8, Input.KEY_F, 1.06f, false, "Hidden", "Play with no approach circles and fading notes for a slight score advantage."), - FLASHLIGHT (Category.HARD, 4, GameImage.MOD_FLASHLIGHT, "FL", 1024, Input.KEY_G, 1.12f, false, + FLASHLIGHT (Category.HARD, 4, GameImage.MOD_FLASHLIGHT, "FL", 1024, Input.KEY_G, 1.12f, "Flashlight", "Restricted view area."), RELAX (Category.SPECIAL, 0, GameImage.MOD_RELAX, "RL", 128, Input.KEY_Z, 0f, "Relax", "You don't need to click.\nGive your clicking/tapping finger a break from the heat of things.\n**UNRANKED**"), diff --git a/src/itdelatrisu/opsu/ScoreData.java b/src/itdelatrisu/opsu/ScoreData.java index 71ea5d74..fc25daf8 100644 --- a/src/itdelatrisu/opsu/ScoreData.java +++ b/src/itdelatrisu/opsu/ScoreData.java @@ -187,11 +187,12 @@ public class ScoreData implements Comparable { /** * Returns letter grade based on score data, * or Grade.NULL if no objects have been processed. - * @see GameData#getGrade(int, int, int, int) + * @see GameData#getGrade(int, int, int, int, boolean) */ public Grade getGrade() { if (grade == null) - grade = GameData.getGrade(hit300, hit100, hit50, miss); + grade = GameData.getGrade(hit300, hit100, hit50, miss, + ((mods & GameMod.HIDDEN.getBit()) > 0 || (mods & GameMod.FLASHLIGHT.getBit()) > 0)); return grade; } diff --git a/src/itdelatrisu/opsu/objects/Slider.java b/src/itdelatrisu/opsu/objects/Slider.java index 9b3aadb0..b526cc9a 100644 --- a/src/itdelatrisu/opsu/objects/Slider.java +++ b/src/itdelatrisu/opsu/objects/Slider.java @@ -94,6 +94,9 @@ public class Slider implements HitObject { /** Number of ticks hit and tick intervals so far. */ private int ticksHit = 0, tickIntervals = 1; + /** Container dimensions. */ + private static int containerWidth, containerHeight; + /** * Initializes the Slider data type with images and dimensions. * @param container the game container @@ -101,6 +104,9 @@ public class Slider implements HitObject { * @param osu the associated OsuFile object */ public static void init(GameContainer container, float circleSize, OsuFile osu) { + containerWidth = container.getWidth(); + containerHeight = container.getHeight(); + int diameter = (int) (104 - (circleSize * 8)); diameter = (int) (diameter * OsuHitObject.getXMultiplier()); // convert from Osupixels (640x480) @@ -192,7 +198,7 @@ public class Slider implements HitObject { color.a = oldAlpha; // repeats - for(int tcurRepeat = currentRepeats; tcurRepeat<=currentRepeats+1; tcurRepeat++){ + for (int tcurRepeat = currentRepeats; tcurRepeat <= currentRepeats + 1; tcurRepeat++) { if (hitObject.getRepeatCount() - 1 > tcurRepeat) { Image arrow = GameImage.REVERSEARROW.getImage(); if (tcurRepeat != currentRepeats) { @@ -232,8 +238,18 @@ public class Slider implements HitObject { sliderBallFrame.drawCentered(c[0], c[1]); // follow circle - if (followCircleActive) + if (followCircleActive) { GameImage.SLIDER_FOLLOWCIRCLE.getImage().drawCentered(c[0], c[1]); + + // "flashlight" mod: dim the screen + if (GameMod.FLASHLIGHT.isActive()) { + float oldAlphaBlack = Utils.COLOR_BLACK_ALPHA.a; + Utils.COLOR_BLACK_ALPHA.a = 0.75f; + g.setColor(Utils.COLOR_BLACK_ALPHA); + g.fillRect(0, 0, containerWidth, containerHeight); + Utils.COLOR_BLACK_ALPHA.a = oldAlphaBlack; + } + } } } diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 5be10f80..d3abb3ec 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -193,6 +193,15 @@ public class Game extends BasicGameState { /** The list of current replay frames (for recording replays). */ private LinkedList replayFrames; + /** The offscreen image rendered to. */ + private Image offscreen; + + /** The offscreen graphics. */ + private Graphics gOffscreen; + + /** The current flashlight area radius. */ + private int flashlightRadius; + // game-related variables private GameContainer container; private StateBasedGame game; @@ -213,6 +222,11 @@ public class Game extends BasicGameState { int width = container.getWidth(); int height = container.getHeight(); + // create offscreen graphics + offscreen = new Image(width, height); + gOffscreen = offscreen.getGraphics(); + gOffscreen.setBackground(Color.black); + // create the associated GameData object data = new GameData(width, height); } @@ -222,9 +236,15 @@ public class Game extends BasicGameState { throws SlickException { int width = container.getWidth(); int height = container.getHeight(); + g.setBackground(Color.black); + + // "flashlight" mod: initialize offscreen graphics + if (GameMod.FLASHLIGHT.isActive()) { + gOffscreen.clear(); + Graphics.setCurrent(gOffscreen); + } // background - g.setBackground(Color.black); float dimLevel = Options.getBackgroundDim(); if (Options.isDefaultPlayfieldForced() || !osu.drawBG(width, height, dimLevel, false)) { Image playfield = GameImage.PLAYFIELD.getImage(); @@ -233,6 +253,9 @@ public class Game extends BasicGameState { playfield.setAlpha(1f); } + if (GameMod.FLASHLIGHT.isActive()) + Graphics.setCurrent(g); + int trackPosition = MusicController.getPosition(); if (pauseTime > -1) // returning from pause screen trackPosition = pauseTime; @@ -257,144 +280,167 @@ public class Game extends BasicGameState { ); } + // "flashlight" mod: restricted view of hit objects around cursor + if (GameMod.FLASHLIGHT.isActive()) { + // render hit objects offscreen + Graphics.setCurrent(gOffscreen); + int trackPos = (isLeadIn()) ? (leadInTime - Options.getMusicOffset()) * -1 : trackPosition; + drawHitObjects(gOffscreen, trackPos); + + // restore original graphics context + gOffscreen.flush(); + Graphics.setCurrent(g); + + // draw alpha map around cursor + g.setDrawMode(Graphics.MODE_ALPHA_MAP); + g.clearAlphaMap(); + int mouseX, mouseY; + if (pauseTime > -1 && pausedMouseX > -1 && pausedMouseY > -1) { + mouseX = pausedMouseX; + mouseY = pausedMouseY; + } else if (isReplay) { + mouseX = replayX; + mouseY = replayY; + } else { + mouseX = input.getMouseX(); + mouseY = input.getMouseY(); + } + int alphaX = mouseX - flashlightRadius / 2; + int alphaY = mouseY - flashlightRadius / 2; + GameImage.ALPHA_MAP.getImage().draw(alphaX, alphaY, flashlightRadius, flashlightRadius); + + // blend offscreen image + g.setDrawMode(Graphics.MODE_ALPHA_BLEND); + g.setClip(alphaX, alphaY, flashlightRadius, flashlightRadius); + g.drawImage(offscreen, 0, 0); + g.clearClip(); + g.setDrawMode(Graphics.MODE_NORMAL); + } + // break periods - if (osu.breaks != null && breakIndex < osu.breaks.size()) { - if (breakTime > 0) { - int endTime = osu.breaks.get(breakIndex); - int breakLength = endTime - breakTime; + if (osu.breaks != null && breakIndex < osu.breaks.size() && breakTime > 0) { + int endTime = osu.breaks.get(breakIndex); + int breakLength = endTime - breakTime; - // letterbox effect (black bars on top/bottom) - if (osu.letterboxInBreaks && breakLength >= 4000) { - g.setColor(Color.black); - g.fillRect(0, 0, width, height * 0.125f); - g.fillRect(0, height * 0.875f, width, height * 0.125f); - } - - data.drawGameElements(g, true, objectIndex == 0); - - if (breakLength >= 8000 && - trackPosition - breakTime > 2000 && - trackPosition - breakTime < 5000) { - // show break start - if (data.getHealth() >= 50) { - GameImage.SECTION_PASS.getImage().drawCentered(width / 2f, height / 2f); - if (!breakSound) { - SoundController.playSound(SoundEffect.SECTIONPASS); - breakSound = true; - } - } else { - GameImage.SECTION_FAIL.getImage().drawCentered(width / 2f, height / 2f); - if (!breakSound) { - SoundController.playSound(SoundEffect.SECTIONFAIL); - breakSound = true; - } - } - } else if (breakLength >= 4000) { - // show break end (flash twice for 500ms) - int endTimeDiff = endTime - trackPosition; - if ((endTimeDiff > 1500 && endTimeDiff < 2000) || - (endTimeDiff > 500 && endTimeDiff < 1000)) { - Image arrow = GameImage.WARNINGARROW.getImage(); - arrow.setRotation(0); - arrow.draw(width * 0.15f, height * 0.15f); - arrow.draw(width * 0.15f, height * 0.75f); - arrow.setRotation(180); - arrow.draw(width * 0.75f, height * 0.15f); - arrow.draw(width * 0.75f, height * 0.75f); - } - } - - if (GameMod.AUTO.isActive()) - GameImage.UNRANKED.getImage().drawCentered(width / 2, height * 0.077f); - if (!isReplay) - UI.draw(g); - else - UI.draw(g, replayX, replayY, replayKeyPressed); - return; + // letterbox effect (black bars on top/bottom) + if (osu.letterboxInBreaks && breakLength >= 4000) { + g.setColor(Color.black); + g.fillRect(0, 0, width, height * 0.125f); + g.fillRect(0, height * 0.875f, width, height * 0.125f); } - } - // game elements - data.drawGameElements(g, false, objectIndex == 0); + data.drawGameElements(g, true, objectIndex == 0); - // skip beginning - if (objectIndex == 0 && - trackPosition < osu.objects[0].getTime() - SKIP_OFFSET) - skipButton.draw(); - - // show retries - if (retries >= 2 && timeDiff >= -1000) { - int retryHeight = Math.max( - GameImage.SCOREBAR_BG.getImage().getHeight(), - GameImage.SCOREBAR_KI.getImage().getHeight() - ); - float oldAlpha = Utils.COLOR_WHITE_FADE.a; - if (timeDiff < -500) - Utils.COLOR_WHITE_FADE.a = (1000 + timeDiff) / 500f; - Utils.FONT_MEDIUM.drawString( - 2 + (width / 100), retryHeight, - String.format("%d retries and counting...", retries), - Utils.COLOR_WHITE_FADE - ); - Utils.COLOR_WHITE_FADE.a = oldAlpha; - } - - if (isLeadIn()) - trackPosition = (leadInTime - Options.getMusicOffset()) * -1; // render approach circles during song lead-in - - // countdown - if (osu.countdown > 0) { // TODO: implement half/double rate settings - timeDiff = firstObjectTime - trackPosition; - if (timeDiff >= 500 && timeDiff < 3000) { - if (timeDiff >= 1500) { - GameImage.COUNTDOWN_READY.getImage().drawCentered(width / 2, height / 2); - if (!countdownReadySound) { - SoundController.playSound(SoundEffect.READY); - countdownReadySound = true; + if (breakLength >= 8000 && + trackPosition - breakTime > 2000 && + trackPosition - breakTime < 5000) { + // show break start + if (data.getHealth() >= 50) { + GameImage.SECTION_PASS.getImage().drawCentered(width / 2f, height / 2f); + if (!breakSound) { + SoundController.playSound(SoundEffect.SECTIONPASS); + breakSound = true; + } + } else { + GameImage.SECTION_FAIL.getImage().drawCentered(width / 2f, height / 2f); + if (!breakSound) { + SoundController.playSound(SoundEffect.SECTIONFAIL); + breakSound = true; } } - if (timeDiff < 2000) { - GameImage.COUNTDOWN_3.getImage().draw(0, 0); - if (!countdown3Sound) { - SoundController.playSound(SoundEffect.COUNT3); - countdown3Sound = true; - } - } - if (timeDiff < 1500) { - GameImage.COUNTDOWN_2.getImage().draw(width - GameImage.COUNTDOWN_2.getImage().getWidth(), 0); - if (!countdown2Sound) { - SoundController.playSound(SoundEffect.COUNT2); - countdown2Sound = true; - } - } - if (timeDiff < 1000) { - GameImage.COUNTDOWN_1.getImage().drawCentered(width / 2, height / 2); - if (!countdown1Sound) { - SoundController.playSound(SoundEffect.COUNT1); - countdown1Sound = true; - } - } - } else if (timeDiff >= -500 && timeDiff < 500) { - Image go = GameImage.COUNTDOWN_GO.getImage(); - go.setAlpha((timeDiff < 0) ? 1 - (timeDiff / -1000f) : 1); - go.drawCentered(width / 2, height / 2); - if (!countdownGoSound) { - SoundController.playSound(SoundEffect.GO); - countdownGoSound = true; + } else if (breakLength >= 4000) { + // show break end (flash twice for 500ms) + int endTimeDiff = endTime - trackPosition; + if ((endTimeDiff > 1500 && endTimeDiff < 2000) || + (endTimeDiff > 500 && endTimeDiff < 1000)) { + Image arrow = GameImage.WARNINGARROW.getImage(); + arrow.setRotation(0); + arrow.draw(width * 0.15f, height * 0.15f); + arrow.draw(width * 0.15f, height * 0.75f); + arrow.setRotation(180); + arrow.draw(width * 0.75f, height * 0.15f); + arrow.draw(width * 0.75f, height * 0.75f); } } } - // draw hit objects in reverse order, or else overlapping objects are unreadable - Stack stack = new Stack(); - for (int i = objectIndex; i < hitObjects.length && osu.objects[i].getTime() < trackPosition + approachTime; i++) - stack.add(i); + // non-break + else { + // game elements + data.drawGameElements(g, false, objectIndex == 0); + + // skip beginning + if (objectIndex == 0 && + trackPosition < osu.objects[0].getTime() - SKIP_OFFSET) + skipButton.draw(); - while (!stack.isEmpty()) - hitObjects[stack.pop()].draw(g, trackPosition); + // show retries + if (retries >= 2 && timeDiff >= -1000) { + int retryHeight = Math.max( + GameImage.SCOREBAR_BG.getImage().getHeight(), + GameImage.SCOREBAR_KI.getImage().getHeight() + ); + float oldAlpha = Utils.COLOR_WHITE_FADE.a; + if (timeDiff < -500) + Utils.COLOR_WHITE_FADE.a = (1000 + timeDiff) / 500f; + Utils.FONT_MEDIUM.drawString( + 2 + (width / 100), retryHeight, + String.format("%d retries and counting...", retries), + Utils.COLOR_WHITE_FADE + ); + Utils.COLOR_WHITE_FADE.a = oldAlpha; + } - // draw OsuHitObjectResult objects - data.drawHitResults(trackPosition); + if (isLeadIn()) + trackPosition = (leadInTime - Options.getMusicOffset()) * -1; // render approach circles during song lead-in + + // countdown + if (osu.countdown > 0) { // TODO: implement half/double rate settings + timeDiff = firstObjectTime - trackPosition; + if (timeDiff >= 500 && timeDiff < 3000) { + if (timeDiff >= 1500) { + GameImage.COUNTDOWN_READY.getImage().drawCentered(width / 2, height / 2); + if (!countdownReadySound) { + SoundController.playSound(SoundEffect.READY); + countdownReadySound = true; + } + } + if (timeDiff < 2000) { + GameImage.COUNTDOWN_3.getImage().draw(0, 0); + if (!countdown3Sound) { + SoundController.playSound(SoundEffect.COUNT3); + countdown3Sound = true; + } + } + if (timeDiff < 1500) { + GameImage.COUNTDOWN_2.getImage().draw(width - GameImage.COUNTDOWN_2.getImage().getWidth(), 0); + if (!countdown2Sound) { + SoundController.playSound(SoundEffect.COUNT2); + countdown2Sound = true; + } + } + if (timeDiff < 1000) { + GameImage.COUNTDOWN_1.getImage().drawCentered(width / 2, height / 2); + if (!countdown1Sound) { + SoundController.playSound(SoundEffect.COUNT1); + countdown1Sound = true; + } + } + } else if (timeDiff >= -500 && timeDiff < 500) { + Image go = GameImage.COUNTDOWN_GO.getImage(); + go.setAlpha((timeDiff < 0) ? 1 - (timeDiff / -1000f) : 1); + go.drawCentered(width / 2, height / 2); + if (!countdownGoSound) { + SoundController.playSound(SoundEffect.GO); + countdownGoSound = true; + } + } + } + + // draw hit objects + if (!GameMod.FLASHLIGHT.isActive()) + drawHitObjects(g, trackPosition); + } if (GameMod.AUTO.isActive()) GameImage.UNRANKED.getImage().drawCentered(width / 2, height * 0.077f); @@ -434,6 +480,62 @@ public class Game extends BasicGameState { mouseY = replayY; } skipButton.hoverUpdate(delta, mouseX, mouseY); + int trackPosition = MusicController.getPosition(); + + // "flashlight" mod: calculate visible area radius + if (GameMod.FLASHLIGHT.isActive()) { + int width = container.getWidth(), height = container.getHeight(); + boolean firstObject = (objectIndex == 0 && trackPosition < osu.objects[0].getTime()); + if (isLeadIn()) { + // lead-in: expand area + float progress = Math.max((float) (leadInTime - osu.audioLeadIn) / approachTime, 0f); + flashlightRadius = (int) (width / (1 + progress)); + } else if (firstObject) { + // before first object: shrink area + int timeDiff = osu.objects[0].getTime() - trackPosition; + flashlightRadius = width; + if (timeDiff < approachTime) { + float progress = (float) timeDiff / approachTime; + flashlightRadius -= (width - (height * 2 / 3)) * (1 - progress); + } + } else { + // gameplay: size based on combo + int targetRadius; + int combo = data.getComboStreak(); + if (combo < 100) + targetRadius = height * 2 / 3; + else if (combo < 200) + targetRadius = height / 2; + else + targetRadius = height / 3; + if (osu.breaks != null && breakIndex < osu.breaks.size() && breakTime > 0) { + // breaks: expand at beginning, shrink at end + flashlightRadius = targetRadius; + int endTime = osu.breaks.get(breakIndex); + int breakLength = endTime - breakTime; + if (breakLength > approachTime * 3) { + float progress = 1f; + if (trackPosition - breakTime < approachTime) + progress = (float) (trackPosition - breakTime) / approachTime; + else if (endTime - trackPosition < approachTime) + progress = (float) (endTime - trackPosition) / approachTime; + flashlightRadius += (width - flashlightRadius) * progress; + } + } else if (flashlightRadius != targetRadius) { + // radius size change + float radiusDiff = height * delta / 2000f; + if (flashlightRadius > targetRadius) { + flashlightRadius -= radiusDiff; + if (flashlightRadius < targetRadius) + flashlightRadius = targetRadius; + } else { + flashlightRadius += radiusDiff; + if (flashlightRadius > targetRadius) + flashlightRadius = targetRadius; + } + } + } + } if (isLeadIn()) { // stop updating during song lead-in leadInTime -= delta; @@ -515,8 +617,6 @@ public class Game extends BasicGameState { return; } - int trackPosition = MusicController.getPosition(); - // timing points if (timingPointIndex < osu.timingPoints.size()) { OsuTimingPoint timingPoint = osu.timingPoints.get(timingPointIndex); @@ -997,6 +1097,24 @@ public class Game extends BasicGameState { } } + /** + * Draws hit objects and hit results. + * @param g the graphics context + * @param trackPosition the track position + */ + private void drawHitObjects(Graphics g, int trackPosition) { + // draw hit objects in reverse order, or else overlapping objects are unreadable + Stack stack = new Stack(); + for (int i = objectIndex; i < hitObjects.length && osu.objects[i].getTime() < trackPosition + approachTime; i++) + stack.add(i); + + while (!stack.isEmpty()) + hitObjects[stack.pop()].draw(g, trackPosition); + + // draw OsuHitObjectResult objects + data.drawHitResults(trackPosition); + } + /** * Loads all required data from an OsuFile. * @param osu the OsuFile to load