From 62fd574ce5d1f9f5945ee8c73d3641bafce69b3d Mon Sep 17 00:00:00 2001 From: Leo Kim <47556641+KimJeongSun@users.noreply.github.com> Date: Wed, 24 Jul 2024 12:33:14 +0900 Subject: [PATCH] [QoL] New Starter Select UI with Filter (#2916) * update images for new UI * add updated starter UI with filter code * update starter-select test code * update win filter condition to pass test * remove unnecessary console log * update test code to match current filter UI * merge update * apply bugfix & chrry-pick small issues fix which are handled beta branch * resolve conflicts * fix lint errors * Fixed a bug where the target location for escaping using the left and right buttons on the starter button did not account for scrolling * update filter bar label color change when activated * fix lint error * fix lint * fix octolock.text.ts. it looks override import error. idk why it is happend in this PR. but it looks ok now * add passive dropdown in unlocks filter * fix lint * fix double button sound bug. refactoring genSpecies -> allSpecies, starterContainers -> starterContainer which are remove unnecessary generation axis * optimize updateStarterValueLabel function which is bottleneck of UI update latency * apply translation of gen filter label. fix lint * add # candies sort option * merge beta * resolve confilcts * fix offset of starter and start cursor * make compatible with starter UI * add missing feature * add images for legacy UI. adjust the position and size of the starterContainerWindow --- public/images/ui/legacy/scroll_bar.png | Bin 0 -> 143 bytes public/images/ui/legacy/scroll_bar_handle.png | Bin 0 -> 108 bytes .../images/ui/legacy/starter_container_bg.png | Bin 0 -> 2179 bytes public/images/ui/scroll_bar.png | Bin 0 -> 143 bytes public/images/ui/scroll_bar_handle.png | Bin 0 -> 108 bytes public/images/ui/starter_container_bg.png | Bin 0 -> 2179 bytes public/images/ui/starter_select_bg.png | Bin 2828 -> 3265 bytes src/loading-scene.ts | 3 + src/test/ui/starter-select.test.ts | 26 +- .../utils/mocks/mocksContainer/mockSprite.ts | 4 + src/ui/dropdown.ts | 253 +++ src/ui/filter-bar.ts | 201 +++ src/ui/scroll-bar.ts | 36 + src/ui/starter-container.ts | 101 ++ src/ui/starter-select-ui-handler.ts | 1379 +++++++++-------- 15 files changed, 1311 insertions(+), 692 deletions(-) create mode 100644 public/images/ui/legacy/scroll_bar.png create mode 100644 public/images/ui/legacy/scroll_bar_handle.png create mode 100644 public/images/ui/legacy/starter_container_bg.png create mode 100644 public/images/ui/scroll_bar.png create mode 100644 public/images/ui/scroll_bar_handle.png create mode 100644 public/images/ui/starter_container_bg.png create mode 100644 src/ui/dropdown.ts create mode 100644 src/ui/filter-bar.ts create mode 100644 src/ui/scroll-bar.ts create mode 100644 src/ui/starter-container.ts diff --git a/public/images/ui/legacy/scroll_bar.png b/public/images/ui/legacy/scroll_bar.png new file mode 100644 index 0000000000000000000000000000000000000000..e4e5eb34dd2cd7701bc322b8ab3db55524bd5c06 GIT binary patch literal 143 zcmeAS@N?(olHy`uVBq!ia0vp^tUx@QgAGWoVh+3pq&N#aB8wRqxP?KOkzv*x380{( zr;B5VN9Wt?3waM1h%g8K*NT>|p4anYLQ|NQ&YtG{=Goge#okq&nYz0=-}cqgeg^>- kM=+{CGBrhIFW*y!+qr_5gznb)0S#mDboFyt=akR{001B=*8l(j literal 0 HcmV?d00001 diff --git a/public/images/ui/legacy/scroll_bar_handle.png b/public/images/ui/legacy/scroll_bar_handle.png new file mode 100644 index 0000000000000000000000000000000000000000..95a1726b3bad26fd8b223039b66bc3f1fb32a9cb GIT binary patch literal 108 zcmeAS@N?(olHy`uVBq!ia0vp^%s|YEal|F_ZNmBO?O?$Ds|kTjmJ-%e#I}g<;MqGe)`mHypNRc2C~|RWf+G`njxgN@xNA DF!&jm literal 0 HcmV?d00001 diff --git a/public/images/ui/legacy/starter_container_bg.png b/public/images/ui/legacy/starter_container_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..5b60fcf551e5d1ca4abc1293c9fd60b34fe8793f GIT binary patch literal 2179 zcmeHJZBP_t9A5?%H3etrgHw8JOExuew=Z|xamyWu908+eh*yG>4tu)~uyFgr?!w*S zgvN|!6N5FTP0GXvN*YQqEe)L@9a9@ChiOX3ji!VmkuxJ3P1Nbxqf zd3JyQ-~atT`%=lu!ufL_nu{RF{Nf@{DSYmQt9|w?cz=0u`H%21M=mN?5#)hXVQOe^!Qpjk<l@7VFC@&57#%mlyqY$mujpZ2% z(K2rdnlC8;by^5BOAsXLa9UV`B5Zyt1EmPciW3x0IL(BertCCnLt_^P)hS#hUFvx% zrVO6kShc3fG>!*@K}*nTk(4T&bUK{|2Su47!mNfxjR~1WHGPV~15{SwWsR3a)L>-% zQoZKJpsABS2y&cORAX_15yL}_jFT3^@F@!9*f>tER{~Mx9E*bh5P+zu5KG3fa zz-W@Tt@Gu&1V=!wrm@hMF^HBtCv(q;6NrIvY zViM|=IKDDG9W*nGChd1YVa;%i#@rLkv2>-R2n=+X7nmx5%VHIV#=}VGO94rNilKGZ zs1;r>T`Z~^BeI~_`sax{M3}Z zM`G)Z%udO3|5E;Gg@>)h1n!&1XgH%J=ptT)$qL6hVl}9X?E*X+tpb{1jp}w|tdUm0 zVX@czeMexDHdqZH=|7}<5~fO(T98pdUKNbiJ5s4hMm; zjgKIBhz~I!!pWt;5mmhC<~jsP%r5ri`9f`xZ{FwAnFluybbdPX=8_e2YM5p3KD_3F ze_nFi?B|ZYMl4y8bYLKL|9cf3J1lL9&8JVznwfGjdu(ET=U06DNN?*&_NaC3&bPlD zQJ-&WG9~x)^i1fw-jSPnbNjVx*Baj#`Q!3#{ovP!yE9L`Bj{aSfr8;V9DZT(q1`UD)rD?x?Hg*? zo^<@xq~ot7HGC_MZ_4_g^gkZc>pPOzmd@*Q_C+>qXjv6GIdJU2;l4A=O}@cx>q8%q zCvHjSbw}DQwL*WUKjmQ8=$ZW)xuzDfX4jtOSC6G%o&TL_Y=6e@N>2Nwkn8r1PV&Uy z$d#gz-+GfL`p#a|&X0r}-g4~)<)cGG9ew@%DH{_q+3!c~rY&Q}Z$fdwN>BH)isnB7 DH|Q{f literal 0 HcmV?d00001 diff --git a/public/images/ui/scroll_bar.png b/public/images/ui/scroll_bar.png new file mode 100644 index 0000000000000000000000000000000000000000..e4e5eb34dd2cd7701bc322b8ab3db55524bd5c06 GIT binary patch literal 143 zcmeAS@N?(olHy`uVBq!ia0vp^tUx@QgAGWoVh+3pq&N#aB8wRqxP?KOkzv*x380{( zr;B5VN9Wt?3waM1h%g8K*NT>|p4anYLQ|NQ&YtG{=Goge#okq&nYz0=-}cqgeg^>- kM=+{CGBrhIFW*y!+qr_5gznb)0S#mDboFyt=akR{001B=*8l(j literal 0 HcmV?d00001 diff --git a/public/images/ui/scroll_bar_handle.png b/public/images/ui/scroll_bar_handle.png new file mode 100644 index 0000000000000000000000000000000000000000..95a1726b3bad26fd8b223039b66bc3f1fb32a9cb GIT binary patch literal 108 zcmeAS@N?(olHy`uVBq!ia0vp^%s|YEal|F_ZNmBO?O?$Ds|kTjmJ-%e#I}g<;MqGe)`mHypNRc2C~|RWf+G`njxgN@xNA DF!&jm literal 0 HcmV?d00001 diff --git a/public/images/ui/starter_container_bg.png b/public/images/ui/starter_container_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..5b60fcf551e5d1ca4abc1293c9fd60b34fe8793f GIT binary patch literal 2179 zcmeHJZBP_t9A5?%H3etrgHw8JOExuew=Z|xamyWu908+eh*yG>4tu)~uyFgr?!w*S zgvN|!6N5FTP0GXvN*YQqEe)L@9a9@ChiOX3ji!VmkuxJ3P1Nbxqf zd3JyQ-~atT`%=lu!ufL_nu{RF{Nf@{DSYmQt9|w?cz=0u`H%21M=mN?5#)hXVQOe^!Qpjk<l@7VFC@&57#%mlyqY$mujpZ2% z(K2rdnlC8;by^5BOAsXLa9UV`B5Zyt1EmPciW3x0IL(BertCCnLt_^P)hS#hUFvx% zrVO6kShc3fG>!*@K}*nTk(4T&bUK{|2Su47!mNfxjR~1WHGPV~15{SwWsR3a)L>-% zQoZKJpsABS2y&cORAX_15yL}_jFT3^@F@!9*f>tER{~Mx9E*bh5P+zu5KG3fa zz-W@Tt@Gu&1V=!wrm@hMF^HBtCv(q;6NrIvY zViM|=IKDDG9W*nGChd1YVa;%i#@rLkv2>-R2n=+X7nmx5%VHIV#=}VGO94rNilKGZ zs1;r>T`Z~^BeI~_`sax{M3}Z zM`G)Z%udO3|5E;Gg@>)h1n!&1XgH%J=ptT)$qL6hVl}9X?E*X+tpb{1jp}w|tdUm0 zVX@czeMexDHdqZH=|7}<5~fO(T98pdUKNbiJ5s4hMm; zjgKIBhz~I!!pWt;5mmhC<~jsP%r5ri`9f`xZ{FwAnFluybbdPX=8_e2YM5p3KD_3F ze_nFi?B|ZYMl4y8bYLKL|9cf3J1lL9&8JVznwfGjdu(ET=U06DNN?*&_NaC3&bPlD zQJ-&WG9~x)^i1fw-jSPnbNjVx*Baj#`Q!3#{ovP!yE9L`Bj{aSfr8;V9DZT(q1`UD)rD?x?Hg*? zo^<@xq~ot7HGC_MZ_4_g^gkZc>pPOzmd@*Q_C+>qXjv6GIdJU2;l4A=O}@cx>q8%q zCvHjSbw}DQwL*WUKjmQ8=$ZW)xuzDfX4jtOSC6G%o&TL_Y=6e@N>2Nwkn8r1PV&Uy z$d#gz-+GfL`p#a|&X0r}-g4~)<)cGG9ew@%DH{_q+3!c~rY&Q}Z$fdwN>BH)isnB7 DH|Q{f literal 0 HcmV?d00001 diff --git a/public/images/ui/starter_select_bg.png b/public/images/ui/starter_select_bg.png index f02182fe140984403ceb30cfe52f47a85c064afe..8b11f55a9636fedba7cc46a223eea893ef213b99 100644 GIT binary patch literal 3265 zcmbVP2~bn#7ETNX#6+sTDv6*mR7Hg3CIm|&h(G|5B1oykmhF;UNH8RqBtQbPL^0H& zve<`JDPk$=s}``@LMuq@6_pW?#R|@IA+53(s3?_9-XBD!?ex9QYi4qD{(HXvobRkN zIS{zrhipzY$Kh~f->v*091gz)$d}CKfoH5P${YMFK)3Ev;&2O>8ozkl@j^=+&SX#$ zEKmvj-{A@6aST`_k3tygI2539I2SiH3JZ55Dq0j0Bayk%hc4IBX%dkuJ;cc$@<%-p zv1Dtq0(m!id$2Hhw~#BMyKSbqsCmFZ9HN40>Nu%P$y2-1XYBHTY<$h6(`F#5-L7UkSk)CEH0O8w83UO0EB~5BU8a@2bt35wFN$+ z6e=XBN+Orhj27W2d4kH74yeA0Ar750D^t$q31p0^hEXPq0U2YO0g8lkI5a^aopCM_ zG7%{fhsab)fMv~LQL$VlSBmBT#q^x|cMd>q{r%^B{3(~XxH%U}l~*DNV>TgwidF_| zP=pzTDCG$XA>x$?XxbQ~L3y4E1Xjrv!E(9ub)^DdQ>L*w3=YjUL?RQ(la%X!a{=MQ zD#Vozs>T8Ja$vE7LDg8!Jjiha#O6WJE2zI*B#GAi0m=zxad>PF&&la`P|!0XSOxzj zSR~{{%N21j2v`ya#~@5p7DK1arIF_;m&z5uF<|F7!@}R6=POgHV3`o{<-5{B%nXS{ z#Dm%JMmE9%+&NJ!2R4fXIdGvU@ZdT@&K#JFa5=2k_xW;Rg0Zr%?~DG=`vD3G7%f=( zx9u1Q&e#&Xtr8_DmS%P$-bLbPC8>lq(*-7HFNXrMmOBbwjVd#hkgVJ>{lE|a+NCPe4ht|Hf-XpGLTi<*v4l7^*~%es2A`zK>vsHHov ztS-K9AYQ4dY5YpcJ~oi<%T2c2!A&5CzHB^qufawADRTRy+fbDC&azB0`=I)KWpiDU zZ@y?(VoJ(HZ`+xt=gKciV;Xqb)9($GEmnURFL zl6vAVX|pgx^n&T}DfctAjBh^pegVb1XAuW2K)P~{)L(M}z@_EXj*f=m*sm2eR{GNf za<%5jX>#|3$JyZ)2!l($5)fHOrr9_ZaH$3%wI@>n-bbZSl9{jwf$_X>TeX z+B0Htq&}zYt|7bb$Nt`hl3rxvNJHhZN9M!mvm-tAJHB&^k9yaj+0lg+Ju*l0rgu%( z;|fc(rGsHioS;OD&uP&IaiD*k`WsT6vqeL7k%lVxXhh$z;rN+XCPE3Gi>U%BXPYL% z&(@vb_nx-GEPR6(D9!-W@Rq(};+oo$Vv5t6mFR27?JR4i~b8%$YO^6R7U$mlwhMmnJ-u;kNvKFXEjOxB( z?Q=SI7C3!=M^B_&mvAZ)3s+rVz^Mo>Cri;Z%Nx!5v`FTmed5UVretX@JW!%d{eBGY zN&=Z`qIPrw4krMgbie@^;mRWSk+5QIs+TUWS+B680++?wDQ}&P55aGJEKuG!NL+-c z99~AYFD=uO*;ebQ&Q>C44-io+;DbuVoy};PvmH415jQ%!Sy@n>-x(;i0mard1A>R~ zWGi-)g{fNrc+sXOc4uKlXSCRja9$+15(5|-0$cPKXqfwF+R|z%aF~@vJiL&yh(xx3 z5>Hqma(;`09=wS{Af6#(Lf~TUPhL907J}dWR$zbBnryYH$s*+tvB3NbExE~JBAe)) zUvYHw?#_(m+2vps@_L^=dGKIU`?-o7(uCN$^D|qkG6EG%>$p6YkdcL*>N#e%q&v&l zE*G+EF8PQ%*Lu{D71M@J6U)1~hL?*YJNUX3?+lni8C;6jrHZQFHnIGahE37*J?4}_Jl;F&CfaTW zedndyr`$tSHYkr)pc%b>Fxo1U+ zB-;$KqG8{>5n1J~`;w+db)-t448szN>;Mj9vF{V{x)f2>1u&V9YE%1Ttv|Fn`9o_1 zPk$jbXziud7`bAZ}n=wmvTP#6{-emR_g-l?06 z9(#%P{B-nqca{*-M4r7qW@sCco;qcn(!10Qa@nHGp*#)|w(rJ$k!RRP?8^EvnX<)+ zT`doM85mk_a@Jdy$}}$r4*46p>SYw#FernP`Pu$=C(8mH}wu}z9w0j(ZU#w{&opjYa zeSGa`+p}F7+QmvI{GqzUv=>sBGTV+8+cntm*0I}8x)JP$8w>9FVay#<%BfX?;d?$1 ST7ftI$?)~s&M)(b-1{G0fIemb literal 2828 zcmb7GX;f0{8r~2H3L=Da`+C4eB}0379#AR;M~SqaCwh zIe=3RrBG&>sc2RQO+|Cgj+*FEIbwH{){ndHT6f*O)*jZkzwdjW_j#Z9**oQ^i-V$q zrUC!}iU%ES-2ebW2A?aDa^Q7jcpeAdAYpC})<9`HVFCaYiVxZzaF5LSvyT_FAxiV* zmzUj!x6Zl4VlP~Fufwxg$B_gK0^y?e=@%S^;vK!3bcz;V{t=>mYPjaqV z9d?+%kN0SJtUN7skdGgEFIc#EQX-8Tr00!^W7(q5;)S{=z?J7}Pe*2Ly3*Itk{VLu zLZc=54<|2##;rV><#pvb`I_f8yV)jA`xfQqCw{&3C6+A^v#hKh0i~3pXY@?pPP>i| zp}2%*8tHmLfu2U1z4J+h>w#s|=3vvClarIep)xr4kI6r0vmS+mJKsK$k7UtYFFhr* z_h0Op^99T_)9iO^rVV#j_?OvT-$;Y82L8Hw^{Sh;Qarho!)X#*%Eyy!6EdUx#WWID zAl@`FKJMuCB(iK}nKGvoQ{OK+ZK?G-cE4cx$-`@Pg6+~*?uiT6HrGVxu+56+MS+)m zJ}as<_`C(ex5p|bqE2sHicYKID`{(SzS1nUSjo-Z-I)X}t(HhjFM)H6R8syIy>Z0B z$As|FRJxqL^JJ-&Qao)4jxcExKM|gr`7-p9c*8B;Cl_L`O`n^5wv9MD{Ml)$zyA0{ zQ{4lx0m+uo?^QDyw0DJM>@cQ-+>NTE!n{f-lS;py45th7tkR2$ii9`WIk|=V{nND{ zlL_H3+m*6>>-UF6Fb0Vkg%3v~)gF^^;qj&MI>}9FKR@TN*?EnYvrKXOa$N_Z0$JC& zJ}M)#Yt*FHg73wtE<-H``J9{eM?_Sil)Z7Vo^2IywqT4n`vF5*wtP52iBwCw;67X5 z=v>Wpwz3LlE|sYTn5m;}pO?X19zAxhtTzg;MJaDz=pTI4g$>KdL^%u?<%}I;nxiXi z44^^04NmD!_q%#Iy3ln~p?cip=E1?~h-g=#NgjQ@Xd$=Z{ru#o=(t;n8I7$uYRm%8 zQW~5)Ce>ZShEF{^7o#PNjrmaV(pDnr=&t+7R?N{$tb052v5LEr zGHWlO42b0laV}D8ARs#p8M|;qWD>Ox`)iT*&SXBhDThe1AS)N@j*^Viukdpqfiy81 z%S}|J>Uz@hqbvD-!d^&V@=vX;m3&};@niVGZ!%m?s9<)RF`2LFNxK}tWPa*|W_yPr z9>_N9f>tw{{)RwRZBL3T`3jxTb?*w`_9Z6I34VT_G;uH+0)5{B)d!QkAr@EPSokoS z%x745(u|EZ)kM&y{&{XKs3GYIy>)t{T%bLwxZ~Y5HL!gslCBg3(TOVyE2lpZ9bt04 zozM+KNAB&;acTVw+p`uRE{IuT7|+-Y#=dNL!QGRv1*AEE*(O~6iHx#1AfqNzwWja} zOJ{lr-f{;{*%r*K7_Mq8P3DiU_Z+z0p_%l3t634M_!k_^KsKoa-o8Gq@%2bj7AF{w zQ>P^07Kev@r0N#r-+HIz@O|=_>Rx$Dh@AR2M#t<1X7&g)Xos;$%u@kOm6jl8Meqkq z7qsAT0P}e11lahiEP8#hur{klLT`hVR63(_nl@wb%+AeE)P$+CJpA31)rtSw0S%KA zYCZQJ@B`CQIm@i@Y4RqL>Jn6I3c{=KZ)GYw!)z;BS%Bh`n9kswW1Ud{gAEZG$kft& zxM_i8Bke(JOp-WYwV*Y)5cNg4A=mm?HQ%G9-y;i&265qT_R8O`&-(^VCcNs9A@tEg zNjq?}rT++)yCvF+43_&7NSAU-qb-eUjS7A-70$S`;|gw-RrSjqn1UcM-JWA$CVRk4 zyyXo^Im&(V-)dS&B>6KmKd+!kDdwq0`EbgE6jk%r&LJ7!qPGRaA=G>Pwyue&!0owr zr3whq8H9LaZI0`q(5)G<`Z?bv$TsHR9gazYyar>wc?7__I7Gy9W9j&?k?luupVB_3u`}UBprjN(ZOj(+Mr0tWM-c zNdy4X6a?99jUo`CjSAp?!BfDRG=K|aR58|&babC5?d>6(R&T$1a8tpkh7L^;gj~rd z&x4BN)~;Kt41^E)G3=_pN|w+v+n>SMv(_Rgy_r|9UP*R^Gj?>Z^<5OMMl{Vr`ZIvN zSN})*eewDMZqyBzVN3>29Qgn6J++4=YN?(gfyy4XA9xp7C1zR2d{avlPPibQCmaW2 z>e&D}+in`6&XT#kS-7ikPBQP?m42OtMC*s@fp&7j<%o8MIRbq~CbP@OY3%^yXL&s^{t(Jx~Y|ss%#UTBhW%4NkB`l}i41Ye55;0A^Z8G{*5ihfO^P9EL(} z{&7i}6SsKaEMB(m3~rG!hY~_Q`Hb)mV_}ocPg{%6x>STX1ONdQ<5IxPbKtzzzQEz% zumAb+oj&MocYBfOTX$qubso?301>Ll<~q;~WSCrw-gn{P)BysPsi*^8H*)!hL%7?^6w=P(OXI+wg@$A< z039b0xe!}gzU6oQfNxz5z*4jROJFXJc?a~IZCeeYbPXxAMC8NOP1R(N?J_RP?!q!5 zKT`~#>prZC6jWo^%hsGd_q`#o4i8(}Eo+dYT2QHpiTAE${uG2+YT&WK4EJsZ+nGo@ zr29?UKUUSM^jb78+kn*757Aeo;@Q5!$Z0Fni-x3Ru*hzDT&v8MnUyjw-qu6bDB^IO zIaPtB!!YWxFgIT`AbyOpnK-i5n()Fjna}t4a#d5H~Ti=6YQ`? zsw}(DVFIxaNxfJphq)zz^K|%QdTf1vq*;az^@@}1L*TO=jGfpWyrrNsU?@Z&cGE^= znPj(}%A<#jwdeAB=MvuSz%IRXSn7)qweVIpIG$gc-pf&XQA~*`r!H@*J}l^KWArP2l$KxPucP2 RA2(&UgLW>qrPj3ge*@%I8cqNJ diff --git a/src/loading-scene.ts b/src/loading-scene.ts index a422adda61d..61178144ded 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -146,6 +146,9 @@ export class LoadingScene extends SceneBase { this.loadImage(`summary_tabs_${t}`, "ui"); } + this.loadImage("scroll_bar", "ui"); + this.loadImage("scroll_bar_handle", "ui"); + this.loadImage("starter_container_bg", "ui"); this.loadImage("starter_select_bg", "ui"); this.loadImage("select_cursor", "ui"); this.loadImage("select_cursor_highlight", "ui"); diff --git a/src/test/ui/starter-select.test.ts b/src/test/ui/starter-select.test.ts index 2b5df315a70..95a4605fd68 100644 --- a/src/test/ui/starter-select.test.ts +++ b/src/test/ui/starter-select.test.ts @@ -54,6 +54,7 @@ describe("UI - Starter select", () => { game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; handler.processInput(Button.RIGHT); + handler.processInput(Button.LEFT); handler.processInput(Button.ACTION); game.phaseInterceptor.unlock(); }); @@ -113,6 +114,7 @@ describe("UI - Starter select", () => { game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; handler.processInput(Button.RIGHT); + handler.processInput(Button.LEFT); handler.processInput(Button.CYCLE_GENDER); handler.processInput(Button.ACTION); game.phaseInterceptor.unlock(); @@ -174,6 +176,7 @@ describe("UI - Starter select", () => { game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; handler.processInput(Button.RIGHT); + handler.processInput(Button.LEFT); handler.processInput(Button.CYCLE_GENDER); handler.processInput(Button.CYCLE_NATURE); handler.processInput(Button.CYCLE_ABILITY); @@ -237,6 +240,7 @@ describe("UI - Starter select", () => { game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; handler.processInput(Button.RIGHT); + handler.processInput(Button.LEFT); handler.processInput(Button.CYCLE_GENDER); handler.processInput(Button.ACTION); game.phaseInterceptor.unlock(); @@ -297,6 +301,7 @@ describe("UI - Starter select", () => { game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; handler.processInput(Button.RIGHT); + handler.processInput(Button.LEFT); handler.processInput(Button.CYCLE_SHINY); handler.processInput(Button.ACTION); game.phaseInterceptor.unlock(); @@ -356,6 +361,7 @@ describe("UI - Starter select", () => { game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; handler.processInput(Button.RIGHT); + handler.processInput(Button.LEFT); handler.processInput(Button.V); handler.processInput(Button.V); handler.processInput(Button.ACTION); @@ -416,6 +422,7 @@ describe("UI - Starter select", () => { game.onNextPrompt("SelectStarterPhase", Mode.STARTER_SELECT, () => { const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; handler.processInput(Button.RIGHT); + handler.processInput(Button.LEFT); handler.processInput(Button.V); handler.processInput(Button.V); handler.processInput(Button.V); @@ -479,7 +486,6 @@ describe("UI - Starter select", () => { handler.processInput(Button.RIGHT); handler.processInput(Button.RIGHT); handler.processInput(Button.RIGHT); - handler.processInput(Button.RIGHT); handler.processInput(Button.ACTION); game.phaseInterceptor.unlock(); }); @@ -509,10 +515,10 @@ describe("UI - Starter select", () => { }); }); - expect(starterSelectUiHandler.starterGens[0]).toBe(0); - expect(starterSelectUiHandler.starterCursors[0]).toBe(3); - expect(starterSelectUiHandler.cursorObj.x).toBe(132 + 4 * 18); - expect(starterSelectUiHandler.cursorObj.y).toBe(10); + // expect(starterSelectUiHandler.starterGens[0]).toBe(0); + // expect(starterSelectUiHandler.starterCursors[0]).toBe(3); + // expect(starterSelectUiHandler.cursorObj.x).toBe(132 + 4 * 18); + // expect(starterSelectUiHandler.cursorObj.y).toBe(10); game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; @@ -544,7 +550,6 @@ describe("UI - Starter select", () => { handler.processInput(Button.RIGHT); handler.processInput(Button.RIGHT); handler.processInput(Button.RIGHT); - handler.processInput(Button.RIGHT); handler.processInput(Button.DOWN); handler.processInput(Button.ACTION); game.phaseInterceptor.unlock(); @@ -575,10 +580,11 @@ describe("UI - Starter select", () => { }); }); - expect(starterSelectUiHandler.starterGens[0]).toBe(0); - expect(starterSelectUiHandler.starterCursors[0]).toBe(12); - expect(starterSelectUiHandler.cursorObj.x).toBe(132 + 4 * 18); - expect(starterSelectUiHandler.cursorObj.y).toBe(28); + expect(starterSelectUiHandler.starterSpecies.length).toBe(1); + expect(starterSelectUiHandler.starterSpecies[0].generation).toBe(1); + expect(starterSelectUiHandler.starterSpecies[0].speciesId).toBe(32); + expect(starterSelectUiHandler.cursorObj.x).toBe(53); + expect(starterSelectUiHandler.cursorObj.y).toBe(31); game.onNextPrompt("SelectStarterPhase", Mode.CONFIRM, () => { const handler = game.scene.ui.getHandler() as StarterSelectUiHandler; diff --git a/src/test/utils/mocks/mocksContainer/mockSprite.ts b/src/test/utils/mocks/mocksContainer/mockSprite.ts index 2eb77f81302..3a5de09b258 100644 --- a/src/test/utils/mocks/mocksContainer/mockSprite.ts +++ b/src/test/utils/mocks/mocksContainer/mockSprite.ts @@ -90,6 +90,10 @@ export default class MockSprite { return this.phaserSprite.setPosition(x, y); } + setRotation(radians) { + return this.phaserSprite.setRotation(radians); + } + stop() { return this.phaserSprite.stop(); } diff --git a/src/ui/dropdown.ts b/src/ui/dropdown.ts new file mode 100644 index 00000000000..77f4ef91ecb --- /dev/null +++ b/src/ui/dropdown.ts @@ -0,0 +1,253 @@ +import BattleScene from "#app/battle-scene.js"; +import { SceneBase } from "#app/scene-base.js"; +import { addTextObject, TextStyle } from "./text"; +import { addWindow, WindowVariant } from "./ui-theme"; + +export enum DropDownState { + ON = 0, + OFF +} + +export enum DropDownType { + MULTI = 0, + SINGLE +} + +export enum SortDirection { + ASC = -1, + DESC = 1 +} + +export class DropDownOption extends Phaser.GameObjects.Container { + public state: DropDownState = DropDownState.ON; + public toggle: Phaser.GameObjects.Sprite; + public text: Phaser.GameObjects.Text; + public sprite?: Phaser.GameObjects.Sprite; + public val: any; + public dir: SortDirection = SortDirection.ASC; + + constructor(scene: SceneBase, val: any, text: string, sprite?: Phaser.GameObjects.Sprite, state: DropDownState = DropDownState.ON) { + super(scene); + this.val = val; + if (text) { + this.text = addTextObject(scene, 0, 0, text, TextStyle.TOOLTIP_CONTENT); + this.text.setOrigin(0, 0.5); + this.add(this.text); + } + + if (sprite) { + this.sprite = sprite.setOrigin(0, 0.5); + this.add(this.sprite); + } + this.state = state; + } + + public setupToggle(type: DropDownType): void { + if (type === DropDownType.MULTI) { + this.toggle = this.scene.add.sprite(0, 0, "candy"); + this.toggle.setScale(0.3); + this.toggle.setOrigin(0, 0.5); + } else { + this.toggle = this.scene.add.sprite(0, 0, "cursor"); + this.toggle.setScale(0.5); + this.toggle.setOrigin(0, 0.5); + this.toggle.setRotation(Math.PI / 180 * -90); + } + this.add(this.toggle); + } + + public setOptionState(state: DropDownState): DropDownState { + this.state = state % 2; + if (this.state === DropDownState.OFF) { + this.toggle.setTint(0x272727); + } else { + this.toggle.setTint(0x55ff55); + } + return this.state; + } + + public toggleOptionState(): DropDownState { + return this.setOptionState(this.state + 1); + } + + public setDirection(dir: SortDirection): void { + this.dir = dir; + this.toggle.flipX = this.dir === SortDirection.DESC; + } + + public toggleDirection(): void { + this.setDirection(this.dir * -1); + } +} + +export class DropDown extends Phaser.GameObjects.Container { + public options: DropDownOption[]; + private window: Phaser.GameObjects.NineSlice; + private cursorObj: Phaser.GameObjects.Image; + private dropDownType: DropDownType = DropDownType.MULTI; + public cursor: integer = 0; + private onChange: () => void; + private lastDir: SortDirection = SortDirection.ASC; + + constructor(scene: BattleScene, x: number, y: number, options: DropDownOption[], onChange: () => void, type: DropDownType = DropDownType.MULTI, optionSpacing: number = 2) { + const windowPadding = 5; + const optionHeight = 7; + const optionPaddingX = 4; + const optionPaddingY = 6; + const cursorOffset = 7; + const optionWidth = 100; + + super(scene, x - cursorOffset - windowPadding, y); + this.options = options; + this.dropDownType = type; + this.onChange = onChange; + + this.cursorObj = scene.add.image(optionPaddingX + 3, 0, "cursor"); + this.cursorObj.setScale(0.5); + this.cursorObj.setOrigin(0, 0.5); + this.cursorObj.setVisible(false); + + if (this.dropDownType === DropDownType.MULTI) { + this.options.unshift(new DropDownOption(scene, "ALL", "All", null, this.checkForAllOn() ? DropDownState.ON : DropDownState.OFF)); + } + + options.forEach((option, index) => { + option.setupToggle(type); + if (type === DropDownType.SINGLE && option.state === DropDownState.OFF) { + option.toggle.setVisible(false); + } + option.setOptionState(option.state); + + option.width = optionWidth; + option.y = index * optionHeight + index * optionSpacing + optionPaddingY; + + if (option.text) { + option.text.x = cursorOffset + optionPaddingX + 3 + 8; + option.text.y = optionHeight / 2; + } + if (option.sprite) { + option.sprite.x = cursorOffset + optionPaddingX + 3 + 8; + option.sprite.y = optionHeight / 2; + } + option.toggle.x = cursorOffset + optionPaddingX + 3 + (type === DropDownType.MULTI ? 0 : 3); + option.toggle.y = optionHeight / 2 + (type === DropDownType.MULTI ? 0 : 1); + }); + this.window = addWindow(scene, 0, 0, optionWidth, options[options.length - 1].y + optionHeight + optionPaddingY, false, false, null, null, WindowVariant.XTHIN); + this.add(this.window); + this.add(options); + this.add(this.cursorObj); + this.setVisible(false); + } + + toggle(): void { + this.setVisible(!this.visible); + } + + setCursor(cursor: integer): boolean { + this.cursor = cursor; + if (cursor < 0) { + cursor = 0; + this.cursorObj.setVisible(false); + return false; + } else if (cursor >= this.options.length) { + cursor = this.options.length - 1; + this.cursorObj.y = this.options[cursor].y + 3.5; + this.cursorObj.setVisible(true); + return false; + } else { + this.cursorObj.y = this.options[cursor].y + 3.5; + this.cursorObj.setVisible(true); + } + return true; + } + + toggleOptionState(): void { + if (this.dropDownType === DropDownType.MULTI) { + const newState = this.options[this.cursor].toggleOptionState(); + + if (this.cursor === 0) { + this.options.forEach((option, index) => { + if (index !== this.cursor) { + option.setOptionState(newState); + } + }); + } else { + if (this.checkForAllOff()) { + this.options[0].setOptionState(DropDownState.OFF); + } else if (this.checkForAllOn()) { + this.options[0].setOptionState(DropDownState.ON); + } + } + } else { + if (this.options[this.cursor].state === DropDownState.OFF) { + this.options.forEach((option) => { + option.setOptionState(DropDownState.OFF); + option.setDirection(SortDirection.ASC); + option.toggle.setVisible(false); + }); + this.options[this.cursor].setOptionState(DropDownState.ON); + this.options[this.cursor].setDirection(this.lastDir); + this.options[this.cursor].toggle.setVisible(true); + } else { + this.options[this.cursor].toggleDirection(); + this.lastDir = this.options[this.cursor].dir; + } + } + this.onChange(); + } + + setVisible(value: boolean): this { + super.setVisible(value); + + if (value) { + this.autoSize(); + } + + return this; + } + + checkForAllOn(): boolean { + return this.options.every((option, i) => i === 0 || option.state === DropDownState.ON); + } + + checkForAllOff(): boolean { + return this.options.every((option, i) => i === 0 || option.state === DropDownState.OFF); + } + + getVals(): any[] { + if (this.dropDownType === DropDownType.MULTI) { + return this.options.filter((option, i) => i > 0 && option.state === DropDownState.ON).map((option) => option.val); + } else { + return this.options.filter((option, i) => option.state === DropDownState.ON).map((option) => { + return {val: option.val, dir: option.dir}; + }); + } + } + + autoSize(): void { + let maxWidth = 0; + let x = 0; + for (let i = 0; i < this.options.length; i++) { + if (this.options[i].sprite) { + if (this.options[i].sprite.displayWidth > maxWidth) { + maxWidth = this.options[i].sprite.displayWidth; + x = this.options[i].sprite.x; + } + } else { + if (this.options[i].text.displayWidth > maxWidth) { + maxWidth = this.options[i].text.displayWidth; + x = this.options[i].text.x; + } + } + } + this.window.width = maxWidth + x - this.window.x + 6; + + if (this.x + this.window.width > this.parentContainer.width) { + this.x = this.parentContainer.width - this.window.width; + } + } + + isActive(): boolean { + return this.options.some((option) => option.state === DropDownState.ON); + } +} diff --git a/src/ui/filter-bar.ts b/src/ui/filter-bar.ts new file mode 100644 index 00000000000..cc4723799d5 --- /dev/null +++ b/src/ui/filter-bar.ts @@ -0,0 +1,201 @@ +import BattleScene from "#app/battle-scene.js"; +import { DropDown } from "./dropdown"; +import { StarterContainer } from "./starter-container"; +import { addTextObject, TextStyle } from "./text"; +import { addWindow, WindowVariant } from "./ui-theme"; + +export enum DropDownColumn { + GEN, + TYPES, + UNLOCKS, + WIN, + SORT +} + +export class FilterBar extends Phaser.GameObjects.Container { + private window: Phaser.GameObjects.NineSlice; + public labels: Phaser.GameObjects.Text[] = []; + public dropDowns: DropDown[] = []; + public cursorObj: Phaser.GameObjects.Image; + public numFilters: number = 0; + public openDropDown: boolean = false; + private lastCursor: number = -1; + public defaultGenVals: any[] = []; + public defaultTypeVals: any[] = []; + public defaultUnlockVals: any[] = []; + public defaultWinVals: any[] = []; + public defaultSortVals: any[] = []; + + constructor(scene: BattleScene, x: number, y: number, width: number, height: number) { + super(scene, x, y); + + this.width = width; + this.height = height; + + this.window = addWindow(scene, 0, 0, width, height, false, false, null, null, WindowVariant.THIN); + this.add(this.window); + + this.cursorObj = this.scene.add.image(1, 1, "cursor"); + this.cursorObj.setScale(0.5); + this.cursorObj.setVisible(false); + this.cursorObj.setOrigin(0, 0); + this.add(this.cursorObj); + } + + addFilter(text: string, dropDown: DropDown): void { + const filterTypesLabel = addTextObject(this.scene, 0, 3, text, TextStyle.TOOLTIP_CONTENT); + this.labels.push(filterTypesLabel); + this.add(filterTypesLabel); + this.dropDowns.push(dropDown); + this.add(dropDown); + + this.calcFilterPositions(); + this.numFilters++; + } + + + updateFilterLabels(): void { + const genVals = this.getVals(DropDownColumn.GEN); + const typeVals = this.getVals(DropDownColumn.TYPES); + const unlockVals = this.getVals(DropDownColumn.UNLOCKS); + const winVals = this.getVals(DropDownColumn.WIN); + const sortVals = this.getVals(DropDownColumn.SORT); + + // onColor is Yellow, offColor is White + const onColor = 0xffef5c; + const offColor = 0xffffff; + + // if genVals and defaultGenVals has same elements, set the label to White else set it to Green + if (genVals.length === this.defaultGenVals.length && genVals.every((value, index) => value === this.defaultGenVals[index])) { + this.labels[DropDownColumn.GEN].setTint(offColor); + } else { + this.labels[DropDownColumn.GEN].setTint(onColor); + } + + // if typeVals and defaultTypeVals has same elements, set the label to White else set it to Green + if (typeVals.length === this.defaultTypeVals.length && typeVals.every((value, index) => value === this.defaultTypeVals[index])) { + this.labels[DropDownColumn.TYPES].setTint(offColor); + } else { + this.labels[DropDownColumn.TYPES].setTint(onColor); + } + + // if unlockVals and defaultUnlockVals has same elements, set the label to White else set it to Green + if (unlockVals.length === this.defaultUnlockVals.length && unlockVals.every((value, index) => value === this.defaultUnlockVals[index])) { + this.labels[DropDownColumn.UNLOCKS].setTint(offColor); + } else { + this.labels[DropDownColumn.UNLOCKS].setTint(onColor); + } + + // if winVals and defaultWinVals has same elements, set the label to White else set it to Green + if (winVals.length === this.defaultWinVals.length && winVals.every((value, index) => value === this.defaultWinVals[index])) { + this.labels[DropDownColumn.WIN].setTint(offColor); + } else { + this.labels[DropDownColumn.WIN].setTint(onColor); + } + + // if sortVals and defaultSortVals has same value and dir, set the label to White else set it to Green + if (sortVals[0]["dir"] === this.defaultSortVals[0]["dir"] && sortVals[0]["val"] === this.defaultSortVals[0]["val"]) { + this.labels[DropDownColumn.SORT].setTint(offColor); + } else { + this.labels[DropDownColumn.SORT].setTint(onColor); + } + } + + calcFilterPositions(): void { + const paddingX = 6; + const cursorOffset = 8; + + // position labels with even space across the width of the container + let totalWidth = paddingX * 2 + cursorOffset; + this.labels.forEach(label => { + totalWidth += label.displayWidth + cursorOffset; + }); + const spacing = (this.width - totalWidth) / (this.labels.length - 1); + for (let i=0; i -1) { + if (this.dropDowns[this.lastCursor].visible) { + this.dropDowns[this.lastCursor].setVisible(false); + this.dropDowns[cursor].setVisible(true); + this.dropDowns[cursor].setCursor(0); + } + } + + const cursorOffset = 8; + this.cursorObj.setPosition(this.labels[cursor].x - cursorOffset + 2, 6); + this.lastCursor = cursor; + } + + toggleDropDown(index: number): void { + this.dropDowns[index].toggle(); + this.openDropDown = this.dropDowns[index].visible; + this.dropDowns[index].setCursor(0); + } + + hideDropDowns(): void { + this.dropDowns.forEach(dropDown => { + dropDown.setVisible(false); + }); + this.openDropDown = false; + } + + incDropDownCursor(): boolean { + if (this.dropDowns[this.lastCursor].cursor === this.dropDowns[this.lastCursor].options.length - 1) {// if at the bottom of the list, wrap around + return this.dropDowns[this.lastCursor].setCursor(0); + } else { + return this.dropDowns[this.lastCursor].setCursor(this.dropDowns[this.lastCursor].cursor + 1); + } + } + + decDropDownCursor(): boolean { + if (this.dropDowns[this.lastCursor].cursor === 0) {// if at the top of the list, wrap around + return this.dropDowns[this.lastCursor].setCursor(this.dropDowns[this.lastCursor].options.length - 1); + } else { + return this.dropDowns[this.lastCursor].setCursor(this.dropDowns[this.lastCursor].cursor - 1); + } + } + + toggleOptionState(): void { + this.dropDowns[this.lastCursor].toggleOptionState(); + } + + getVals(col: DropDownColumn): any[] { + return this.dropDowns[col].getVals(); + } + + getNearestFilter(container: StarterContainer): number { + // find the nearest filter to the x position + const midx = container.x + container.icon.displayWidth / 2; + let nearest = 0; + let nearestDist = 1000; + for (let i=0; i < this.labels.length; i++) { + const dist = Math.abs(midx - (this.labels[i].x + this.labels[i].displayWidth / 3)); + if (dist < nearestDist) { + nearest = i; + nearestDist = dist; + } + } + + return nearest; + } + + getLastFilterX(): number { + return this.labels[this.lastCursor].x + this.labels[this.lastCursor].displayWidth / 2; + } + + isFilterActive(index: number) { + return this.dropDowns[index].isActive(); + } +} diff --git a/src/ui/scroll-bar.ts b/src/ui/scroll-bar.ts new file mode 100644 index 00000000000..02282edb4cd --- /dev/null +++ b/src/ui/scroll-bar.ts @@ -0,0 +1,36 @@ +export class ScrollBar extends Phaser.GameObjects.Container { + private bg: Phaser.GameObjects.Image; + private handleBody: Phaser.GameObjects.Rectangle; + private handleBottom: Phaser.GameObjects.Image; + private pages: number; + private page: number; + + constructor(scene: Phaser.Scene, x: number, y: number, pages: number) { + super(scene, x, y); + + this.bg = scene.add.image(0, 0, "scroll_bar"); + this.bg.setOrigin(0, 0); + this.add(this.bg); + + this.handleBody = scene.add.rectangle(1, 1, 3, 4, 0xaaaaaa); + this.handleBody.setOrigin(0, 0); + this.add(this.handleBody); + + this.handleBottom = scene.add.image(1, 1, "scroll_bar_handle"); + this.handleBottom.setOrigin(0, 0); + this.add(this.handleBottom); + } + + setPage(page: number): void { + this.page = page; + this.handleBody.y = 1 + (this.bg.displayHeight - 1 - this.handleBottom.displayHeight) / this.pages * page; + this.handleBottom.y = this.handleBody.y + this.handleBody.displayHeight; + } + + setPages(pages: number): void { + this.pages = pages; + this.handleBody.height = (this.bg.displayHeight - 1 - this.handleBottom.displayHeight) / this.pages; + + this.setVisible(this.pages > 1); + } +} diff --git a/src/ui/starter-container.ts b/src/ui/starter-container.ts new file mode 100644 index 00000000000..ae589d138b9 --- /dev/null +++ b/src/ui/starter-container.ts @@ -0,0 +1,101 @@ +import BattleScene from "../battle-scene"; +import PokemonSpecies from "../data/pokemon-species"; +import { addTextObject, TextStyle } from "./text"; + +export class StarterContainer extends Phaser.GameObjects.Container { + public scene: BattleScene; + public species: PokemonSpecies; + public icon: Phaser.GameObjects.Sprite; + public shinyIcons: Phaser.GameObjects.Image[] = []; + public label: Phaser.GameObjects.Text; + public starterPassiveBgs: Phaser.GameObjects.Image; + public hiddenAbilityIcon: Phaser.GameObjects.Image; + public classicWinIcon: Phaser.GameObjects.Image; + public candyUpgradeIcon: Phaser.GameObjects.Image; + public candyUpgradeOverlayIcon: Phaser.GameObjects.Image; + public cost: number = 0; + + constructor(scene: BattleScene, species: PokemonSpecies) { + super(scene, 0, 0); + + this.species = species; + + const defaultDexAttr = scene.gameData.getSpeciesDefaultDexAttr(species, false, true); + const defaultProps = scene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); + + // starter passive bg + const starterPassiveBg = this.scene.add.image(2, 5, "passive_bg"); + starterPassiveBg.setOrigin(0, 0); + starterPassiveBg.setScale(0.75); + starterPassiveBg.setVisible(false); + this.add(starterPassiveBg); + this.starterPassiveBgs = starterPassiveBg; + + // icon + this.icon = this.scene.add.sprite(-2, 2, species.getIconAtlasKey(defaultProps.formIndex, defaultProps.shiny, defaultProps.variant)); + this.icon.setScale(0.5); + this.icon.setOrigin(0, 0); + this.icon.setFrame(species.getIconId(defaultProps.female, defaultProps.formIndex, defaultProps.shiny, defaultProps.variant)); + this.checkIconId(defaultProps.female, defaultProps.formIndex, defaultProps.shiny, defaultProps.variant); + this.icon.setTint(0); + this.add(this.icon); + + // shiny icons + for (let i = 0; i < 3; i++) { + const shinyIcon = this.scene.add.image(i * -3 + 12, 2, "shiny_star_small"); + shinyIcon.setScale(0.5); + shinyIcon.setOrigin(0, 0); + shinyIcon.setVisible(false); + this.shinyIcons.push(shinyIcon); + } + this.add(this.shinyIcons); + + // value label + const label = addTextObject(this.scene, 1, 2, "0", TextStyle.WINDOW, { fontSize: "32px" }); + label.setShadowOffset(2, 2); + label.setOrigin(0, 0); + label.setVisible(false); + this.add(label); + this.label = label; + + // hidden ability icon + const abilityIcon = this.scene.add.image(12, 7, "ha_capsule"); + abilityIcon.setOrigin(0, 0); + abilityIcon.setScale(0.5); + abilityIcon.setVisible(false); + this.add(abilityIcon); + this.hiddenAbilityIcon = abilityIcon; + + // classic win icon + const classicWinIcon = this.scene.add.image(2, 12, "champion_ribbon"); + classicWinIcon.setOrigin(0, 0); + classicWinIcon.setScale(0.5); + classicWinIcon.setVisible(false); + this.add(classicWinIcon); + this.classicWinIcon = classicWinIcon; + + // candy upgrade icon + const candyUpgradeIcon = this.scene.add.image(12, 12, "candy"); + candyUpgradeIcon.setOrigin(0, 0); + candyUpgradeIcon.setScale(0.25); + candyUpgradeIcon.setVisible(false); + this.add(candyUpgradeIcon); + this.candyUpgradeIcon = candyUpgradeIcon; + + // candy upgrade overlay icon + const candyUpgradeOverlayIcon = this.scene.add.image(12, 12, "candy_overlay"); + candyUpgradeOverlayIcon.setOrigin(0, 0); + candyUpgradeOverlayIcon.setScale(0.25); + candyUpgradeOverlayIcon.setVisible(false); + this.add(candyUpgradeOverlayIcon); + this.candyUpgradeOverlayIcon = candyUpgradeOverlayIcon; + } + + checkIconId(female, formIndex, shiny, variant) { + if (this.icon.frame.name !== this.species.getIconId(female, formIndex, shiny, variant)) { + console.log(`${this.species.name}'s variant icon does not exist. Replacing with default.`); + this.icon.setTexture(this.species.getIconAtlasKey(formIndex, false, variant)); + this.icon.setFrame(this.species.getIconId(female, formIndex, false, variant)); + } + } +} diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 048f772390e..2300463e2cd 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -40,6 +40,10 @@ import { Species } from "#enums/species"; import {Button} from "#enums/buttons"; import { EggSourceType } from "#app/enums/egg-source-types.js"; import AwaitableUiHandler from "./awaitable-ui-handler"; +import { DropDown, DropDownOption, DropDownState, DropDownType } from "./dropdown"; +import { StarterContainer } from "./starter-container"; +import { DropDownColumn, FilterBar } from "./filter-bar"; +import { ScrollBar } from "./scroll-bar"; export type StarterSelectCallback = (starters: Starter[]) => void; @@ -128,44 +132,27 @@ function getSameSpeciesEggCandyCounts(baseValue: integer): integer { } /** - * Calculates the icon position for a Pokemon of a given UI index - * @param index UI index to calculate the icon position of + * Calculates the starter position for a Pokemon of a given UI index + * @param index UI index to calculate the starter position of * @returns An interface with an x and y property */ -function calcIconPosition(index: number): {x: number, y: number} { +function calcStarterPosition(index: number, scrollCursor:number = 0): {x: number, y: number} { + const yOffset = 13; + const height = 17; const x = (index % 9) * 18; - const y = Math.floor(index / 9) * 18; + const y = yOffset + (Math.floor(index / 9) - scrollCursor) * height; return {x: x, y: y}; } -/** - * Calculates the {@linkcode Phaser.GameObjects.Sprite} position for a Pokemon of a given UI index - * @param index UI index to calculate the icon position of - * @returns An interface with an x and y property - */ -function calcSpritePosition(index: number): {x: number, y: number} { - const position = calcIconPosition(index); - - return {x: position.x - 2, y: position.y + 2}; -} - -const gens = [ - i18next.t("starterSelectUiHandler:gen1"), - i18next.t("starterSelectUiHandler:gen2"), - i18next.t("starterSelectUiHandler:gen3"), - i18next.t("starterSelectUiHandler:gen4"), - i18next.t("starterSelectUiHandler:gen5"), - i18next.t("starterSelectUiHandler:gen6"), - i18next.t("starterSelectUiHandler:gen7"), - i18next.t("starterSelectUiHandler:gen8"), - i18next.t("starterSelectUiHandler:gen9") -]; - export default class StarterSelectUiHandler extends MessageUiHandler { private starterSelectContainer: Phaser.GameObjects.Container; + private starterSelectScrollBar: ScrollBar; + private filterBarContainer: Phaser.GameObjects.Container; + private filterBar: FilterBar; private shinyOverlay: Phaser.GameObjects.Image; - private starterSelectGenIconContainers: Phaser.GameObjects.Container[]; + private starterContainer: StarterContainer[] = []; + private filteredStarterContainers: StarterContainer[] = []; private pokemonNumberText: Phaser.GameObjects.Text; private pokemonSprite: Phaser.GameObjects.Sprite; private pokemonNameText: Phaser.GameObjects.Text; @@ -201,7 +188,6 @@ export default class StarterSelectUiHandler extends MessageUiHandler { private pokemonHatchedIcon : Phaser.GameObjects.Sprite; private pokemonHatchedCountText: Phaser.GameObjects.Text; private pokemonShinyIcon: Phaser.GameObjects.Sprite; - private genOptionsText: Phaser.GameObjects.Text; private instructionsContainer: Phaser.GameObjects.Container; private shinyIconElement: Phaser.GameObjects.Sprite; @@ -223,25 +209,23 @@ export default class StarterSelectUiHandler extends MessageUiHandler { private pokemonFormText: Phaser.GameObjects.Text; private moveInfoOverlay : MoveInfoOverlay; - private genMode: boolean; private statsMode: boolean; - private starterIconsCursorXOffset: number = -2; + private starterIconsCursorXOffset: number = -3; private starterIconsCursorYOffset: number = 1; private starterIconsCursorIndex: number; + private filterMode: boolean; private dexAttrCursor: bigint = 0n; - private abilityCursor: integer = -1; - private natureCursor: integer = -1; - private genCursor: integer = 0; - private genScrollCursor: integer = 0; + private abilityCursor: number = -1; + private natureCursor: number = -1; + private filterBarCursor: integer = 0; private starterMoveset: StarterMoveset; + private scrollCursor: number; - private genSpecies: PokemonSpecies[][] = []; + private allSpecies: PokemonSpecies[] = []; private lastSpecies: PokemonSpecies; private speciesLoaded: Map = new Map(); - public starterGens: integer[] = []; - public starterCursors: integer[] = []; - private pokerusGens: integer[] = []; - private pokerusCursors: integer[] = []; + public starterSpecies: PokemonSpecies[] = []; + private pokerusSpecies: PokemonSpecies[] = []; private starterAttr: bigint[] = []; private starterAbilityIndexes: integer[] = []; private starterNatures: Nature[] = []; @@ -262,19 +246,16 @@ export default class StarterSelectUiHandler extends MessageUiHandler { private starterCursorObjs: Phaser.GameObjects.Image[]; private pokerusCursorObjs: Phaser.GameObjects.Image[]; private starterIcons: Phaser.GameObjects.Sprite[]; - private genCursorObj: Phaser.GameObjects.Image; - private genCursorHighlightObj: Phaser.GameObjects.Image; private starterIconsCursorObj: Phaser.GameObjects.Image; private valueLimitLabel: Phaser.GameObjects.Text; private startCursorObj: Phaser.GameObjects.NineSlice; - private starterValueLabels: Phaser.GameObjects.Text[]; - private shinyIcons: Phaser.GameObjects.Image[][]; - private hiddenAbilityIcons: Phaser.GameObjects.Image[]; - private classicWinIcons: Phaser.GameObjects.Image[]; - private candyUpgradeIcon: Phaser.GameObjects.Image[]; - private candyUpgradeOverlayIcon: Phaser.GameObjects.Image[]; - private starterPassiveBgs: Phaser.GameObjects.Image[]; - + // private starterValueLabels: Phaser.GameObjects.Text[]; + // private shinyIcons: Phaser.GameObjects.Image[][]; + // private hiddenAbilityIcons: Phaser.GameObjects.Image[]; + // private classicWinIcons: Phaser.GameObjects.Image[]; + // private candyUpgradeIcon: Phaser.GameObjects.Image[]; + // private candyUpgradeOverlayIcon: Phaser.GameObjects.Image[]; + // private iconAnimHandler: PokemonIconAnimHandler; //variables to keep track of the dynamically rendered list of instruction prompts for starter select @@ -315,13 +296,96 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.shinyOverlay.setVisible(false); this.starterSelectContainer.add(this.shinyOverlay); - const starterContainerWindow = addWindow(this.scene, 141, 1, 178, 178); + const starterContainerWindow = addWindow(this.scene, 109, 18, 175, 161); + const starterContainerBg = this.scene.add.image(110, 19, "starter_container_bg"); + starterContainerBg.setOrigin(0, 0); + this.starterSelectContainer.add(starterContainerBg); - this.starterSelectContainer.add(addWindow(this.scene, 107, 1, 34, 58)); - this.starterSelectContainer.add(addWindow(this.scene, 107, 59, 34, 91)); - this.starterSelectContainer.add(addWindow(this.scene, 107, 145, 34, 34, true)); + this.starterSelectContainer.add(addWindow(this.scene, 285, 59, 34, 91)); + this.starterSelectContainer.add(addWindow(this.scene, 285, 145, 34, 34, true)); this.starterSelectContainer.add(starterContainerWindow); + this.filterBarContainer = this.scene.add.container(0, 0); + + // this.filterBar = new FilterBar(this.scene, 143, 1, 175, 17); + this.filterBar = new FilterBar(this.scene, 109, 1, 175, 17); + + // gen filter + const genOptions: DropDownOption[] = [ + new DropDownOption(this.scene, 1, i18next.t("starterSelectUiHandler:gen1"), null, DropDownState.ON), + new DropDownOption(this.scene, 2, i18next.t("starterSelectUiHandler:gen2"), null, DropDownState.ON), + new DropDownOption(this.scene, 3, i18next.t("starterSelectUiHandler:gen3"), null, DropDownState.ON), + new DropDownOption(this.scene, 4, i18next.t("starterSelectUiHandler:gen4"), null, DropDownState.ON), + new DropDownOption(this.scene, 5, i18next.t("starterSelectUiHandler:gen5"), null, DropDownState.ON), + new DropDownOption(this.scene, 6, i18next.t("starterSelectUiHandler:gen6"), null, DropDownState.ON), + new DropDownOption(this.scene, 7, i18next.t("starterSelectUiHandler:gen7"), null, DropDownState.ON), + new DropDownOption(this.scene, 8, i18next.t("starterSelectUiHandler:gen8"), null, DropDownState.ON), + new DropDownOption(this.scene, 9, i18next.t("starterSelectUiHandler:gen9"), null, DropDownState.ON), + ]; + this.filterBar.addFilter("Gen", new DropDown(this.scene, 0, 0, genOptions, this.updateStarters, DropDownType.MULTI)); + this.filterBar.defaultGenVals = this.filterBar.getVals(DropDownColumn.GEN); + // set gen filter to all off except for the I GEN + for (const option of genOptions) { + if (option.val !== 1) { + option.setOptionState(DropDownState.OFF); + } + } + + // type filter + const typeKeys = Object.keys(Type).filter(v => isNaN(Number(v))); + const typeOptions: DropDownOption[] = []; + typeKeys.forEach((type, index) => { + if (index === 0 || index === 19) { + return; + } + const typeSprite = this.scene.add.sprite(0, 0, `types${Utils.verifyLang(i18next.resolvedLanguage) ? `_${i18next.resolvedLanguage}` : ""}`); + typeSprite.setScale(0.5); + typeSprite.setFrame(type.toLowerCase()); + typeOptions.push(new DropDownOption(this.scene, index, null, typeSprite)); + }); + this.filterBar.addFilter("Type", new DropDown(this.scene, 0, 0, typeOptions, this.updateStarters, DropDownType.MULTI, 0.5)); + this.filterBar.defaultTypeVals = this.filterBar.getVals(DropDownColumn.TYPES); + + // Unlocks filter + const shiny1Sprite = this.scene.add.sprite(0, 0, "shiny_star_small"); + shiny1Sprite.setTint(getVariantTint(0)); + const shiny2Sprite = this.scene.add.sprite(0, 0, "shiny_star_small"); + shiny2Sprite.setTint(getVariantTint(1)); + const shiny3Sprite = this.scene.add.sprite(0, 0, "shiny_star_small"); + shiny3Sprite.setTint(getVariantTint(2)); + + const unlocksOptions = [ + new DropDownOption(this.scene, "SHINY3", null, shiny3Sprite), + new DropDownOption(this.scene, "SHINY2", null, shiny2Sprite), + new DropDownOption(this.scene, "SHINY", null, shiny1Sprite), + new DropDownOption(this.scene, "NORMAL", "Normal"), + new DropDownOption(this.scene, "UNCAUGHT", "Not Caught"), + new DropDownOption(this.scene, "PASSIVEUNLOCKED", "Passive Unlocked"), + new DropDownOption(this.scene, "PASSIVELOCKED", "Passive Locked"),]; + + this.filterBar.addFilter("Unlocks", new DropDown(this.scene, 0, 0, unlocksOptions, this.updateStarters, DropDownType.MULTI)); + this.filterBar.defaultUnlockVals = this.filterBar.getVals(DropDownColumn.UNLOCKS); + + // win filter + const winOptions = [ + new DropDownOption(this.scene, "WIN", "has won"), + new DropDownOption(this.scene, "NOTWIN", "hasn't won yet")]; + this.filterBar.addFilter("Win", new DropDown(this.scene, 0, 0, winOptions, this.updateStarters, DropDownType.MULTI)); + this.filterBar.defaultWinVals = this.filterBar.getVals(DropDownColumn.WIN); + + // sort filter + const sortOptions = [ + new DropDownOption(this.scene, 0, "No."), + new DropDownOption(this.scene, 1, "Cost", null, DropDownState.OFF), + new DropDownOption(this.scene, 2, "# Candies", null, DropDownState.OFF), + new DropDownOption(this.scene, 3, "IVs", null, DropDownState.OFF), + new DropDownOption(this.scene, 4, "Name", null, DropDownState.OFF)]; + this.filterBar.addFilter("Sort", new DropDown(this.scene, 0, 0, sortOptions, this.updateStarters, DropDownType.SINGLE)); + this.filterBarContainer.add(this.filterBar); + this.filterBar.defaultSortVals = this.filterBar.getVals(DropDownColumn.SORT); + + this.starterSelectContainer.add(this.filterBarContainer); + if (!this.scene.uiTheme) { starterContainerWindow.setVisible(false); } @@ -397,37 +461,32 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.pokemonEggMoveBgs = []; this.pokemonEggMoveLabels = []; - this.genOptionsText = addTextObject(this.scene, 124, 7, "", TextStyle.WINDOW, { fontSize: 72, lineSpacing: 39, align: "center" }); - this.genOptionsText.setShadowOffset(4.5, 4.5); - this.genOptionsText.setOrigin(0.5, 0); - this.starterSelectContainer.add(this.genOptionsText); + this.valueLimitLabel = addTextObject(this.scene, 302, 150, "0/10", TextStyle.TOOLTIP_CONTENT); + this.valueLimitLabel.setOrigin(0.5, 0); + this.starterSelectContainer.add(this.valueLimitLabel); - this.updateGenOptions(); + const startLabel = addTextObject(this.scene, 302, 162, i18next.t("common:start"), TextStyle.TOOLTIP_CONTENT); + startLabel.setOrigin(0.5, 0); + this.starterSelectContainer.add(startLabel); - this.starterPassiveBgs = new Array(81).fill(null).map((_, i) => { - const position = calcIconPosition(i); - const ret = this.scene.add.image(position.x + 153, position.y+14, "passive_bg"); - ret.setOrigin(0, 0); - ret.setScale(0.75); - ret.setVisible(false); - this.starterSelectContainer.add(ret); - return ret; - }); + this.startCursorObj = this.scene.add.nineslice(289, 160, "select_cursor", null, 26, 15, 6, 6, 6, 6); + this.startCursorObj.setVisible(false); + this.startCursorObj.setOrigin(0, 0); + this.starterSelectContainer.add(this.startCursorObj); - this.starterSelectGenIconContainers = new Array(gens.length).fill(null).map((_, i) => { - const container = this.scene.add.container(151, 9); - if (i) { - container.setVisible(false); - } - this.starterSelectContainer.add(container); - return container; - }); + const starterSpecies: Species[] = []; + + const starterBoxContainer = this.scene.add.container(115, 9); + + this.starterSelectScrollBar = new ScrollBar(this.scene, 161, 12, 0); + + starterBoxContainer.add(this.starterSelectScrollBar); this.pokerusCursorObjs = new Array(3).fill(null).map(() => { const cursorObj = this.scene.add.image(0, 0, "select_cursor_pokerus"); cursorObj.setVisible(false); cursorObj.setOrigin(0, 0); - this.starterSelectContainer.add(cursorObj); + starterBoxContainer.add(cursorObj); return cursorObj; }); @@ -435,72 +494,39 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const cursorObj = this.scene.add.image(0, 0, "select_cursor_highlight"); cursorObj.setVisible(false); cursorObj.setOrigin(0, 0); - this.starterSelectContainer.add(cursorObj); + starterBoxContainer.add(cursorObj); return cursorObj; }); this.cursorObj = this.scene.add.image(0, 0, "select_cursor"); this.cursorObj.setOrigin(0, 0); - this.starterSelectContainer.add(this.cursorObj); - - this.genCursorHighlightObj = this.scene.add.image(111, 5, "select_gen_cursor_highlight"); - this.genCursorHighlightObj.setOrigin(0, 0); - this.starterSelectContainer.add(this.genCursorHighlightObj); - - this.genCursorObj = this.scene.add.image(111, 5, "select_gen_cursor"); - this.genCursorObj.setVisible(false); - this.genCursorObj.setOrigin(0, 0); - this.starterSelectContainer.add(this.genCursorObj); - - this.starterIconsCursorObj = this.scene.add.image(this.genCursorObj.x, 64, "select_gen_cursor"); + this.starterIconsCursorObj = this.scene.add.image(289, 64, "select_gen_cursor"); this.starterIconsCursorObj.setName("starter-icons-cursor"); this.starterIconsCursorObj.setVisible(false); this.starterIconsCursorObj.setOrigin(0, 0); this.starterSelectContainer.add(this.starterIconsCursorObj); - this.valueLimitLabel = addTextObject(this.scene, 124, 150, "0/10", TextStyle.TOOLTIP_CONTENT); - this.valueLimitLabel.setOrigin(0.5, 0); - this.starterSelectContainer.add(this.valueLimitLabel); + starterBoxContainer.add(this.cursorObj); - const startLabel = addTextObject(this.scene, 124, 162, i18next.t("common:start"), TextStyle.TOOLTIP_CONTENT); - startLabel.setOrigin(0.5, 0); - this.starterSelectContainer.add(startLabel); - - this.startCursorObj = this.scene.add.nineslice(111, 160, "select_cursor", null, 26, 15, 6, 6, 6, 6); - this.startCursorObj.setVisible(false); - this.startCursorObj.setOrigin(0, 0); - this.starterSelectContainer.add(this.startCursorObj); - - const starterSpecies: Species[] = []; - - for (let g = 0; g < this.starterSelectGenIconContainers.length; g++) { - let s = 0; - this.genSpecies.push([]); - - for (const species of allSpecies) { - if (!speciesStarters.hasOwnProperty(species.speciesId) || species.generation !== g + 1 || !species.isObtainable()) { - continue; - } - starterSpecies.push(species.speciesId); - this.speciesLoaded.set(species.speciesId, false); - this.genSpecies[g].push(species); - const defaultDexAttr = this.scene.gameData.getSpeciesDefaultDexAttr(species, false, true); - const defaultProps = this.scene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); - const position = calcIconPosition(s); - const icon = this.scene.add.sprite(position.x - 2, position.y + 2, species.getIconAtlasKey(defaultProps.formIndex, defaultProps.shiny, defaultProps.variant)); - icon.setScale(0.5); - icon.setOrigin(0, 0); - icon.setFrame(species.getIconId(defaultProps.female, defaultProps.formIndex, defaultProps.shiny, defaultProps.variant)); - this.checkIconId(icon, species, defaultProps.female, defaultProps.formIndex, defaultProps.shiny, defaultProps.variant); - icon.setTint(0); - this.starterSelectGenIconContainers[g].add(icon); - this.iconAnimHandler.addOrUpdate(icon, PokemonIconAnimMode.NONE); - s++; + for (const species of allSpecies) { + if (!speciesStarters.hasOwnProperty(species.speciesId) || !species.isObtainable()) { + continue; } + + starterSpecies.push(species.speciesId); + this.speciesLoaded.set(species.speciesId, false); + this.allSpecies.push(species); + + const starterContainer = new StarterContainer(this.scene, species).setVisible(false); + this.iconAnimHandler.addOrUpdate(starterContainer.icon, PokemonIconAnimMode.NONE); + this.starterContainer.push(starterContainer); + starterBoxContainer.add(starterContainer); } + this.starterSelectContainer.add(starterBoxContainer); + this.starterIcons = new Array(6).fill(null).map((_, i) => { - const icon = this.scene.add.sprite(113, 63 + 13 * i, "pokemon_icons_0"); + const icon = this.scene.add.sprite(292, 63 + 13 * i, "pokemon_icons_0"); icon.setScale(0.5); icon.setOrigin(0, 0); icon.setFrame("unknown"); @@ -509,70 +535,6 @@ export default class StarterSelectUiHandler extends MessageUiHandler { return icon; }); - this.starterValueLabels = new Array(81).fill(null).map((_, i) => { - const position = calcIconPosition(i); - const ret = addTextObject(this.scene, position.x + 152, position.y + 11, "0", TextStyle.WINDOW, { fontSize: "32px" }); - ret.setShadowOffset(2, 2); - ret.setOrigin(0, 0); - ret.setVisible(false); - this.starterSelectContainer.add(ret); - return ret; - }); - - const getShinyStar = (i: integer, v: integer): Phaser.GameObjects.Image => { - const position = calcIconPosition(i); - const ret = this.scene.add.image((position.x - v * 3) + 163, position.y + 11, "shiny_star_small"); - ret.setOrigin(0, 0); - ret.setScale(0.5); - ret.setVisible(false); - this.starterSelectContainer.add(ret); - return ret; - }; - - this.shinyIcons = new Array(81).fill(null).map((_, i) => { - return new Array(3).fill(null).map((_, v) => getShinyStar(i, v)); - }); - - this.hiddenAbilityIcons = new Array(81).fill(null).map((_, i) => { - const position = calcIconPosition(i); - const ret = this.scene.add.image(position.x + 163, position.y + 16, "ha_capsule"); - ret.setOrigin(0, 0); - ret.setScale(0.5); - ret.setVisible(false); - this.starterSelectContainer.add(ret); - return ret; - }); - - this.classicWinIcons = new Array(81).fill(null).map((_, i) => { - const position = calcIconPosition(i); - const ret = this.scene.add.image(position.x + 153, position.y + 21, "champion_ribbon"); - ret.setOrigin(0, 0); - ret.setScale(0.5); - ret.setVisible(false); - this.starterSelectContainer.add(ret); - return ret; - }); - - this.candyUpgradeIcon = new Array(81).fill(null).map((_, i) => { - const position = calcIconPosition(i); - const ret = this.scene.add.image(position.x + 163, position.y + 21, "candy"); - ret.setOrigin(0, 0); - ret.setScale(0.25); - ret.setVisible(false); - this.starterSelectContainer.add(ret); - return ret; - }); - - this.candyUpgradeOverlayIcon = new Array(81).fill(null).map((_, i) => { - const position = calcIconPosition(i); - const ret = this.scene.add.image(position.x + 163, position.y + 21, "candy_overlay"); - ret.setOrigin(0, 0); - ret.setScale(0.25); - ret.setVisible(false); - this.starterSelectContainer.add(ret); - return ret; - }); - this.pokemonSprite = this.scene.add.sprite(53, 63, "pkmn__sub"); this.pokemonSprite.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true }); this.starterSelectContainer.add(this.pokemonSprite); @@ -779,12 +741,10 @@ export default class StarterSelectUiHandler extends MessageUiHandler { for (let c = 0; c < 3; c++) { let randomSpeciesId: Species; let species: PokemonSpecies; - let pokerusCursor: integer; const generateSpecies = () => { randomSpeciesId = Utils.randSeedItem(starterSpecies); species = getPokemonSpecies(randomSpeciesId); - pokerusCursor = this.genSpecies[species.generation - 1].indexOf(species); }; let dupe = false; @@ -794,17 +754,15 @@ export default class StarterSelectUiHandler extends MessageUiHandler { generateSpecies(); - for (let pc = 0; pc < c; pc++) { - if (this.pokerusGens[pc] === species.generation -1 && this.pokerusCursors[pc] === pokerusCursor) { + for (let ps = 0; ps < c; ps++) { + if (this.pokerusSpecies[ps] === species) { dupe = true; break; } } } while (dupe); - this.pokerusGens.push(species.generation - 1); - this.pokerusCursors.push(pokerusCursor); - this.pokerusCursorObjs[c].setPosition(150 + 18 * (pokerusCursor % 9), 10 + 18 * Math.floor(pokerusCursor / 9)); + this.pokerusSpecies.push(species); } }, 0, date.getTime().toString()); @@ -825,6 +783,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { y: this.scene.game.canvas.height / 6 - MoveInfoOverlay.getHeight(overlayScale) - 29, }); this.starterSelectContainer.add(this.moveInfoOverlay); + this.starterSelectContainer.bringToTop(this.filterBarContainer); this.scene.eventTarget.addEventListener(BattleSceneEventType.CANDY_UPGRADE_NOTIFICATION_CHANGED, (e) => this.onCandyUpgradeDisplayChanged(e)); @@ -843,28 +802,26 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.starterSelectContainer.setVisible(true); - this.setCursor(0); - this.setGenMode(false); - this.setCursor(0); - this.setGenMode(true); + this.allSpecies.forEach((species, s) => { + const icon = this.starterContainer[s].icon; + const dexEntry = this.scene.gameData.dexData[species.speciesId]; + + if (dexEntry.caughtAttr) { + icon.clearTint(); + } else if (dexEntry.seenAttr) { + icon.setTint(0x808080); + } + + this.setUpgradeAnimation(icon, species); + }); + + this.updateStarters(); + + this.setFilterMode(false); + this.filterBarCursor = 0; this.setCursor(0); this.tryUpdateValue(0); - for (let g = 0; g < this.genSpecies.length; g++) { - this.genSpecies[g].forEach((species, s) => { - const icon = this.starterSelectGenIconContainers[g].getAt(s) as Phaser.GameObjects.Sprite; - const dexEntry = this.scene.gameData.dexData[species.speciesId]; - - if (dexEntry.caughtAttr) { - icon.clearTint(); - } else if (dexEntry.seenAttr) { - icon.setTint(0x808080); - } - - this.setUpgradeAnimation(icon, species); - }); - } - handleTutorial(this.scene, Tutorial.Starter_Select); return true; @@ -953,8 +910,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { return; } - const position = calcSpritePosition(this.genSpecies[species.generation - 1].indexOf(species)); - icon.y = position.y; + icon.y = 2; const tweenChain: Phaser.Types.Tweens.TweenChainBuilderConfig = { targets: icon, @@ -965,14 +921,14 @@ export default class StarterSelectUiHandler extends MessageUiHandler { tweens: [ { targets: icon, - y: position.y - 5, + y: 2 - 5, duration: Utils.fixedInt(125), ease: "Cubic.easeOut", yoyo: true }, { targets: icon, - y: position.y - 3, + y: 2 - 3, duration: Utils.fixedInt(150), ease: "Cubic.easeOut", yoyo: true @@ -994,34 +950,29 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } /** - * Sets the visibility of a Candy Upgrade Icon given an index - * @param index The UI index of the icon within this generation container + * Sets the visibility of a Candy Upgrade Icon */ - setUpgradeIcon(index: number): void { - const species = this.genSpecies[this.getGenCursorWithScroll()][index]; + setUpgradeIcon(starter: StarterContainer): void { + const species = starter.species; const slotVisible = !!species?.speciesId; - if (!species // No Pokemon exists at that UI index - || this.scene.candyUpgradeNotification === 0 // Notification setting is 'Off' - || species.speciesId !== species.getRootSpeciesId(false)) { // Pokemon is not the base evolution and can't use candy - // Set all icons as hidden and exit early - this.candyUpgradeIcon[index].setVisible(false); - this.candyUpgradeOverlayIcon[index].setVisible(false); - + if (!species || this.scene.candyUpgradeNotification === 0 || species.speciesId !== species.getRootSpeciesId(false)) { + starter.candyUpgradeIcon.setVisible(false); + starter.candyUpgradeOverlayIcon.setVisible(false); return; } const passiveAvailable = this.isPassiveAvailable(species.speciesId); // 'Only Passive Unlocks' mode if (this.scene.candyUpgradeNotification === 1) { - this.candyUpgradeIcon[index].setVisible(slotVisible && passiveAvailable); - this.candyUpgradeOverlayIcon[index].setVisible(slotVisible && this.candyUpgradeIcon[index].visible); + starter.candyUpgradeIcon.setVisible(slotVisible && passiveAvailable); + starter.candyUpgradeOverlayIcon.setVisible(slotVisible && starter.candyUpgradeIcon.visible); // 'On' mode } else if (this.scene.candyUpgradeNotification === 2) { - this.candyUpgradeIcon[index].setVisible( + starter.candyUpgradeIcon.setVisible( slotVisible && ( passiveAvailable || this.isValueReductionAvailable(species.speciesId))); - this.candyUpgradeOverlayIcon[index].setVisible(slotVisible && this.candyUpgradeIcon[index].visible); + starter.candyUpgradeOverlayIcon.setVisible(slotVisible && starter.candyUpgradeIcon.visible); } } @@ -1037,21 +988,19 @@ export default class StarterSelectUiHandler extends MessageUiHandler { // Loop through all visible candy icons when set to 'Icon' mode if (this.scene.candyUpgradeDisplay === 0) { - this.genSpecies[this.getGenCursorWithScroll()].forEach((_species, s) => { - this.setUpgradeIcon(s); + this.filteredStarterContainers.forEach((starter) => { + this.setUpgradeIcon(starter); }); return; } // Loop through all animations when set to 'Animation' mode - for (let g = 0; g < this.genSpecies.length; g++) { - this.genSpecies[g].forEach((species, s) => { - const icon = this.starterSelectGenIconContainers[g].getAt(s) as Phaser.GameObjects.Sprite; + this.filteredStarterContainers.forEach((starter, s) => { + const icon = this.filteredStarterContainers[s].icon; - this.setUpgradeAnimation(icon, species); - }); - } + this.setUpgradeAnimation(icon, starter.species); + }); } processInput(button: Button): boolean { @@ -1059,6 +1008,19 @@ export default class StarterSelectUiHandler extends MessageUiHandler { return false; } + const numberOfStarters = this.filteredStarterContainers.length; + const numOfRows = Math.ceil(numberOfStarters / 9); + const currentRow = Math.floor(this.cursor / 9); + const onScreenFirstIndex = this.scrollCursor * 9; // this is first starter index on the screen + const onScreenLastIndex = Math.min(onScreenFirstIndex + 9*9, numberOfStarters) - 1; // this is the last starter index on the screen + const onScreenNumberOfStarters = onScreenLastIndex - onScreenFirstIndex + 1; + const onScreenNumberOfRows = Math.ceil(onScreenNumberOfStarters / 9); + // const onScreenFirstRow = Math.floor(onScreenFirstIndex / 9); + const onScreenCurrentRow = Math.floor((this.cursor - onScreenFirstIndex) / 9); + + + // console.log("this.cursor: ", this.cursor, "this.scrollCursor" , this.scrollCursor, "numberOfStarters: ", numberOfStarters, "numOfRows: ", numOfRows, "currentRow: ", currentRow, "onScreenFirstIndex: ", onScreenFirstIndex, "onScreenLastIndex: ", onScreenLastIndex, "onScreenNumberOfStarters: ", onScreenNumberOfStarters, "onScreenNumberOfRow: ", onScreenNumberOfRows, "onScreenCurrentRow: ", onScreenCurrentRow); + const ui = this.getUi(); let success = false; @@ -1071,11 +1033,14 @@ export default class StarterSelectUiHandler extends MessageUiHandler { error = true; } } else if (button === Button.CANCEL) { - if (this.statsMode) { + if (this.filterMode && this.filterBar.openDropDown) { + this.filterBar.toggleDropDown(this.filterBarCursor); + success = true; + } else if (this.statsMode) { this.toggleStatsMode(false); success = true; - } else if (this.starterCursors.length) { - this.popStarter(this.starterCursors.length - 1); + } else if (this.starterSpecies.length) { + this.popStarter(this.starterSpecies.length - 1); success = true; this.updateInstructions(); } else { @@ -1090,8 +1055,6 @@ export default class StarterSelectUiHandler extends MessageUiHandler { success = true; } } else if (this.startCursorObj.visible) { // this checks to see if the start button is selected - const genStarters = this.starterSelectGenIconContainers[this.getGenCursorWithScroll()].getAll().length; - const rows = Math.ceil(genStarters / 9); switch (button) { case Button.ACTION: if (this.tryStart(true)) { @@ -1102,132 +1065,151 @@ export default class StarterSelectUiHandler extends MessageUiHandler { break; case Button.UP: this.startCursorObj.setVisible(false); - if (this.starterCursors.length > 0) { - this.starterIconsCursorIndex = this.starterCursors.length - 1; + if (this.starterSpecies.length > 0) { + this.starterIconsCursorIndex = this.starterSpecies.length - 1; this.moveStarterIconsCursor(this.starterIconsCursorIndex); } else { - this.setGenMode(true); + this.setFilterMode(true); } success = true; break; case Button.DOWN: this.startCursorObj.setVisible(false); - this.setGenMode(true); + this.starterIconsCursorIndex = 0; + this.moveStarterIconsCursor(this.starterIconsCursorIndex); success = true; break; case Button.LEFT: this.startCursorObj.setVisible(false); - this.setGenMode(false); - this.setCursor(genStarters - 1); + this.cursorObj.setVisible(true); + success = this.setCursor(onScreenFirstIndex + (onScreenNumberOfRows-1) * 9 + 8); // set last column success = true; break; case Button.RIGHT: this.startCursorObj.setVisible(false); - this.setGenMode(false); - this.setCursor((rows - 1) * 9); + this.cursorObj.setVisible(true); + success = this.setCursor(onScreenFirstIndex + (onScreenNumberOfRows-1) * 9); // set first column success = true; break; } - } else if (this.genMode && this.genCursorObj.visible) { // this checks to see if the generation selection icons are selected + } else if (this.filterMode) { switch (button) { - case Button.UP: - if (this.genCursor > 0) { - success = this.setCursor(this.genCursor - 1); + case Button.LEFT: + if (this.filterBarCursor > 0) { + success = this.setCursor(this.filterBarCursor - 1); } else { - this.startCursorObj.setVisible(true); - this.setGenMode(true); - success = true; + success = this.setCursor(this.filterBar.numFilters - 1); + } + break; + case Button.RIGHT: + if (this.filterBarCursor < this.filterBar.numFilters - 1) { + success = this.setCursor(this.filterBarCursor + 1); + } else { + success = this.setCursor(0); + } + break; + case Button.UP: + if (this.filterBar.openDropDown) { + success = this.filterBar.decDropDownCursor(); + // else if there is filtered starters + } else if (numberOfStarters > 0) { + this.setFilterMode(false); + this.scrollCursor = Math.max(0,numOfRows - 9); + this.updateScroll(); + const proportion = (this.filterBarCursor + 0.5) / this.filterBar.numFilters; + const targetCol = Math.floor(proportion * 9); + if (numberOfStarters % 9 > targetCol) { + success = this.setCursor(numberOfStarters - (numberOfStarters) % 9 + targetCol); + } else { + success = this.setCursor(Math.max(numberOfStarters - (numberOfStarters) % 9 + targetCol - 9,0)); + } } break; case Button.DOWN: - if (this.genCursor < 2) { - success = this.setCursor(this.genCursor + 1); - } else { - if (this.starterCursors.length === 0) { - this.startCursorObj.setVisible(true); - } else { - this.starterIconsCursorIndex = 0; - this.moveStarterIconsCursor(this.starterIconsCursorIndex); - } - this.setGenMode(true); + if (this.filterBar.openDropDown) { + success = this.filterBar.incDropDownCursor(); + } else if (numberOfStarters > 0) { + this.setFilterMode(false); + this.scrollCursor = 0; + this.updateScroll(); + const proportion = this.filterBarCursor / Math.max(1, this.filterBar.numFilters - 1); + this.setCursor(Math.round(proportion * (Math.min(9, numberOfStarters) - 1))); success = true; } break; - case Button.LEFT: - success = this.setGenMode(false); - this.setCursor(this.cursor + 8); - break; - case Button.RIGHT: - success = this.setGenMode(false); + case Button.ACTION: + if (!this.filterBar.openDropDown) { + this.filterBar.toggleDropDown(this.filterBarCursor); + } else { + this.filterBar.toggleOptionState(); + } + success = true; break; } } else { - /** - * This code generates the menu for a pokemon when you press the action button - * This works in modules; it does a check for each option to see if it's valid, and if so, will add that option to the menu - * As an example, if you try to add an invalid pokemon, the "Add to Party" option won't show up - * But if you can still use candies or change natures, those menu items will be added - * Once it's all done, it displays the menu for you - **/ if (button === Button.ACTION) { if (!this.speciesStarterDexEntry?.caughtAttr) { error = true; - } else if (this.starterCursors.length < 6) { // checks to see you have less than 6 pokemon in your party - let pokemonGen; - let pokemonCursor; - // this gets the correct generation and pokemon cursor depending on whether you're in the starter screen or the party icons - if (!this.starterIconsCursorObj.visible) { - pokemonGen = this.getGenCursorWithScroll(); - pokemonCursor = this.cursor; - } else { - pokemonGen = this.starterGens[this.starterIconsCursorIndex]; - pokemonCursor = this.starterCursors[this.starterIconsCursorIndex]; - } - const ui = this.getUi(); - let options = []; - - const [isDupe, removeIndex]: [boolean, number] = this.isInParty(pokemonGen, pokemonCursor); // checks to see if the pokemon is a duplicate; if it is, returns the index that will be removed - - const species = this.genSpecies[pokemonGen][pokemonCursor]; - - const isPartyValid = this.isPartyValid(); - const isValidForChallenge = new Utils.BooleanHolder(true); - if (isPartyValid) { - Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, species, isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(species, this.scene.gameData.getSpeciesDefaultDexAttr(species, false, true)), !!(this.starterGens.length)); - } else { - Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, species, isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(species, this.scene.gameData.getSpeciesDefaultDexAttr(species, false, true)), !!(this.starterGens.length), false, false); - } - const currentPartyValue = this.starterGens.reduce((total: number, gen: number, i: number) => total += this.scene.gameData.getSpeciesStarterValue(this.genSpecies[gen][this.starterCursors[i]].speciesId), 0); - const newCost = this.scene.gameData.getSpeciesStarterValue(species.speciesId); - if (!isDupe && isValidForChallenge.value && currentPartyValue + newCost <= this.getValueLimit()) { // this checks to make sure the pokemon doesn't exist in your party, it's valid for the challenge and that it won't go over the cost limit; if it meets all these criteria it will add it to your party - options = [ - { - label: i18next.t("starterSelectUiHandler:addToParty"), - handler: () => { - ui.setMode(Mode.STARTER_SELECT); - - if (!isDupe && isValidForChallenge.value && this.tryUpdateValue(this.scene.gameData.getSpeciesStarterValue(species.speciesId), true)) { - this.addToParty(species, pokemonGen, pokemonCursor); - ui.playSelect(); - } else { - ui.playError(); // this should be redundant as there is now a trigger for when a pokemon can't be added to party - } - return true; - }, - overrideSound: true - }]; - } else if (isDupe) { // if it already exists in your party, it will give you the option to remove from your party - options = [{ - label: i18next.t("starterSelectUiHandler:removeFromParty"), + } else if (this.starterSpecies.length < 6) { + const options = [ + { + label: i18next.t("starterSelectUiHandler:addToParty"), handler: () => { - this.popStarter(removeIndex); ui.setMode(Mode.STARTER_SELECT); - return true; - } - }]; - } + let isDupe = false; - options.push( // this shows the IVs for the pokemon + const species = this.filteredStarterContainers[this.cursor].species; + for (let s = 0; s < this.starterSpecies.length; s++) { + if (species === this.starterSpecies[s]) { + isDupe = true; + break; + } + } + + const isValidForChallenge = new Utils.BooleanHolder(true); + Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, species, isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor), !!this.starterSpecies.length); + + + if (!isDupe && isValidForChallenge.value && this.tryUpdateValue(this.scene.gameData.getSpeciesStarterValue(species.speciesId))) { + const cursorObj = this.starterCursorObjs[this.starterSpecies.length]; + cursorObj.setVisible(true); + cursorObj.setPosition(this.cursorObj.x, this.cursorObj.y); + const props = this.scene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor); + this.starterIcons[this.starterSpecies.length].setTexture(species.getIconAtlasKey(props.formIndex, props.shiny, props.variant)); + this.starterIcons[this.starterSpecies.length].setFrame(species.getIconId(props.female, props.formIndex, props.shiny, props.variant)); + this.checkIconId(this.starterIcons[this.starterSpecies.length], species, props.female, props.formIndex, props.shiny, props.variant); + this.starterSpecies.push(species); + this.starterAttr.push(this.dexAttrCursor); + this.starterAbilityIndexes.push(this.abilityCursor); + this.starterNatures.push(this.natureCursor as unknown as Nature); + this.starterMovesets.push(this.starterMoveset.slice(0) as StarterMoveset); + if (this.speciesLoaded.get(species.speciesId)) { + getPokemonSpeciesForm(species.speciesId, props.formIndex).cry(this.scene); + } + if (this.starterSpecies.length === 6 || this.value === this.getValueLimit()) { + this.cursorObj.setVisible(false); + this.setSpecies(null); + this.startCursorObj.setVisible(true); + this.tryStart(); + } + this.updateInstructions(); + + /** + * If the user can't select a pokemon anymore, + * go to start button. + */ + if (!this.canAddParty) { + this.startCursorObj.setVisible(true); + } + + ui.playSelect(); + } else { + ui.playError(); + } + return true; + }, + overrideSound: true + }, { label: i18next.t("starterSelectUiHandler:toggleIVs"), handler: () => { @@ -1235,7 +1217,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { ui.setMode(Mode.STARTER_SELECT); return true; } - }); + } + ]; if (this.speciesStarterMoves.length > 1) { // this lets you change the pokemon moves const showSwapOptions = (moveset: StarterMoveset) => { ui.setMode(Mode.STARTER_SELECT).then(() => { @@ -1317,6 +1300,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } }); } + const starterContainer = this.filteredStarterContainers[this.cursor]; const starterData = this.scene.gameData.starterData[this.lastSpecies.speciesId]; let starterAttributes = this.starterPreferences[this.lastSpecies.speciesId]; if (this.canCycleNature) { @@ -1332,8 +1316,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { handler: () => { // update default nature in starter save data if (!starterAttributes) { - starterAttributes = - this.starterPreferences[this.lastSpecies.speciesId] = {}; + starterAttributes = this.starterPreferences[this.lastSpecies.speciesId] = {}; } starterAttributes.nature = n as unknown as integer; this.clearText(); @@ -1396,7 +1379,6 @@ export default class StarterSelectUiHandler extends MessageUiHandler { if (!(passiveAttr & PassiveAttr.UNLOCKED)) { const passiveCost = getPassiveCandyCount(speciesStarters[this.lastSpecies.speciesId]); options.push({ - label: `x${passiveCost} ${i18next.t("starterSelectUiHandler:unlockPassive")} (${allAbilities[starterPassiveAbilities[this.lastSpecies.speciesId]].name})`, handler: () => { if (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= passiveCost) { @@ -1414,12 +1396,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, undefined, undefined); // Update the candy upgrade display - if (this.isUpgradeIconEnabled()) { - this.setUpgradeIcon(pokemonCursor); + if (this.isUpgradeIconEnabled() ) { + this.setUpgradeIcon(starterContainer); } if (this.isUpgradeAnimationEnabled()) { - const genSpecies = this.genSpecies[this.lastSpecies.generation - 1]; - this.setUpgradeAnimation(this.starterSelectGenIconContainers[this.lastSpecies.generation - 1].getAt(genSpecies.indexOf(this.lastSpecies)), this.lastSpecies, true); + this.setUpgradeAnimation(starterContainer.icon, this.lastSpecies, true); } return true; @@ -1447,19 +1428,18 @@ export default class StarterSelectUiHandler extends MessageUiHandler { return this.scene.reset(true); } }); - this.updateStarterValueLabel(pokemonCursor); + this.updateStarterValueLabel(starterContainer); this.tryUpdateValue(0); ui.setMode(Mode.STARTER_SELECT); this.scene.playSound("buy"); // If the notification setting is set to 'On', update the candy upgrade display if (this.scene.candyUpgradeNotification === 2) { - if (this.isUpgradeIconEnabled()) { - this.setUpgradeIcon(pokemonCursor); + if (this.isUpgradeIconEnabled() ) { + this.setUpgradeIcon(starterContainer); } if (this.isUpgradeAnimationEnabled()) { - const genSpecies = this.genSpecies[this.lastSpecies.generation - 1]; - this.setUpgradeAnimation(this.starterSelectGenIconContainers[this.lastSpecies.generation - 1].getAt(genSpecies.indexOf(this.lastSpecies)), this.lastSpecies, true); + this.setUpgradeAnimation(starterContainer.icon, this.lastSpecies, true); } } @@ -1495,17 +1475,6 @@ export default class StarterSelectUiHandler extends MessageUiHandler { ui.setMode(Mode.STARTER_SELECT); this.scene.playSound("buy"); - // If the notification setting is set to 'On', update the candy upgrade display - // if (this.scene.candyUpgradeNotification === 2) { - // if (this.isUpgradeIconEnabled() ) { - // this.setUpgradeIcon(this.cursor); - // } - // if (this.isUpgradeAnimationEnabled()) { - // const genSpecies = this.genSpecies[this.lastSpecies.generation - 1]; - // this.setUpgradeAnimation(this.starterSelectGenIconContainers[this.lastSpecies.generation - 1].getAt(genSpecies.indexOf(this.lastSpecies)), this.lastSpecies, true); - // } - // } - return true; } return false; @@ -1549,18 +1518,13 @@ export default class StarterSelectUiHandler extends MessageUiHandler { success = true; } } else { - const genStarters = this.starterSelectGenIconContainers[this.getGenCursorWithScroll()].getAll().length; - const rows = Math.ceil(genStarters / 9); - const row = Math.floor(this.cursor / 9); const props = this.scene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.dexAttrCursor); // prepare persistent starter data to store changes let starterAttributes = this.starterPreferences[this.lastSpecies.speciesId]; if (!starterAttributes) { - starterAttributes = - this.starterPreferences[this.lastSpecies.speciesId] = {}; + starterAttributes = this.starterPreferences[this.lastSpecies.speciesId] = {}; } switch (button) { - case Button.CYCLE_SHINY: if (this.canCycleShiny) { const newVariant = props.variant; @@ -1656,6 +1620,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } } } while (newVariant !== props.variant); + starterAttributes.variant = newVariant; // store the selected variant this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, newVariant, undefined, undefined); // Cycle tint based on current sprite tint const tint = getVariantTint(newVariant); @@ -1664,46 +1629,45 @@ export default class StarterSelectUiHandler extends MessageUiHandler { success = true; } break; - case Button.UP: if (!this.starterIconsCursorObj.visible) { - if (row) { + if (currentRow > 0) { + if (this.scrollCursor > 0 && currentRow - this.scrollCursor === 0) { + this.scrollCursor--; + this.updateScroll(); + } success = this.setCursor(this.cursor - 9); } else { - // when strictly opposite starter based on rows length - // does not exits, set cursor on the second to last row - if (this.cursor + (rows - 1) * 9 > genStarters - 1) { - success = this.setCursor(this.cursor + (rows - 2) * 9); - } else { - success = this.setCursor(this.cursor + (rows - 1) * 9); - } + this.filterBarCursor = this.filterBar.getNearestFilter(this.filteredStarterContainers[this.cursor]); + this.setFilterMode(true); + success = true; } } else { if (this.starterIconsCursorIndex === 0) { this.starterIconsCursorObj.setVisible(false); this.setSpecies(null); - this.setGenMode(true); + this.startCursorObj.setVisible(true); } else { this.starterIconsCursorIndex--; this.moveStarterIconsCursor(this.starterIconsCursorIndex); } + success = true; } break; case Button.DOWN: if (!this.starterIconsCursorObj.visible) { - if (row < rows - 2 || (row < rows - 1 && this.cursor % 9 <= (genStarters - 1) % 9)) { - success = this.setCursor(this.cursor + 9); - } else { - // if there is no starter below while being on the second to - // last row, adjust cursor position with one line less - if (row === rows - 2 && this.cursor + 9 > genStarters - 1) { - success = this.setCursor(this.cursor - (rows - 2) * 9); - } else { - success = this.setCursor(this.cursor - (rows - 1) * 9); + if (currentRow < numOfRows - 1) { // not last row + if (currentRow - this.scrollCursor === 8) { // last row of visible starters + this.scrollCursor++; } + success = this.setCursor(this.cursor + 9); + this.updateScroll(); + } else { // last row + this.setFilterMode(true); + success = true; } } else { - if (this.starterIconsCursorIndex <= this.starterCursors.length - 2) { + if (this.starterIconsCursorIndex <= this.starterSpecies.length - 2) { this.starterIconsCursorIndex++; this.moveStarterIconsCursor(this.starterIconsCursorIndex); } else { @@ -1711,6 +1675,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.setSpecies(null); this.startCursorObj.setVisible(true); } + success = true; } break; case Button.LEFT: @@ -1718,66 +1683,64 @@ export default class StarterSelectUiHandler extends MessageUiHandler { if (this.cursor % 9 !== 0) { success = this.setCursor(this.cursor - 1); } else { - if (this.starterCursors.length === 0) { - if (row >= Math.min(5, rows - 1)) { - this.startCursorObj.setVisible(true); - } - success = this.setGenMode(true); + if (this.starterSpecies.length === 0) { + // just wrap around to the last column + success = this.setCursor(this.cursor + Math.min(8, numberOfStarters - this.cursor)); + } else if (onScreenCurrentRow < 3) { + // always to the first starter + this.cursorObj.setVisible(false); + this.starterIconsCursorIndex = 0; + this.moveStarterIconsCursor(this.starterIconsCursorIndex); + } else if (onScreenCurrentRow < 7) { + this.cursorObj.setVisible(false); + this.starterIconsCursorIndex = Math.min(onScreenCurrentRow-2, this.starterSpecies.length - 1); + this.moveStarterIconsCursor(this.starterIconsCursorIndex); } else { - if (row >= rows - 1) { // the last row will always go to the starter button - this.startCursorObj.setVisible(true); - } else if (row > 2) { // the first three rows will always go to the gen select, so anything else will go to the starterIcons party section - if (this.starterCursors.length >= row - 2) { - this.starterIconsCursorIndex = row - 3; - } else { - this.starterIconsCursorIndex = this.starterCursors.length - 1; - } - this.moveStarterIconsCursor(this.starterIconsCursorIndex); - } - success = this.setGenMode(true); + this.cursorObj.setVisible(false); + this.setSpecies(null); + this.startCursorObj.setVisible(true); } + success = true; } } else { this.starterIconsCursorObj.setVisible(false); - this.setGenMode(false); - const rowToUse = Math.min(this.starterIconsCursorIndex + 3, rows - 1); - this.setCursor(Math.min((rowToUse * 9) + 8, genStarters - 1)); - success = true; + this.cursorObj.setVisible(true); + success = this.setCursor(Math.min(onScreenFirstIndex + (this.starterIconsCursorIndex + 2) * 9 + 8,onScreenLastIndex)); // set last column } break; case Button.RIGHT: if (!this.starterIconsCursorObj.visible) { - if (this.cursor % 9 < (row < rows - 1 ? 8 : (genStarters - 1) % 9)) { + // is not right edge + if (this.cursor % 9 < (currentRow < numOfRows - 1 ? 8 : (numberOfStarters - 1) % 9)) { success = this.setCursor(this.cursor + 1); } else { - if (this.starterCursors.length === 0) { - if (row >= Math.min(5, rows - 1)) { - this.startCursorObj.setVisible(true); - } - success = this.setGenMode(true); + // in right edge + if (this.starterSpecies.length === 0) { + // just wrap around to the first column + success = this.setCursor(this.cursor - Math.min(8, this.cursor % 9)); + } else if (onScreenCurrentRow < 3) { + // always to the first starter + this.cursorObj.setVisible(false); + this.starterIconsCursorIndex = 0; + this.moveStarterIconsCursor(this.starterIconsCursorIndex); + } else if (onScreenCurrentRow < 7) { + this.cursorObj.setVisible(false); + this.starterIconsCursorIndex = Math.min(onScreenCurrentRow-2, this.starterSpecies.length - 1); + this.moveStarterIconsCursor(this.starterIconsCursorIndex); } else { - if (row >= rows - 1) { // the last row will always go to the starter button - this.startCursorObj.setVisible(true); - } else if (row > 2) { // the first three rows will always go to the gen select, so anything else will go to the starterIcons party section - if (this.starterCursors.length >= row - 2) { - this.starterIconsCursorIndex = row - 3; - } else { - this.starterIconsCursorIndex = this.starterCursors.length - 1; - } - this.moveStarterIconsCursor(this.starterIconsCursorIndex); - } - success = this.setGenMode(true); + this.cursorObj.setVisible(false); + this.setSpecies(null); + this.startCursorObj.setVisible(true); } + success = true; } + break; } else { this.starterIconsCursorObj.setVisible(false); - this.setGenMode(false); - const rowToUse = Math.min(this.starterIconsCursorIndex + 3, rows - 1); - this.setCursor(Math.min((rowToUse * 9), genStarters - 1)); - this.setSpecies(this.genSpecies[this.getGenCursorWithScroll()][this.cursor]); - success = true; + this.cursorObj.setVisible(true); + success = this.setCursor(Math.min(onScreenFirstIndex + (this.starterIconsCursorIndex + 2) * 9, onScreenLastIndex - (onScreenLastIndex % 9))); // set first column + break; } - break; } } } @@ -1791,11 +1754,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler { return success || error; } - isInParty(pokemonGen: number, pokemonCursor: number): [boolean, number] { + isInParty(species: PokemonSpecies): [boolean, number] { let removeIndex = 0; let isDupe = false; - for (let s = 0; s < this.starterCursors.length; s++) { - if (this.starterGens[s] === pokemonGen && this.starterCursors[s] === pokemonCursor) { + for (let s = 0; s < this.starterSpecies.length; s++) { + if (this.starterSpecies[s] === species) { isDupe = true; removeIndex = s; break; @@ -1804,26 +1767,6 @@ export default class StarterSelectUiHandler extends MessageUiHandler { return [isDupe, removeIndex]; } - addToParty(species: PokemonSpecies, pokemonGen: number, pokemonCursor: number) { - const cursorObj = this.starterCursorObjs[this.starterCursors.length]; - cursorObj.setVisible(true); - cursorObj.setPosition(this.cursorObj.x, this.cursorObj.y); - const props = this.scene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor); - this.starterIcons[this.starterCursors.length].setTexture(species.getIconAtlasKey(props.formIndex, props.shiny, props.variant)); - this.starterIcons[this.starterCursors.length].setFrame(species.getIconId(props.female, props.formIndex, props.shiny, props.variant)); - this.checkIconId(this.starterIcons[this.starterCursors.length], species, props.female, props.formIndex, props.shiny, props.variant); - this.starterGens.push(pokemonGen); - this.starterCursors.push(pokemonCursor); - this.starterAttr.push(this.dexAttrCursor); - this.starterAbilityIndexes.push(this.abilityCursor); - this.starterNatures.push(this.natureCursor as unknown as Nature); - this.starterMovesets.push(this.starterMoveset.slice(0) as StarterMoveset); - if (this.speciesLoaded.get(species.speciesId)) { - getPokemonSpeciesForm(species.speciesId, props.formIndex).cry(this.scene); - } - this.updateInstructions(); - } - switchMoveHandler(i: number, newMove: Moves, move: Moves) { const speciesId = this.lastSpecies.speciesId; const existingMoveIndex = this.starterMoveset.indexOf(newMove); @@ -1859,8 +1802,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { // switch moves of starter if exists if (this.starterMovesets.length) { - Array.from({ length: this.starterGens.length }, (_, i) => { - const starterSpecies = this.genSpecies[this.starterGens[i]][this.starterCursors[i]]; + Array.from({ length: this.starterSpecies.length }, (_, i) => { + const starterSpecies = this.starterSpecies[i]; if (starterSpecies.speciesId === speciesId) { this.starterMovesets[i] = this.starterMoveset; } @@ -1962,137 +1905,235 @@ export default class StarterSelectUiHandler extends MessageUiHandler { return valueLimit.value; } + updateStarters = () => { + this.scrollCursor = 0; + this.filteredStarterContainers = []; + + this.pokerusCursorObjs.forEach(cursor => cursor.setVisible(false)); + this.starterCursorObjs.forEach(cursor => cursor.setVisible(false)); + + this.filterBar.updateFilterLabels(); + + // filter + this.starterContainer.forEach(container => { + container.setVisible(false); + + // First, ensure you have the caught attributes for the species else default to bigint 0 + const caughtVariants = this.scene.gameData.dexData[container.species.speciesId]?.caughtAttr || BigInt(0); + + // Define the variables based on whether their respective variants have been caught + const isVariant3Caught = !!(caughtVariants & DexAttr.VARIANT_3); + const isVariant2Caught = !!(caughtVariants & DexAttr.VARIANT_2); + const isVariantCaught = !!(caughtVariants & DexAttr.SHINY); + const isCaught = !!(caughtVariants & DexAttr.NON_SHINY); + const isUncaught = !isCaught && !isVariantCaught && !isVariant2Caught && !isVariant3Caught; + const isPassiveUnlocked = this.scene.gameData.starterData[container.species.speciesId].passiveAttr > 0; + + + const fitsGen = this.filterBar.getVals(DropDownColumn.GEN).includes(container.species.generation); + const fitsType = this.filterBar.getVals(DropDownColumn.TYPES).some(type => container.species.isOfType((type as number) - 1)); + const fitsUnlocks = this.filterBar.getVals(DropDownColumn.UNLOCKS).some(variant => { + if (variant === "SHINY3") { + return isVariant3Caught; + } else if (variant === "SHINY2") { + return isVariant2Caught && !isVariant3Caught; + } else if (variant === "SHINY") { + return isVariantCaught && !isVariant2Caught && !isVariant3Caught; + } else if (variant === "NORMAL") { + return isCaught && !isVariantCaught && !isVariant2Caught && !isVariant3Caught; + } else if (variant === "UNCAUGHT") { + return isUncaught; + } else if (variant === "PASSIVEUNLOCKED") { + return isPassiveUnlocked; + } else if (variant === "PASSIVELOCKED") { + return !isPassiveUnlocked; + } + }); + const isWin = this.scene.gameData.starterData[container.species.speciesId].classicWinCount > 0; + const isNotWin = this.scene.gameData.starterData[container.species.speciesId].classicWinCount === 0; + const isUndefined = this.scene.gameData.starterData[container.species.speciesId].classicWinCount === undefined; + + const fitsWin = this.filterBar.getVals(DropDownColumn.WIN).some(win => { + if (win === "WIN") { + return isWin; + } else if (win === "NOTWIN") { + return isNotWin || isUndefined; + } + }); + + if (fitsGen && fitsType && fitsUnlocks && fitsWin) { + this.filteredStarterContainers.push(container); + } + }); + + this.starterSelectScrollBar.setPages(Math.ceil((this.filteredStarterContainers.length - 81) / 9) + 1); + this.starterSelectScrollBar.setPage(0); + + // sort + const sort = this.filterBar.getVals(DropDownColumn.SORT)[0]; + this.filteredStarterContainers.sort((a, b) => { + switch (sort.val) { + default: + break; + case 0: + return (a.species.speciesId - b.species.speciesId) * -sort.dir; + case 1: + return (a.cost - b.cost) * -sort.dir; + case 2: + const candyCountA = this.scene.gameData.starterData[a.species.speciesId].candyCount; + const candyCountB = this.scene.gameData.starterData[b.species.speciesId].candyCount; + return (candyCountA - candyCountB) * -sort.dir; + case 3: + const avgIVsA = this.scene.gameData.dexData[a.species.speciesId].ivs.reduce((a, b) => a + b, 0) / this.scene.gameData.dexData[a.species.speciesId].ivs.length; + const avgIVsB = this.scene.gameData.dexData[b.species.speciesId].ivs.reduce((a, b) => a + b, 0) / this.scene.gameData.dexData[b.species.speciesId].ivs.length; + return (avgIVsA - avgIVsB) * -sort.dir; + case 4: + return a.species.name.localeCompare(b.species.name) * -sort.dir; + } + return 0; + }); + + this.updateScroll(); + }; + + updateScroll = () => { + const perRow = 9; + const maxRows = 9; + + this.starterSelectScrollBar.setPage(this.scrollCursor); + + let pokerusCursorIndex = 0; + let starterCursorIndex = 0; + this.filteredStarterContainers.forEach((container, i) => { + const pos = calcStarterPosition(i, this.scrollCursor); + container.setPosition(pos.x, pos.y); + + if (i < (maxRows + this.scrollCursor) * perRow && i >= this.scrollCursor * perRow) { + container.setVisible(true); + } else { + container.setVisible(false); + } + + if (this.pokerusSpecies.includes(container.species)) { + this.pokerusCursorObjs[pokerusCursorIndex].setPosition(pos.x - 1, pos.y + 1); + this.pokerusCursorObjs[pokerusCursorIndex].setVisible(true); + pokerusCursorIndex++; + } + + if (this.starterSpecies.includes(container.species)) { + this.starterCursorObjs[starterCursorIndex].setPosition(pos.x - 1, pos.y + 1); + this.starterCursorObjs[starterCursorIndex].setVisible(true); + starterCursorIndex++; + } + + const speciesId = container.species.speciesId; + this.updateStarterValueLabel(container); + + container.label.setVisible(true); + const speciesVariants = speciesId && this.scene.gameData.dexData[speciesId].caughtAttr & DexAttr.SHINY + ? [ DexAttr.DEFAULT_VARIANT, DexAttr.VARIANT_2, DexAttr.VARIANT_3 ].filter(v => !!(this.scene.gameData.dexData[speciesId].caughtAttr & v)) + : []; + for (let v = 0; v < 3; v++) { + const hasVariant = speciesVariants.length > v; + container.shinyIcons[v].setVisible(hasVariant); + if (hasVariant) { + container.shinyIcons[v].setTint(getVariantTint(speciesVariants[v] === DexAttr.DEFAULT_VARIANT ? 0 : speciesVariants[v] === DexAttr.VARIANT_2 ? 1 : 2)); + } + } + + container.starterPassiveBgs.setVisible(!!this.scene.gameData.starterData[speciesId].passiveAttr); + container.hiddenAbilityIcon.setVisible(!!this.scene.gameData.dexData[speciesId].caughtAttr && !!(this.scene.gameData.starterData[speciesId].abilityAttr & 4)); + container.classicWinIcon.setVisible(this.scene.gameData.starterData[speciesId].classicWinCount > 0); + + // 'Candy Icon' mode + if (this.scene.candyUpgradeDisplay === 0) { + + if (!starterColors[speciesId]) { + // Default to white if no colors are found + starterColors[speciesId] = [ "ffffff", "ffffff" ]; + } + + // Set the candy colors + container.candyUpgradeIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(starterColors[speciesId][0]))); + container.candyUpgradeOverlayIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(starterColors[speciesId][1]))); + + this.setUpgradeIcon(container); + } else if (this.scene.candyUpgradeDisplay === 1) { + container.candyUpgradeIcon.setVisible(false); + container.candyUpgradeOverlayIcon.setVisible(false); + } + }); + }; + setCursor(cursor: integer): boolean { let changed = false; - if (this.genMode) { - changed = this.genCursor !== cursor; + if (this.filterMode) { + changed = this.filterBarCursor !== cursor; + this.filterBarCursor = cursor; - let genCursorWithScroll = this.getGenCursorWithScroll(); - - if (!cursor && this.genScrollCursor) { - this.genScrollCursor--; - cursor++; - this.updateGenOptions(); - } else if (cursor === 2 && this.genScrollCursor < gens.length - 3) { - this.genScrollCursor++; - cursor--; - this.updateGenOptions(); - } - - if (genCursorWithScroll !== undefined) { - this.starterSelectGenIconContainers[genCursorWithScroll].setVisible(false); - } - this.cursor = 0; - this.genCursor = cursor; - genCursorWithScroll = this.getGenCursorWithScroll(); - this.genCursorObj.setY(5 + 17 * this.genCursor); - this.genCursorHighlightObj.setY(this.genCursorObj.y); - this.starterSelectGenIconContainers[genCursorWithScroll].setVisible(true); - - for (let s = 0; s < this.starterCursorObjs.length; s++) { - this.starterCursorObjs[s].setVisible(this.starterGens[s] === genCursorWithScroll); - } - for (let s = 0; s < this.pokerusCursorObjs.length; s++) { - this.pokerusCursorObjs[s].setVisible(this.pokerusGens[s] === genCursorWithScroll); - } - - const genLimit = this.genSpecies[genCursorWithScroll].length; - for (let s = 0; s < 81; s++) { - const speciesId = s < genLimit ? this.genSpecies[genCursorWithScroll][s].speciesId : 0 as Species; - const slotVisible = !!speciesId; - if (slotVisible) { - this.updateStarterValueLabel(s); - } - this.starterValueLabels[s].setVisible(slotVisible); - const speciesVariants = speciesId && this.scene.gameData.dexData[speciesId].caughtAttr & DexAttr.SHINY - ? [ DexAttr.DEFAULT_VARIANT, DexAttr.VARIANT_2, DexAttr.VARIANT_3 ].filter(v => !!(this.scene.gameData.dexData[speciesId].caughtAttr & v)) - : []; - for (let v = 0; v < 3; v++) { - const hasVariant = speciesVariants.length > v; - this.shinyIcons[s][v].setVisible(slotVisible && hasVariant); - if (hasVariant) { - this.shinyIcons[s][v].setTint(getVariantTint(speciesVariants[v] === DexAttr.DEFAULT_VARIANT ? 0 : speciesVariants[v] === DexAttr.VARIANT_2 ? 1 : 2)); - } - } - this.starterPassiveBgs[s].setVisible(slotVisible && !!this.scene.gameData.starterData[speciesId].passiveAttr); - this.hiddenAbilityIcons[s].setVisible(slotVisible && !!this.scene.gameData.dexData[speciesId].caughtAttr && !!(this.scene.gameData.starterData[speciesId].abilityAttr & 4)); - this.classicWinIcons[s].setVisible(slotVisible && this.scene.gameData.starterData[speciesId].classicWinCount > 0); - - // 'Candy Icon' mode - if (this.scene.candyUpgradeDisplay === 0) { - - if (!starterColors[speciesId]) { - // Default to white if no colors are found - starterColors[speciesId] = [ "ffffff", "ffffff" ]; - } - - // Set the candy colors - this.candyUpgradeIcon[s].setTint(argbFromRgba(Utils.rgbHexToRgba(starterColors[speciesId][0]))); - this.candyUpgradeOverlayIcon[s].setTint(argbFromRgba(Utils.rgbHexToRgba(starterColors[speciesId][1]))); - - this.setUpgradeIcon(s); - } else if (this.scene.candyUpgradeDisplay === 1) { - this.candyUpgradeIcon[s].setVisible(false); - this.candyUpgradeOverlayIcon[s].setVisible(false); - } - } + this.filterBar.setCursor(cursor); } else { + cursor = Math.max(Math.min(this.filteredStarterContainers.length - 1, cursor),0); changed = super.setCursor(cursor); - this.cursorObj.setPosition(150 + 18 * (cursor % 9), 10 + 18 * Math.floor(cursor / 9)); + const pos = calcStarterPosition(cursor, this.scrollCursor); + this.cursorObj.setPosition(pos.x - 1, pos.y + 1); - const species = this.genSpecies[this.getGenCursorWithScroll()][cursor]; + const species = this.filteredStarterContainers[cursor]?.species; - const defaultDexAttr = this.scene.gameData.getSpeciesDefaultDexAttr(species, false, true); - const defaultProps = this.scene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); - const variant = defaultProps.variant; - const tint = getVariantTint(variant); - this.pokemonShinyIcon.setFrame(getVariantIcon(variant)); - this.pokemonShinyIcon.setTint(tint); - this.setSpecies(species); - this.updateInstructions(); + if (species) { + const defaultDexAttr = this.scene.gameData.getSpeciesDefaultDexAttr(species, false, true); + const defaultProps = this.scene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); + const variant = defaultProps.variant; + const tint = getVariantTint(variant); + this.pokemonShinyIcon.setFrame(getVariantIcon(variant)); + this.pokemonShinyIcon.setTint(tint); + this.setSpecies(species); + this.updateInstructions(); + } else { + console.warn("Species is undefined for cursor position", cursor); + this.setFilterMode(true); + } } return changed; } getGenCursorWithScroll(): integer { - return this.genCursor !== undefined - ? this.genCursor + this.genScrollCursor - : undefined; + return undefined; + // return this.genCursor !== undefined + // ? this.genCursor + this.genScrollCursor + // : undefined; } updateGenOptions(): void { - let text = ""; - for (let g = this.genScrollCursor; g <= this.genScrollCursor + 2; g++) { - let optionText = ""; - if (g === this.genScrollCursor && this.genScrollCursor) { - optionText = "↑"; - } else if (g === this.genScrollCursor + 2 && this.genScrollCursor < gens.length - 3) { - optionText = "↓"; - } else { - optionText = i18next.t(`starterSelectUiHandler:gen${g + 1}`); - } - text += `${text ? "\n" : ""}${optionText}`; - } - this.genOptionsText.setText(text); + // let text = ""; + // for (let g = this.genScrollCursor; g <= this.genScrollCursor + 2; g++) { + // let optionText = ""; + // if (g === this.genScrollCursor && this.genScrollCursor) { + // optionText = "↑"; + // } else if (g === this.genScrollCursor + 2 && this.genScrollCursor < gens.length - 3) { + // optionText = "↓"; + // } else { + // optionText = i18next.t(`starterSelectUiHandler:gen${g + 1}`); + // } + // text += `${text ? "\n" : ""}${optionText}`; + // } + // this.genOptionsText.setText(text); } - setGenMode(genMode: boolean): boolean { - this.genCursorObj.setVisible(genMode && !(this.startCursorObj.visible || this.starterIconsCursorObj.visible)); - this.cursorObj.setVisible(!genMode && !(this.startCursorObj.visible || this.starterIconsCursorObj.visible)); + setFilterMode(filterMode: boolean): boolean { + // this.genCursorObj.setVisible(!filterMode); + this.cursorObj.setVisible(!filterMode); + this.filterBar.cursorObj.setVisible(filterMode); - if (genMode !== this.genMode) { - this.genMode = genMode; - - this.setCursor(genMode ? this.genCursor : this.cursor); - if (genMode) { + if (filterMode !== this.filterMode) { + this.filterMode = filterMode; + this.setCursor(filterMode ? this.filterBarCursor : this.cursor); + if (filterMode) { this.setSpecies(null); } - if (this.starterIconsCursorObj.visible) { - this.setSpecies(this.genSpecies[this.starterGens[this.starterIconsCursorIndex]][this.starterCursors[this.starterIconsCursorIndex]]); - } return true; } @@ -2103,9 +2144,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler { moveStarterIconsCursor(index: number): void { this.starterIconsCursorObj.x = this.starterIcons[index].x + this.starterIconsCursorXOffset; this.starterIconsCursorObj.y = this.starterIcons[index].y + this.starterIconsCursorYOffset; - if (this.starterCursors.length > 0) { + if (this.starterSpecies.length > 0) { this.starterIconsCursorObj.setVisible(true); - this.setSpecies(this.genSpecies[this.starterGens[index]][this.starterCursors[index]]); + this.setSpecies(this.starterSpecies[index]); } else { this.starterIconsCursorObj.setVisible(false); this.setSpecies(null); @@ -2185,13 +2226,13 @@ export default class StarterSelectUiHandler extends MessageUiHandler { if (this.lastSpecies) { const dexAttr = this.scene.gameData.getSpeciesDefaultDexAttr(this.lastSpecies, false, true); const props = this.scene.gameData.getSpeciesDexAttrProps(this.lastSpecies, dexAttr); - const lastSpeciesIcon = (this.starterSelectGenIconContainers[this.lastSpecies.generation - 1].getAt(this.genSpecies[this.lastSpecies.generation - 1].indexOf(this.lastSpecies)) as Phaser.GameObjects.Sprite); + const speciesIndex = this.allSpecies.indexOf(this.lastSpecies); + const lastSpeciesIcon = this.starterContainer[speciesIndex].icon; this.checkIconId(lastSpeciesIcon, this.lastSpecies, props.female, props.formIndex, props.shiny, props.variant); this.iconAnimHandler.addOrUpdate(lastSpeciesIcon, PokemonIconAnimMode.NONE); // Resume the animation for the previously selected species - const speciesIndex = this.genSpecies[this.lastSpecies.generation - 1].indexOf(this.lastSpecies); - const icon = this.starterSelectGenIconContainers[this.lastSpecies.generation - 1].getAt(speciesIndex) as Phaser.GameObjects.Sprite; + const icon = this.starterContainer[speciesIndex].icon; this.scene.tweens.getTweensOf(icon).forEach(tween => tween.resume()); } @@ -2286,30 +2327,21 @@ export default class StarterSelectUiHandler extends MessageUiHandler { // Pause the animation when the species is selected - const speciesIndex = this.genSpecies[species.generation - 1].indexOf(species); - const icon = this.starterSelectGenIconContainers[species.generation - 1].getAt(speciesIndex) as Phaser.GameObjects.Sprite; + const speciesIndex = this.allSpecies.indexOf(species); + const icon = this.starterContainer[speciesIndex].icon; if (this.isUpgradeAnimationEnabled()) { this.scene.tweens.getTweensOf(icon).forEach(tween => tween.pause()); // Reset the position of the icon - const position = calcSpritePosition(speciesIndex); - icon.x = position.x; - icon.y = position.y; + icon.x = -2; + icon.y = 2; } // Initiates the small up and down idle animation this.iconAnimHandler.addOrUpdate(icon, PokemonIconAnimMode.PASSIVE); let starterIndex = -1; - - this.starterGens.every((g, i) => { - const starterSpecies = this.genSpecies[g][this.starterCursors[i]]; - if (starterSpecies.speciesId === species.speciesId) { - starterIndex = i; - return false; - } - return true; - }); + starterIndex = this.starterSpecies.indexOf(species); let props: DexAttrProps; @@ -2337,7 +2369,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.setTypeIcons(speciesForm.type1, speciesForm.type2); this.pokemonSprite.clearTint(); - if (this.pokerusCursors.find((cursor: integer, i: integer) => cursor === this.cursor && this.pokerusGens[i] === this.getGenCursorWithScroll())) { + if (this.pokerusSpecies.includes(species)) { handleTutorial(this.scene, Tutorial.Pokerus); } } else { @@ -2463,15 +2495,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { if (forSeen ? this.speciesStarterDexEntry?.seenAttr : this.speciesStarterDexEntry?.caughtAttr) { let starterIndex = -1; - - this.starterGens.every((g, i) => { - const starterSpecies = this.genSpecies[g][this.starterCursors[i]]; - if (starterSpecies.speciesId === species.speciesId) { - starterIndex = i; - return false; - } - return true; - }); + starterIndex = this.starterSpecies.indexOf(species); if (starterIndex > -1) { this.starterAttr[starterIndex] = this.dexAttrCursor; @@ -2495,15 +2519,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.pokemonSprite.setVisible(!this.statsMode); }); - if (!this.starterIconsCursorObj.visible) { - (this.starterSelectGenIconContainers[this.getGenCursorWithScroll()].getAt(this.cursor) as Phaser.GameObjects.Sprite) - .setTexture(species.getIconAtlasKey(formIndex, shiny, variant), species.getIconId(female, formIndex, shiny, variant)); - this.checkIconId((this.starterSelectGenIconContainers[this.getGenCursorWithScroll()].getAt(this.cursor) as Phaser.GameObjects.Sprite), species, female, formIndex, shiny, variant); - } else { - (this.starterSelectGenIconContainers[this.starterGens[this.starterIconsCursorIndex]].getAt(this.starterCursors[this.starterIconsCursorIndex]) as Phaser.GameObjects.Sprite) - .setTexture(species.getIconAtlasKey(formIndex, shiny, variant), species.getIconId(female, formIndex, shiny, variant)); - this.checkIconId((this.starterSelectGenIconContainers[this.starterGens[this.starterIconsCursorIndex]].getAt(this.starterCursors[this.starterIconsCursorIndex]) as Phaser.GameObjects.Sprite), species, female, formIndex, shiny, variant); - } + + const isValidForChallenge = new Utils.BooleanHolder(true); + Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, species, isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor), !!this.starterSpecies.length); + const starterSprite = this.filteredStarterContainers[this.cursor].icon as Phaser.GameObjects.Sprite; + starterSprite.setTexture(species.getIconAtlasKey(formIndex, shiny, variant), species.getIconId(female, formIndex, shiny, variant)); + this.filteredStarterContainers[this.cursor].checkIconId(female, formIndex, shiny, variant); this.canCycleShiny = !!(dexEntry.caughtAttr & DexAttr.NON_SHINY && dexEntry.caughtAttr & DexAttr.SHINY); this.canCycleGender = !!(dexEntry.caughtAttr & DexAttr.MALE && dexEntry.caughtAttr & DexAttr.FEMALE); this.canCycleAbility = [ abilityAttr & AbilityAttr.ABILITY_1, (abilityAttr & AbilityAttr.ABILITY_2) && species.ability2, abilityAttr & AbilityAttr.ABILITY_HIDDEN ].filter(a => a).length > 1; @@ -2642,15 +2663,14 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } popStarter(index: number): void { - this.starterGens.splice(index, 1); - this.starterCursors.splice(index, 1); + this.starterSpecies.splice(index, 1); this.starterAttr.splice(index, 1); this.starterAbilityIndexes.splice(index, 1); this.starterNatures.splice(index, 1); this.starterMovesets.splice(index, 1); - for (let s = 0; s < this.starterCursors.length; s++) { - const species = this.genSpecies[this.starterGens[s]][this.starterCursors[s]]; + for (let s = 0; s < this.starterSpecies.length; s++) { + const species = this.starterSpecies[s]; const currentDexAttr = this.scene.gameData.getSpeciesDefaultDexAttr(species, false, true); const props = this.scene.gameData.getSpeciesDexAttrProps(species, currentDexAttr); this.starterIcons[s].setTexture(species.getIconAtlasKey(props.formIndex, props.shiny, props.variant)); @@ -2660,18 +2680,18 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.starterCursorObjs[s].setVisible(this.starterCursorObjs[s + 1].visible); } } - this.starterCursorObjs[this.starterCursors.length].setVisible(false); - this.starterIcons[this.starterCursors.length].setTexture("pokemon_icons_0"); - this.starterIcons[this.starterCursors.length].setFrame("unknown"); + this.starterCursorObjs[this.starterSpecies.length].setVisible(false); + this.starterIcons[this.starterSpecies.length].setTexture("pokemon_icons_0"); + this.starterIcons[this.starterSpecies.length].setFrame("unknown"); if (this.starterIconsCursorObj.visible) { - if (this.starterIconsCursorIndex === this.starterCursors.length) { - if (this.starterCursors.length > 0) { + if (this.starterIconsCursorIndex === this.starterSpecies.length) { + if (this.starterSpecies.length > 0) { this.starterIconsCursorIndex--; } else { this.starterIconsCursorObj.setVisible(false); this.setSpecies(null); - this.setGenMode(true); + this.setFilterMode(true); } } this.moveStarterIconsCursor(this.starterIconsCursorIndex); @@ -2680,15 +2700,16 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.tryUpdateValue(); } - updateStarterValueLabel(cursor: integer): void { - const speciesId = this.genSpecies[this.getGenCursorWithScroll()][cursor].speciesId; + updateStarterValueLabel(starter: StarterContainer): void { + const speciesId = starter.species.speciesId; const baseStarterValue = speciesStarters[speciesId]; const starterValue = this.scene.gameData.getSpeciesStarterValue(speciesId); + starter.cost = starterValue; let valueStr = starterValue.toString(); if (valueStr.startsWith("0.")) { valueStr = valueStr.slice(1); } - this.starterValueLabels[cursor].setText(valueStr); + starter.label.setText(valueStr); let textStyle: TextStyle; switch (baseStarterValue - starterValue) { case 0: @@ -2702,12 +2723,14 @@ export default class StarterSelectUiHandler extends MessageUiHandler { textStyle = TextStyle.SUMMARY_GOLD; break; } - this.starterValueLabels[cursor].setColor(this.getTextColor(textStyle)); - this.starterValueLabels[cursor].setShadowColor(this.getTextColor(textStyle, true)); + if (baseStarterValue - starterValue > 0) { + starter.label.setColor(this.getTextColor(textStyle)); + starter.label.setShadowColor(this.getTextColor(textStyle, true)); + } } tryUpdateValue(add?: integer, addingToParty?: boolean): boolean { - const value = this.starterGens.reduce((total: integer, gen: integer, i: integer) => total += this.scene.gameData.getSpeciesStarterValue(this.genSpecies[gen][this.starterCursors[i]].speciesId), 0); + const value = this.starterSpecies.map(s => s.generation).reduce((total: integer, gen: integer, i: integer) => total += this.scene.gameData.getSpeciesStarterValue(this.starterSpecies[i].speciesId), 0); const newValue = value + (add || 0); const valueLimit = this.getValueLimit(); const overLimit = newValue > valueLimit; @@ -2722,12 +2745,10 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.scene.time.delayedCall(Utils.fixedInt(500), () => this.tryUpdateValue()); return false; } - - let isPartyValid: boolean = this.isPartyValid(); // this checks to see if the party is valid if (addingToParty) { // this does a check to see if the pokemon being added is valid; if so, it will update the isPartyValid boolean const isNewPokemonValid = new Utils.BooleanHolder(true); - Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, this.genSpecies[this.getGenCursorWithScroll()][this.cursor], isNewPokemonValid, this.scene.gameData.getSpeciesDexAttrProps(this.genSpecies[this.getGenCursorWithScroll()][this.cursor], this.scene.gameData.getSpeciesDefaultDexAttr(this.genSpecies[this.getGenCursorWithScroll()][this.cursor], false, true)), !!(this.starterGens.length), false, false); - isPartyValid = isPartyValid || isNewPokemonValid.value; + const species = this.filteredStarterContainers[this.cursor].species; + Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, species, isNewPokemonValid, this.scene.gameData.getSpeciesDexAttrProps(species, this.scene.gameData.getSpeciesDefaultDexAttr(species, false, true)), !!(this.starterSpecies.length + (add ? 1 : 0))); } /** @@ -2735,56 +2756,50 @@ export default class StarterSelectUiHandler extends MessageUiHandler { */ this.canAddParty = false; const remainValue = valueLimit - newValue; - for (let g = 0; g < this.genSpecies.length; g++) { - for (let s = 0; s < this.genSpecies[g].length; s++) { - /** Cost of pokemon species */ - const speciesStarterValue = this.scene.gameData.getSpeciesStarterValue(this.genSpecies[g][s].speciesId); - /** Used to detect if this pokemon is registered in starter */ - const speciesStarterDexEntry = this.scene.gameData.dexData[this.genSpecies[g][s].speciesId]; - /** {@linkcode Phaser.GameObjects.Sprite} object of Pokémon for setting the alpha value */ - const speciesSprite = this.starterSelectGenIconContainers[g].getAt(s) as Phaser.GameObjects.Sprite; + for (let s = 0; s < this.allSpecies.length; s++) { + /** Cost of pokemon species */ + const speciesStarterValue = this.scene.gameData.getSpeciesStarterValue(this.allSpecies[s].speciesId); + /** Used to detect if this pokemon is registered in starter */ + const speciesStarterDexEntry = this.scene.gameData.dexData[this.allSpecies[s].speciesId]; + /** {@linkcode Phaser.GameObjects.Sprite} object of Pokémon for setting the alpha value */ + const speciesSprite = this.starterContainer[s].icon; + /** + * If remainValue greater than or equal pokemon species and the pokemon is legal for this challenge, the user can select. + * so that the alpha value of pokemon sprite set 1. + * + * However, if isPartyValid is false, that means none of the party members are valid for the run. In this case, we should + * check the challenge to make sure evolutions and forms aren't being checked for mono type runs. + * This will let us set the sprite's alpha to show it can't be selected + * + * If speciesStarterDexEntry?.caughtAttr is true, this species registered in stater. + * we change to can AddParty value to true since the user has enough cost to choose this pokemon and this pokemon registered too. + */ + const isValidForChallenge = new Utils.BooleanHolder(true); + Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, this.allSpecies[s], isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(this.allSpecies[s], this.scene.gameData.getSpeciesDefaultDexAttr(this.allSpecies[s], false, true)), !!(this.starterSpecies.length + (add ? 1 : 0))); + + const canBeChosen = remainValue >= speciesStarterValue && isValidForChallenge.value; + + const isPokemonInParty = this.isInParty(this.allSpecies[s])[0]; // this will get the valud of isDupe from isInParty. This will let us see if the pokemon in question is in our party already so we don't grey out the sprites if they're invalid + + /* This code does a check to tell whether or not a sprite should be lit up or greyed out. There are 3 ways a pokemon's sprite should be lit up: + * 1) If it's in your party, it's a valid pokemon (i.e. for challenge) and you have enough points to have it + * 2) If it's in your party, it's not valid (i.e. for challenges), and you have enough points to have it + * 3) If it's not in your party, but it's a valid pokemon and you have enough points for it + * Any other time, the sprite should be greyed out. + * For example, if it's in your party, valid, but costs too much, or if it's not in your party and not valid, regardless of cost + */ + if (canBeChosen || (isPokemonInParty && remainValue >= speciesStarterValue)) { + speciesSprite.setAlpha(1); + if (speciesStarterDexEntry?.caughtAttr) { + this.canAddParty = true; + } + } else { /** - * If remainValue greater than or equal pokemon species and the pokemon is legal for this challenge, the user can select. - * so that the alpha value of pokemon sprite set 1. - * - * However, if isPartyValid is false, that means none of the party members are valid for the run. In this case, we should - * check the challenge to make sure evolutions and forms aren't being checked for mono type runs. - * This will let us set the sprite's alpha to show it can't be selected - * - * If speciesStarterDexEntry?.caughtAttr is true, this species registered in stater. - * we change to can AddParty value to true since the user has enough cost to choose this pokemon and this pokemon registered too. + * If it can't be chosen, the user can't select. + * so that the alpha value of pokemon sprite set 0.375. */ - const isValidForChallenge = new Utils.BooleanHolder(true); - if (isPartyValid) { // we have two checks here - one for the party being valid and one for not. This comes from mono type challenges - if the party is valid it will check pokemon's evolutions and forms, and if it's not valid it won't check their evolutions and forms - Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, this.genSpecies[g][s], isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(this.genSpecies[g][s], this.scene.gameData.getSpeciesDefaultDexAttr(this.genSpecies[g][s], false, true)), !!(this.starterGens.length + (add ? 1 : 0))); - } else { - Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, this.genSpecies[g][s], isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(this.genSpecies[g][s], this.scene.gameData.getSpeciesDefaultDexAttr(this.genSpecies[g][s], false, true)), !!(this.starterGens.length + (add ? 1 : 0)), false, false); - } - - const canBeChosen = remainValue >= speciesStarterValue && isValidForChallenge.value; - - const isPokemonInParty = this.isInParty(g, s)[0]; // this will get the valud of isDupe from isInParty. This will let us see if the pokemon in question is in our party already so we don't grey out the sprites if they're invalid - - /* This code does a check to tell whether or not a sprite should be lit up or greyed out. There are 3 ways a pokemon's sprite should be lit up: - * 1) If it's in your party, it's a valid pokemon (i.e. for challenge) and you have enough points to have it - * 2) If it's in your party, it's not valid (i.e. for challenges), and you have enough points to have it - * 3) If it's not in your party, but it's a valid pokemon and you have enough points for it - * Any other time, the sprite should be greyed out. - * For example, if it's in your party, valid, but costs too much, or if it's not in your party and not valid, regardless of cost - */ - if (canBeChosen || (isPokemonInParty && remainValue >= speciesStarterValue)) { - speciesSprite.setAlpha(1); - if (speciesStarterDexEntry?.caughtAttr) { - this.canAddParty = true; - } - } else { - /** - * If it can't be chosen, the user can't select. - * so that the alpha value of pokemon sprite set 0.375. - */ - speciesSprite.setAlpha(0.375); - } + speciesSprite.setAlpha(0.375); } } @@ -2793,7 +2808,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } tryStart(manualTrigger: boolean = false): boolean { - if (!this.starterGens.length) { + if (!this.starterSpecies.length) { return false; } @@ -2802,7 +2817,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const cancel = () => { ui.setMode(Mode.STARTER_SELECT); if (!manualTrigger) { - this.popStarter(this.starterGens.length - 1); + this.popStarter(this.starterSpecies.length - 1); } this.clearText(); }; @@ -2818,8 +2833,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const thisObj = this; const originalStarterSelectCallback = this.starterSelectCallback; this.starterSelectCallback = null; - originalStarterSelectCallback(new Array(this.starterGens.length).fill(0).map(function (_, i) { - const starterSpecies = thisObj.genSpecies[thisObj.starterGens[i]][thisObj.starterCursors[i]]; + originalStarterSelectCallback(new Array(this.starterSpecies.length).fill(0).map(function (_, i) { + const starterSpecies = thisObj.starterSpecies[i]; return { species: starterSpecies, dexAttr: thisObj.starterAttr[i], @@ -2827,7 +2842,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { passive: !(thisObj.scene.gameData.starterData[starterSpecies.speciesId].passiveAttr ^ (PassiveAttr.ENABLED | PassiveAttr.UNLOCKED)), nature: thisObj.starterNatures[i] as Nature, moveset: thisObj.starterMovesets[i], - pokerus: !![0, 1, 2].filter(n => thisObj.pokerusGens[n] === starterSpecies.generation - 1 && thisObj.pokerusCursors[n] === thisObj.genSpecies[starterSpecies.generation - 1].indexOf(starterSpecies)).length + pokerus: thisObj.pokerusSpecies.includes(starterSpecies) }; })); }; @@ -2847,10 +2862,10 @@ export default class StarterSelectUiHandler extends MessageUiHandler { */ isPartyValid(): boolean { let canStart = false; - for (let s = 0; s < this.starterGens.length; s++) { + for (let s = 0; s < this.starterSpecies.length; s++) { const isValidForChallenge = new Utils.BooleanHolder(true); - const species = this.genSpecies[this.starterGens[s]][this.starterCursors[s]]; - Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, species, isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor), !!(this.starterGens.length), false, false); + const species = this.starterSpecies[s]; + Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, species, isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor), !!this.starterSpecies.length); canStart = canStart || isValidForChallenge.value; } return canStart; @@ -2911,8 +2926,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.starterSelectContainer.setVisible(false); this.blockInput = false; - while (this.starterCursors.length) { - this.popStarter(this.starterCursors.length - 1); + while (this.starterSpecies.length) { + this.popStarter(this.starterSpecies.length - 1); } if (this.statsMode) {